From e68e7ff966c1c80a1fe484c7f17311fb75e9b478 Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Thu, 5 Feb 2026 15:24:20 +0100 Subject: [PATCH] android sdk local resolve --- openfeature-provider/android/.gitignore | 33 + openfeature-provider/android/Makefile | 22 + openfeature-provider/android/README.md | 161 +++ openfeature-provider/android/build.gradle.kts | 7 + .../confidence-provider/build.gradle.kts | 175 +++ .../confidence-provider/consumer-rules.pro | 8 + .../confidence-provider/proguard-rules.pro | 18 + .../src/main/AndroidManifest.xml | 4 + .../android/ConfidenceLocalProvider.kt | 431 ++++++ .../confidence/android/ConfidenceValue.kt | 62 + .../spotify/confidence/android/Evaluation.kt | 64 + .../confidence/android/GrpcWasmFlagLogger.kt | 138 ++ .../android/InitialisationStrategy.kt | 24 + .../confidence/android/LocalProviderConfig.kt | 56 + .../android/MaterializationStore.kt | 127 ++ .../spotify/confidence/android/ResolverApi.kt | 35 + .../confidence/android/StateFetcher.kt | 101 ++ .../confidence/android/SwapWasmResolverApi.kt | 106 ++ .../spotify/confidence/android/TypeMapper.kt | 168 +++ .../UnsupportedMaterializationStore.kt | 18 + .../confidence/android/ValueConversions.kt | 98 ++ .../com/spotify/confidence/android/Version.kt | 8 + .../confidence/android/WasmFlagLogger.kt | 21 + .../confidence/android/WasmResolver.kt | 243 ++++ .../proto/confidence/api/annotations.proto | 37 + .../confidence/flags/resolver/v1/api.proto | 183 +++ .../flags/resolver/v1/internal_api.proto | 189 +++ .../confidence/flags/resolver/v1/types.proto | 70 + .../flags/resolver/v1/wasm_api.proto | 75 + .../confidence/flags/types/v1/types.proto | 73 + .../main/proto/google/api/annotations.proto | 31 + .../src/main/proto/google/api/client.proto | 99 ++ .../proto/google/api/field_behavior.proto | 104 ++ .../src/main/proto/google/api/http.proto | 375 +++++ .../src/main/proto/google/api/httpbody.proto | 18 + .../src/main/proto/google/api/resource.proto | 238 ++++ .../main/proto/google/api/visibility.proto | 113 ++ .../src/main/proto/google/protobuf/any.proto | 162 +++ .../src/main/proto/google/protobuf/api.proto | 207 +++ .../proto/google/protobuf/descriptor.proto | 1218 +++++++++++++++++ .../main/proto/google/protobuf/duration.proto | 115 ++ .../main/proto/google/protobuf/empty.proto | 51 + .../proto/google/protobuf/field_mask.proto | 245 ++++ .../google/protobuf/source_context.proto | 48 + .../main/proto/google/protobuf/struct.proto | 95 ++ .../proto/google/protobuf/timestamp.proto | 144 ++ .../src/main/proto/google/protobuf/type.proto | 193 +++ .../main/proto/google/protobuf/wrappers.proto | 123 ++ .../src/main/proto/google/type/decimal.proto | 95 ++ .../src/main/proto/messages.proto | 37 + .../resources/wasm/confidence_resolver.wasm | Bin 0 -> 451028 bytes .../android/demo-app/build.gradle.kts | 86 ++ .../demo-app/src/main/AndroidManifest.xml | 24 + .../spotify/confidence/demo/MainActivity.kt | 372 +++++ .../res/drawable/ic_launcher_foreground.xml | 16 + .../src/main/res/layout/activity_main.xml | 339 +++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../demo-app/src/main/res/values/colors.xml | 4 + .../demo-app/src/main/res/values/strings.xml | 4 + .../demo-app/src/main/res/values/themes.xml | 11 + .../android/gradle.properties | 9 + .../android/gradle/libs.versions.toml | 54 + .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 60756 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + openfeature-provider/android/gradlew | 240 ++++ openfeature-provider/android/gradlew.bat | 91 ++ .../android/settings.gradle.kts | 19 + 68 files changed, 7750 insertions(+) create mode 100644 openfeature-provider/android/.gitignore create mode 100644 openfeature-provider/android/Makefile create mode 100644 openfeature-provider/android/README.md create mode 100644 openfeature-provider/android/build.gradle.kts create mode 100644 openfeature-provider/android/confidence-provider/build.gradle.kts create mode 100644 openfeature-provider/android/confidence-provider/consumer-rules.pro create mode 100644 openfeature-provider/android/confidence-provider/proguard-rules.pro create mode 100644 openfeature-provider/android/confidence-provider/src/main/AndroidManifest.xml create mode 100644 openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/ConfidenceLocalProvider.kt create mode 100644 openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/ConfidenceValue.kt create mode 100644 openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/Evaluation.kt create mode 100644 openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/GrpcWasmFlagLogger.kt create mode 100644 openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/InitialisationStrategy.kt create mode 100644 openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/LocalProviderConfig.kt create mode 100644 openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/MaterializationStore.kt create mode 100644 openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/ResolverApi.kt create mode 100644 openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/StateFetcher.kt create mode 100644 openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/SwapWasmResolverApi.kt create mode 100644 openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/TypeMapper.kt create mode 100644 openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/UnsupportedMaterializationStore.kt create mode 100644 openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/ValueConversions.kt create mode 100644 openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/Version.kt create mode 100644 openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/WasmFlagLogger.kt create mode 100644 openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/WasmResolver.kt create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/confidence/api/annotations.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/confidence/flags/resolver/v1/api.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/confidence/flags/resolver/v1/internal_api.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/confidence/flags/resolver/v1/types.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/confidence/flags/resolver/v1/wasm_api.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/confidence/flags/types/v1/types.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/google/api/annotations.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/google/api/client.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/google/api/field_behavior.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/google/api/http.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/google/api/httpbody.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/google/api/resource.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/google/api/visibility.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/any.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/api.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/descriptor.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/duration.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/empty.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/field_mask.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/source_context.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/struct.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/timestamp.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/type.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/wrappers.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/google/type/decimal.proto create mode 100644 openfeature-provider/android/confidence-provider/src/main/proto/messages.proto create mode 100755 openfeature-provider/android/confidence-provider/src/main/resources/wasm/confidence_resolver.wasm create mode 100644 openfeature-provider/android/demo-app/build.gradle.kts create mode 100644 openfeature-provider/android/demo-app/src/main/AndroidManifest.xml create mode 100644 openfeature-provider/android/demo-app/src/main/kotlin/com/spotify/confidence/demo/MainActivity.kt create mode 100644 openfeature-provider/android/demo-app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 openfeature-provider/android/demo-app/src/main/res/layout/activity_main.xml create mode 100644 openfeature-provider/android/demo-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 openfeature-provider/android/demo-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 openfeature-provider/android/demo-app/src/main/res/values/colors.xml create mode 100644 openfeature-provider/android/demo-app/src/main/res/values/strings.xml create mode 100644 openfeature-provider/android/demo-app/src/main/res/values/themes.xml create mode 100644 openfeature-provider/android/gradle.properties create mode 100644 openfeature-provider/android/gradle/libs.versions.toml create mode 100644 openfeature-provider/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 openfeature-provider/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 openfeature-provider/android/gradlew create mode 100644 openfeature-provider/android/gradlew.bat create mode 100644 openfeature-provider/android/settings.gradle.kts diff --git a/openfeature-provider/android/.gitignore b/openfeature-provider/android/.gitignore new file mode 100644 index 00000000..3abdecab --- /dev/null +++ b/openfeature-provider/android/.gitignore @@ -0,0 +1,33 @@ +# Gradle +.gradle/ +build/ +**/build/ + +# IDE +.idea/ +*.iml +local.properties + +# OS +.DS_Store +Thumbs.db + +# Kotlin +*.class +*.log +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# Android +*.apk +*.ap_ +*.aab +*.dex +/captures +.externalNativeBuild +.cxx diff --git a/openfeature-provider/android/Makefile b/openfeature-provider/android/Makefile new file mode 100644 index 00000000..b1039a17 --- /dev/null +++ b/openfeature-provider/android/Makefile @@ -0,0 +1,22 @@ +.PHONY: build test clean + +build: + ./gradlew build + +test: + ./gradlew test + +clean: + ./gradlew clean + +assemble: + ./gradlew assemble + +lint: + ./gradlew lint + +publish-local: + ./gradlew publishToMavenLocal + +copy-wasm: + cp ../../wasm/confidence_resolver.wasm confidence-provider/src/main/resources/wasm/ diff --git a/openfeature-provider/android/README.md b/openfeature-provider/android/README.md new file mode 100644 index 00000000..4ae26d6c --- /dev/null +++ b/openfeature-provider/android/README.md @@ -0,0 +1,161 @@ +# Confidence Android OpenFeature Provider + +Local-resolution OpenFeature provider for Android that evaluates Confidence feature flags using a WebAssembly (WASM) resolver. + +## Features + +- **Local Resolution**: Evaluates flags locally using an embedded WASM resolver for fast, low-latency flag evaluation +- **Automatic State Sync**: Periodically syncs flag configurations from the Confidence CDN +- **Static Context Support**: Designed for Android's static context pattern where evaluation context is set once and reused +- **Sticky Assignments**: Supports maintaining consistent variant assignments via materialization stores +- **Background Processing**: Uses Kotlin coroutines for non-blocking state polling and log flushing + +## Requirements + +- Android API 26+ (Android 8.0 Oreo) +- Kotlin 1.9+ + +## Installation + +Add the dependency to your `build.gradle.kts`: + +```kotlin +dependencies { + implementation("com.spotify.confidence:confidence-android-provider:0.1.0") +} +``` + +## Usage + +### Basic Setup + +```kotlin +import com.spotify.confidence.android.ConfidenceLocalProvider +import dev.openfeature.sdk.OpenFeatureAPI +import dev.openfeature.sdk.ImmutableContext +import dev.openfeature.sdk.Value + +// Create the provider +val provider = ConfidenceLocalProvider("your-client-secret") + +// Set global evaluation context (Android static context pattern) +OpenFeatureAPI.setEvaluationContext( + ImmutableContext( + targetingKey = "user-123", + attributes = mapOf( + "country" to Value.String("US"), + "premium" to Value.Boolean(true) + ) + ) +) + +// Initialize the provider +OpenFeatureAPI.setProviderAndWait(provider) + +// Get a client and resolve flags +val client = OpenFeatureAPI.getClient() +val showFeature = client.getBooleanValue("show-new-feature", false) +val buttonColor = client.getStringValue("button-color", "blue") +``` + +### Advanced Configuration + +```kotlin +import com.spotify.confidence.android.LocalProviderConfig +import java.time.Duration + +val config = LocalProviderConfig.Builder() + .statePollInterval(Duration.ofSeconds(60)) // How often to check for state updates + .logFlushInterval(Duration.ofSeconds(30)) // How often to flush flag logs + .useRemoteMaterializationStore(true) // Enable remote sticky assignments + .build() + +val provider = ConfidenceLocalProvider(config, "your-client-secret") +``` + +### Nested Flag Values + +Access nested values using dot notation: + +```kotlin +// If your flag has a structure like: { "button": { "color": "red", "size": 12 } } +val buttonColor = client.getStringValue("my-flag.button.color", "blue") +val buttonSize = client.getIntegerValue("my-flag.button.size", 10) +``` + +### Shutdown + +Clean up resources when the provider is no longer needed: + +```kotlin +provider.shutdown() +``` + +## Static Context Pattern + +Android's OpenFeature SDK uses a static context pattern, meaning: + +1. The evaluation context is set once during app initialization +2. This context is reused for all subsequent flag evaluations +3. Context updates are applied to all future evaluations + +This differs from server-side SDKs where context is passed per-request. The Confidence provider fully supports this pattern: + +```kotlin +// Set context once at startup +OpenFeatureAPI.setEvaluationContext( + ImmutableContext( + targetingKey = userId, + attributes = userAttributes + ) +) + +// All evaluations use this context +val feature1 = client.getBooleanValue("feature-1", false) +val feature2 = client.getStringValue("feature-2", "default") + +// Update context when user info changes +OpenFeatureAPI.setEvaluationContext( + ImmutableContext( + targetingKey = newUserId, + attributes = newUserAttributes + ) +) +``` + +## Architecture + +The provider consists of these components: + +- **ConfidenceLocalProvider**: Main OpenFeature provider implementation +- **WasmResolver**: Interfaces with the Confidence WASM resolver binary via Chicory +- **SwapWasmResolverApi**: Manages WASM instance lifecycle with hot-swapping support +- **StateFetcher**: Fetches and caches resolver state from CDN with ETag support +- **GrpcWasmFlagLogger**: Sends flag resolution logs via gRPC +- **MaterializationStore**: Interface for sticky assignment storage +- **ConfidenceValue**: Type-safe value system compatible with Confidence SDK +- **ValueConversions**: Utilities for converting between OpenFeature and Confidence types +- **TypeMapper**: Converts between Protobuf and OpenFeature types + +## Compatibility with Existing Confidence SDK + +This provider is designed to be compatible with the [Confidence SDK Android](https://github.com/spotify/confidence-sdk-android). +It uses the same: +- Value type system (ConfidenceValue) +- Context handling patterns +- Initialization strategies + +The main difference is that this provider uses local WASM-based resolution instead of remote HTTP/gRPC calls. + +## Configuration Options + +| Option | Description | Default | +|--------|-------------|---------| +| `statePollInterval` | How often to poll for state updates | 30 seconds | +| `logFlushInterval` | How often to flush flag logs | 10 seconds | +| `initialisationStrategy` | How to initialize (FetchAndActivate or ActivateAndFetchAsync) | FetchAndActivate | +| `useRemoteMaterializationStore` | Whether to use remote gRPC for sticky assignments | false | + +## License + +Apache 2.0 diff --git a/openfeature-provider/android/build.gradle.kts b/openfeature-provider/android/build.gradle.kts new file mode 100644 index 00000000..41f1b97c --- /dev/null +++ b/openfeature-provider/android/build.gradle.kts @@ -0,0 +1,7 @@ +// Root build file for Confidence Android OpenFeature Provider +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.protobuf) apply false +} diff --git a/openfeature-provider/android/confidence-provider/build.gradle.kts b/openfeature-provider/android/confidence-provider/build.gradle.kts new file mode 100644 index 00000000..c39889b5 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/build.gradle.kts @@ -0,0 +1,175 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.protobuf) +} + +// Configuration for Chicory build-time WASM compilation +val chicoryCompiler: Configuration by configurations.creating { + isTransitive = false +} + +// Task to compile WASM to JVM bytecode at build time +val compileWasm by tasks.registering { + group = "build" + description = "Compiles WASM to JVM bytecode using Chicory build-time compiler" + + val wasmFile = file("src/main/resources/wasm/confidence_resolver.wasm") + val outputSourceDir = layout.buildDirectory.dir("generated/wasm-sources/java") + val outputClassDir = layout.buildDirectory.dir("generated/wasm-classes") + val outputMetaDir = layout.buildDirectory.dir("generated/wasm-meta") + + inputs.file(wasmFile) + inputs.files(chicoryCompiler) + outputs.dir(outputSourceDir) + outputs.dir(outputClassDir) + outputs.dir(outputMetaDir) + + doLast { + outputSourceDir.get().asFile.mkdirs() + outputClassDir.get().asFile.mkdirs() + outputMetaDir.get().asFile.mkdirs() + + val cliJar = chicoryCompiler.singleFile + exec { + commandLine( + "java", "-jar", + cliJar.absolutePath, + "--source-dir=${outputSourceDir.get().asFile.absolutePath}", + "--class-dir=${outputClassDir.get().asFile.absolutePath}", + "--wasm-dir=${outputMetaDir.get().asFile.absolutePath}", + "--prefix=com.spotify.confidence.wasm.ConfidenceResolver", + "--interpreter-fallback=WARN", + wasmFile.absolutePath + ) + } + } +} + +android { + namespace = "com.spotify.confidence.android" + compileSdk = 34 + + defaultConfig { + minSdk = 26 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" + } + + packaging { + resources { + // Exclude proto files that conflict with protobuf-javalite + excludes += "google/protobuf/*.proto" + excludes += "google/api/*.proto" + excludes += "google/type/*.proto" + } + } + + sourceSets { + getByName("main") { + resources.srcDirs("src/main/resources") + // Include pre-compiled WASM meta file + resources.srcDirs(layout.buildDirectory.dir("generated/wasm-meta")) + // Include pre-compiled WASM Java source + java.srcDirs(layout.buildDirectory.dir("generated/wasm-sources/java")) + } + } +} + +// Wire WASM compilation into the build +tasks.named("preBuild") { + dependsOn("compileWasm") +} + +// Add pre-compiled WASM classes to the compile classpath and bundle +android.libraryVariants.all { + val variant = this + val variantName = variant.name.replaceFirstChar { it.uppercaseChar() } + + // Add to compile classpath + val compileTask = tasks.named("compile${variantName}JavaWithJavac") + compileTask.configure { + dependsOn("compileWasm") + classpath += files(layout.buildDirectory.dir("generated/wasm-classes")) + } + + // Register the pre-compiled classes as additional class files + variant.registerPostJavacGeneratedBytecode(files(layout.buildDirectory.dir("generated/wasm-classes"))) +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:${libs.versions.protobuf.get()}" + } + plugins { + create("grpc") { + artifact = "io.grpc:protoc-gen-grpc-java:${libs.versions.grpc.get()}" + } + } + generateProtoTasks { + all().forEach { task -> + task.builtins { + create("java") { + option("lite") + } + } + task.plugins { + create("grpc") { + option("lite") + } + } + } + } +} + + +dependencies { + // Chicory build-time WASM compiler (CLI tool) + chicoryCompiler("com.dylibso.chicory:build-time-compiler-cli-experimental:${libs.versions.chicory.get()}") + + // OpenFeature SDK for Android + implementation(libs.openfeature.android) + + // WASM runtime - Chicory + implementation(libs.bundles.chicory) + + // Protobuf + implementation(libs.protobuf.javalite) + + // gRPC for Android + implementation(libs.bundles.grpc) + + // javax.annotation for gRPC generated code + compileOnly("javax.annotation:javax.annotation-api:1.3.2") + + // Networking + implementation(libs.okhttp) + + // Coroutines + implementation(libs.bundles.coroutines) + + // Testing + testImplementation(libs.junit) + testImplementation(libs.kotlinx.coroutines.test) + testImplementation(libs.mockk) +} diff --git a/openfeature-provider/android/confidence-provider/consumer-rules.pro b/openfeature-provider/android/confidence-provider/consumer-rules.pro new file mode 100644 index 00000000..8a5f9a84 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/consumer-rules.pro @@ -0,0 +1,8 @@ +# Consumer ProGuard Rules for Confidence Android Provider +# These rules are automatically included in consumer apps + +# Keep protobuf generated classes +-keep class * extends com.google.protobuf.GeneratedMessageLite { *; } + +# Keep gRPC service stubs +-keepclassmembers class * extends io.grpc.stub.AbstractStub { *; } diff --git a/openfeature-provider/android/confidence-provider/proguard-rules.pro b/openfeature-provider/android/confidence-provider/proguard-rules.pro new file mode 100644 index 00000000..742af77e --- /dev/null +++ b/openfeature-provider/android/confidence-provider/proguard-rules.pro @@ -0,0 +1,18 @@ +# Confidence Android Provider ProGuard Rules + +# Keep protobuf classes +-keep class com.google.protobuf.** { *; } +-keep class * extends com.google.protobuf.GeneratedMessageLite { *; } + +# Keep gRPC classes +-keep class io.grpc.** { *; } +-keepnames class io.grpc.** { *; } + +# Keep Chicory WASM runtime +-keep class com.dylibso.chicory.** { *; } + +# Keep OpenFeature classes +-keep class dev.openfeature.** { *; } + +# Keep our SDK classes +-keep class com.spotify.confidence.android.** { *; } diff --git a/openfeature-provider/android/confidence-provider/src/main/AndroidManifest.xml b/openfeature-provider/android/confidence-provider/src/main/AndroidManifest.xml new file mode 100644 index 00000000..19d2638e --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/ConfidenceLocalProvider.kt b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/ConfidenceLocalProvider.kt new file mode 100644 index 00000000..7979fa91 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/ConfidenceLocalProvider.kt @@ -0,0 +1,431 @@ +package com.spotify.confidence.android + +import android.util.Log +import com.google.protobuf.Struct +import com.spotify.confidence.flags.resolver.v1.ResolveFlagsRequest +import com.spotify.confidence.flags.resolver.v1.ResolveWithStickyRequest +import com.spotify.confidence.flags.resolver.v1.Sdk +import com.spotify.confidence.flags.resolver.v1.SdkId +import dev.openfeature.kotlin.sdk.EvaluationContext +import dev.openfeature.kotlin.sdk.FeatureProvider +import dev.openfeature.kotlin.sdk.Hook +import dev.openfeature.kotlin.sdk.ProviderEvaluation +import dev.openfeature.kotlin.sdk.ProviderMetadata +import dev.openfeature.kotlin.sdk.Value +import dev.openfeature.kotlin.sdk.exceptions.OpenFeatureError +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import java.util.concurrent.atomic.AtomicReference + +/** + * OpenFeature provider for Confidence feature flags using local resolution. + * + * This provider evaluates feature flags locally using a WebAssembly (WASM) resolver. It + * periodically syncs flag configurations from the Confidence service and caches them locally for + * fast, low-latency flag evaluation. + * + * **Android Static Context Pattern:** + * Unlike server-side providers where context is passed per-request, Android uses a static + * context pattern. The evaluation context is typically set once during app initialization + * via `OpenFeatureAPI.setEvaluationContext()` and used for all subsequent flag evaluations. + * This provider fully supports this pattern while still allowing per-evaluation context + * overrides when needed. + * + * **Usage Example:** + * ```kotlin + * val clientSecret = "your-application-client-secret" + * val config = LocalProviderConfig() + * val provider = ConfidenceLocalProvider(config, clientSecret) + * + * // Set global evaluation context (static context pattern) + * OpenFeatureAPI.setEvaluationContext( + * ImmutableContext( + * targetingKey = "user-123", + * attributes = mapOf("country" to Value.String("US")) + * ) + * ) + * + * OpenFeatureAPI.setProviderAndWait(provider) + * + * val client = OpenFeatureAPI.getClient() + * val flagValue = client.getStringValue("my-flag", "default-value") + * ``` + */ +class ConfidenceLocalProvider( + private val config: LocalProviderConfig = LocalProviderConfig(), + private val clientSecret: String, + private val materializationStore: MaterializationStore = UnsupportedMaterializationStore() +) : FeatureProvider { + + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + private var statePollJob: Job? = null + private var logFlushJob: Job? = null + + private val stateFetcher = StateFetcher(clientSecret, config.httpClient ?: okhttp3.OkHttpClient()) + private val flagLogger: WasmFlagLogger = GrpcWasmFlagLogger(clientSecret, config.channelFactory) + private lateinit var wasmResolveApi: ResolverApi + + private val resolverState = AtomicReference(ByteArray(0)) + private val accountIdRef = AtomicReference("") + + @Volatile + private var initialized = false + + companion object { + private const val TAG = "ConfidenceLocalProvider" + internal const val PROVIDER_ID = "SDK_ID_KOTLIN_CONFIDENCE_LOCAL" + + /** + * Creates a new ConfidenceLocalProvider with the given configuration. + * + * @param clientSecret The Confidence client secret + * @param config Configuration options for the provider + * @param materializationStore Optional custom materialization store for sticky assignments + * @return A configured ConfidenceLocalProvider instance + */ + @JvmStatic + @JvmOverloads + fun create( + clientSecret: String, + config: LocalProviderConfig = LocalProviderConfig(), + materializationStore: MaterializationStore = UnsupportedMaterializationStore() + ): ConfidenceLocalProvider { + return ConfidenceLocalProvider( + config = config, + clientSecret = clientSecret, + materializationStore = materializationStore + ) + } + } + + /** + * Secondary constructor for simple initialization without custom materialization store. + */ + constructor(clientSecret: String) : this(LocalProviderConfig(), clientSecret) + + /** + * Secondary constructor with config and clientSecret. + */ + constructor(config: LocalProviderConfig, clientSecret: String) : this( + config, + clientSecret, + UnsupportedMaterializationStore() + ) + + override val metadata: ProviderMetadata = object : ProviderMetadata { + override val name: String = "confidence-sdk-android-local" + } + + override val hooks: List> = emptyList() + + override suspend fun initialize(initialContext: EvaluationContext?) { + try { + // Fetch initial state + stateFetcher.reload() + resolverState.set(stateFetcher.provide()) + accountIdRef.set(stateFetcher.accountId()) + + // Only initialize WASM if we got valid state + if (accountIdRef.get().isNotEmpty()) { + wasmResolveApi = SwapWasmResolverApi( + flagLogger, + resolverState.get(), + accountIdRef.get(), + materializationStore + ) + initialized = true + } else { + Log.w(TAG, "Initial state load failed, provider starting in NOT_READY state, serving default values.") + } + + // Start background tasks + startBackgroundTasks() + } catch (e: Exception) { + Log.e(TAG, "Failed to initialize provider", e) + } + } + + override fun shutdown() { + statePollJob?.cancel() + logFlushJob?.cancel() + + if (initialized) { + wasmResolveApi.close() + } + } + + /** + * Called when the evaluation context is updated. + * This triggers a state refresh to ensure flags are evaluated with the new context. + */ + override suspend fun onContextSet( + oldContext: EvaluationContext?, + newContext: EvaluationContext + ) { + // Context has changed - refresh state to get updated resolutions + Log.d(TAG, "Context updated, refreshing state...") + try { + stateFetcher.reload() + resolverState.set(stateFetcher.provide()) + accountIdRef.set(stateFetcher.accountId()) + + if (initialized && ::wasmResolveApi.isInitialized) { + wasmResolveApi.updateStateAndFlushLogs( + resolverState.get(), + accountIdRef.get() + ) + } + } catch (e: Exception) { + Log.w(TAG, "Failed to refresh state after context change", e) + } + } + + override fun getBooleanEvaluation( + key: String, + defaultValue: Boolean, + context: EvaluationContext? + ): ProviderEvaluation { + return getCastedEvaluation(key, Value.Boolean(defaultValue), context) { + (it as? Value.Boolean)?.boolean + } + } + + override fun getStringEvaluation( + key: String, + defaultValue: String, + context: EvaluationContext? + ): ProviderEvaluation { + return getCastedEvaluation(key, Value.String(defaultValue), context) { + (it as? Value.String)?.string + } + } + + override fun getIntegerEvaluation( + key: String, + defaultValue: Int, + context: EvaluationContext? + ): ProviderEvaluation { + return getCastedEvaluation(key, Value.Integer(defaultValue), context) { + (it as? Value.Integer)?.integer + } + } + + override fun getDoubleEvaluation( + key: String, + defaultValue: Double, + context: EvaluationContext? + ): ProviderEvaluation { + return getCastedEvaluation(key, Value.Double(defaultValue), context) { + (it as? Value.Double)?.double + } + } + + override fun getObjectEvaluation( + key: String, + defaultValue: Value, + context: EvaluationContext? + ): ProviderEvaluation { + if (!initialized) { + return ProviderEvaluation( + value = defaultValue, + reason = "Provider not initialized" + ) + } + + val flagPath = try { + FlagPath.parse(key) + } catch (e: IllegalArgumentException) { + Log.w(TAG, e.message ?: "Invalid flag path") + throw OpenFeatureError.GeneralError(e.message ?: "Invalid flag path") + } + + val evaluationContext = TypeMapper.evaluationContextToStruct(context) + + try { + val requestFlagName = "flags/${flagPath.flag}" + + val req = ResolveFlagsRequest.newBuilder() + .addFlags(requestFlagName) + .setApply(true) + .setClientSecret(clientSecret) + .setEvaluationContext( + Struct.newBuilder().putAllFields(evaluationContext.fieldsMap).build() + ) + .setSdk( + Sdk.newBuilder() + .setId(SdkId.SDK_ID_KOTLIN_PROVIDER) + .setVersion(Version.VERSION) + .build() + ) + .build() + + val resolveFlagResponse = wasmResolveApi + .resolveWithSticky( + ResolveWithStickyRequest.newBuilder() + .setResolveRequest(req) + .setFailFastOnSticky(false) + .build() + ) + .get() + + if (resolveFlagResponse.resolvedFlagsList.isEmpty()) { + Log.w(TAG, "No active flag '${flagPath.flag}' was found") + throw OpenFeatureError.FlagNotFoundError("No active flag '${flagPath.flag}' was found") + } + + val responseFlagName = resolveFlagResponse.getResolvedFlags(0).flag + if (requestFlagName != responseFlagName) { + val unexpectedFlag = responseFlagName.removePrefix("flags/") + Log.w(TAG, "Unexpected flag '$unexpectedFlag' from remote") + throw OpenFeatureError.FlagNotFoundError("Unexpected flag '$unexpectedFlag' from remote") + } + + val resolvedFlag = resolveFlagResponse.getResolvedFlags(0) + + return if (resolvedFlag.variant.isEmpty()) { + ProviderEvaluation( + value = defaultValue, + reason = "The server returned no assignment for the flag. Typically, this happens " + + "if no configured rules matches the given evaluation context." + ) + } else { + val fullValue = TypeMapper.fromProto(resolvedFlag.value, resolvedFlag.flagSchema) + + // If a path is given, extract expected portion from the structured value + var value = getValueForPath(flagPath.path, fullValue) + + if (value is Value.Null) { + value = defaultValue + } + + ProviderEvaluation( + value = value, + reason = resolvedFlag.reason.toString(), + variant = resolvedFlag.variant + ) + } + } catch (e: OpenFeatureError) { + throw e + } catch (e: Exception) { + Log.e(TAG, "Error resolving flag", e) + throw OpenFeatureError.GeneralError("Error resolving flag: ${e.message}") + } + } + + private fun getCastedEvaluation( + key: String, + wrappedDefaultValue: Value, + context: EvaluationContext?, + cast: (Value) -> T? + ): ProviderEvaluation { + val objectEvaluation = getObjectEvaluation(key, wrappedDefaultValue, context) + + val castedValue = cast(objectEvaluation.value) + ?: run { + Log.w(TAG, "Cannot cast value '${objectEvaluation.value}' to expected type") + throw OpenFeatureError.TypeMismatchError("Cannot cast value '${objectEvaluation.value}' to expected type") + } + + @Suppress("UNCHECKED_CAST") + return ProviderEvaluation( + value = castedValue, + variant = objectEvaluation.variant, + reason = objectEvaluation.reason + ) + } + + private fun startBackgroundTasks() { + // State refresh task + statePollJob = scope.launch { + while (isActive) { + delay(config.statePollInterval.toMillis()) + try { + stateFetcher.reload() + resolverState.set(stateFetcher.provide()) + accountIdRef.set(stateFetcher.accountId()) + + if (accountIdRef.get().isNotEmpty()) { + if (!initialized) { + wasmResolveApi = SwapWasmResolverApi( + flagLogger, + resolverState.get(), + accountIdRef.get(), + materializationStore + ) + initialized = true + Log.i(TAG, "Provider recovered and is now READY") + } else if (::wasmResolveApi.isInitialized) { + wasmResolveApi.updateStateAndFlushLogs( + resolverState.get(), + accountIdRef.get() + ) + } + } + } catch (e: Exception) { + Log.w(TAG, "Failed to refresh state", e) + } + } + } + + // Log flush task + logFlushJob = scope.launch { + while (isActive) { + delay(config.logFlushInterval.toMillis()) + try { + if (initialized && ::wasmResolveApi.isInitialized) { + wasmResolveApi.updateStateAndFlushLogs( + resolverState.get(), + accountIdRef.get() + ) + } + } catch (e: Exception) { + Log.w(TAG, "Failed to flush logs", e) + } + } + } + } + + private fun getValueForPath(path: List, value: Value): Value { + if (path.isEmpty()) { + return value + } + + var current = value + for (segment in path) { + val structure = (current as? Value.Structure)?.structure ?: return Value.Null + current = structure[segment] ?: return Value.Null + } + return current + } +} + +/** + * Represents a parsed flag path (e.g., "my-flag.nested.value"). + */ +internal data class FlagPath( + val flag: String, + val path: List +) { + companion object { + fun parse(key: String): FlagPath { + if (key.isBlank()) { + throw IllegalArgumentException("Flag key cannot be empty") + } + + val parts = key.split(".") + if (parts.isEmpty()) { + throw IllegalArgumentException("Invalid flag key: $key") + } + + return FlagPath( + flag = parts[0], + path = if (parts.size > 1) parts.subList(1, parts.size) else emptyList() + ) + } + } +} diff --git a/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/ConfidenceValue.kt b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/ConfidenceValue.kt new file mode 100644 index 00000000..192646eb --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/ConfidenceValue.kt @@ -0,0 +1,62 @@ +package com.spotify.confidence.android + +import java.util.Date + +/** + * Sealed interface representing all possible Confidence value types. + * Mirrors the existing Confidence SDK's value system for compatibility. + */ +sealed interface ConfidenceValue { + data class String(val string: kotlin.String) : ConfidenceValue { + override fun toString() = string + } + + data class Double(val double: kotlin.Double) : ConfidenceValue { + override fun toString() = double.toString() + } + + data class Boolean(val boolean: kotlin.Boolean) : ConfidenceValue { + override fun toString() = boolean.toString() + } + + data class Integer(val integer: Int) : ConfidenceValue { + override fun toString() = integer.toString() + } + + data class Struct(val map: Map) : ConfidenceValue { + override fun toString() = map.toString() + + fun getValue(key: kotlin.String): ConfidenceValue? = map[key] + } + + data class List(val list: kotlin.collections.List) : ConfidenceValue { + override fun toString() = list.toString() + } + + data class Timestamp(val dateTime: Date) : ConfidenceValue { + override fun toString() = dateTime.toString() + } + + object Null : ConfidenceValue { + override fun toString() = "null" + } + + companion object { + fun stringList(list: kotlin.collections.List) = + List(list.map(ConfidenceValue::String)) + + fun doubleList(list: kotlin.collections.List) = + List(list.map(ConfidenceValue::Double)) + + fun booleanList(list: kotlin.collections.List) = + List(list.map(ConfidenceValue::Boolean)) + + fun integerList(list: kotlin.collections.List) = + List(list.map(ConfidenceValue::Integer)) + } +} + +/** + * Type alias for context maps. + */ +typealias ConfidenceValueMap = Map diff --git a/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/Evaluation.kt b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/Evaluation.kt new file mode 100644 index 00000000..e59b059e --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/Evaluation.kt @@ -0,0 +1,64 @@ +package com.spotify.confidence.android + +/** + * Represents the result of a flag evaluation. + */ +data class Evaluation( + val value: T, + val variant: String? = null, + val reason: ResolveReason, + val errorCode: ErrorCode? = null, + val errorMessage: String? = null +) + +/** + * Enum representing the reason for a flag resolution result. + */ +enum class ResolveReason { + /** Unspecified enum. */ + RESOLVE_REASON_UNSPECIFIED, + + /** The flag was successfully resolved because one rule matched. */ + RESOLVE_REASON_MATCH, + + /** The flag value is from cache and may be stale. */ + RESOLVE_REASON_STALE, + + /** The flag could not be resolved because no rule matched. */ + RESOLVE_REASON_NO_SEGMENT_MATCH, + + /** The flag could not be resolved because the matching rule had no variant. */ + RESOLVE_REASON_NO_TREATMENT_MATCH, + + /** The flag could not be resolved because the targeting key is invalid. */ + RESOLVE_REASON_TARGETING_KEY_ERROR, + + /** The flag could not be resolved because it was archived. */ + RESOLVE_REASON_FLAG_ARCHIVED, + + /** Default fallback reason. */ + DEFAULT, + + /** An error occurred during evaluation. */ + ERROR +} + +/** + * Error codes for flag evaluation errors. + */ +enum class ErrorCode { + /** The provider is not ready yet. */ + PROVIDER_NOT_READY, + + /** The requested flag was not found. */ + FLAG_NOT_FOUND, + + /** Error parsing the flag value. */ + PARSE_ERROR, + + /** The evaluation context is invalid. */ + INVALID_CONTEXT, + + /** General error. */ + GENERAL +} diff --git a/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/GrpcWasmFlagLogger.kt b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/GrpcWasmFlagLogger.kt new file mode 100644 index 00000000..0af0479e --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/GrpcWasmFlagLogger.kt @@ -0,0 +1,138 @@ +package com.spotify.confidence.android + +import android.util.Log +import com.spotify.confidence.flags.resolver.v1.InternalFlagLoggerServiceGrpc +import com.spotify.confidence.flags.resolver.v1.WriteFlagLogsRequest +import io.grpc.ManagedChannel +import io.grpc.Metadata +import io.grpc.stub.MetadataUtils +import java.time.Duration +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit + +/** + * Flag logger that sends flag resolution events to the Confidence service via gRPC. + */ +internal class GrpcWasmFlagLogger( + private val clientSecret: String, + private val channelFactory: ChannelFactory = DefaultChannelFactory(), + private val shutdownTimeout: Duration = DEFAULT_SHUTDOWN_TIMEOUT +) : WasmFlagLogger { + + private val executorService: ExecutorService = Executors.newCachedThreadPool() + private var channel: ManagedChannel? = null + + companion object { + private const val TAG = "GrpcWasmFlagLogger" + private val DEFAULT_SHUTDOWN_TIMEOUT = Duration.ofSeconds(10) + private const val AUTH_HEADER = "authorization" + } + + private fun getOrCreateChannel(): ManagedChannel { + return channel ?: channelFactory.create().also { channel = it } + } + + private fun createStub(): InternalFlagLoggerServiceGrpc.InternalFlagLoggerServiceBlockingStub { + val metadata = Metadata() + metadata.put( + Metadata.Key.of(AUTH_HEADER, Metadata.ASCII_STRING_MARSHALLER), + "ClientSecret $clientSecret" + ) + + return InternalFlagLoggerServiceGrpc.newBlockingStub(getOrCreateChannel()) + .withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata)) + .withDeadlineAfter(30, TimeUnit.SECONDS) + } + + override fun write(logData: ByteArray) { + if (logData.isEmpty()) { + Log.d(TAG, "Skipping empty flag log") + return + } + + executorService.submit { + try { + val request = WriteFlagLogsRequest.parseFrom(logData) + Log.d(TAG, "Sending ${logData.size} bytes of log data (${request.flagAssignedCount} assignments)") + + val stub = createStub() + stub.clientWriteFlagLogs(request) + + Log.d(TAG, "Successfully sent flag logs") + } catch (e: Exception) { + Log.e(TAG, "Failed to write flag logs", e) + } + } + } + + override fun writeSync(logData: ByteArray) { + if (logData.isEmpty()) { + Log.d(TAG, "Skipping empty flag log") + return + } + + try { + val request = WriteFlagLogsRequest.parseFrom(logData) + Log.d(TAG, "Synchronously sending ${logData.size} bytes of log data") + + val stub = createStub() + stub.clientWriteFlagLogs(request) + + Log.d(TAG, "Successfully sent flag logs synchronously") + } catch (e: Exception) { + Log.e(TAG, "Failed to write flag logs synchronously", e) + } + } + + override fun shutdown() { + executorService.shutdown() + try { + if (!executorService.awaitTermination(shutdownTimeout.toMillis(), TimeUnit.MILLISECONDS)) { + Log.w(TAG, "Flag logger executor did not terminate within ${shutdownTimeout.seconds} seconds") + executorService.shutdownNow() + } else { + Log.d(TAG, "Flag logger executor terminated gracefully") + } + } catch (e: InterruptedException) { + Log.w(TAG, "Interrupted while waiting for flag logger shutdown", e) + executorService.shutdownNow() + Thread.currentThread().interrupt() + } + + channel?.let { ch -> + try { + ch.shutdown() + if (!ch.awaitTermination(5, TimeUnit.SECONDS)) { + ch.shutdownNow() + } + } catch (e: Exception) { + Log.w(TAG, "Error shutting down gRPC channel", e) + } + } + } +} + +/** + * Factory for creating gRPC channels. + */ +interface ChannelFactory { + fun create(): ManagedChannel +} + +/** + * Default channel factory that creates a channel to the Confidence edge service. + */ +class DefaultChannelFactory : ChannelFactory { + companion object { + private const val CONFIDENCE_HOST = "edge-grpc.spotify.com" + private const val CONFIDENCE_PORT = 443 + } + + override fun create(): ManagedChannel { + return io.grpc.okhttp.OkHttpChannelBuilder + .forAddress(CONFIDENCE_HOST, CONFIDENCE_PORT) + .useTransportSecurity() + .build() + } +} diff --git a/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/InitialisationStrategy.kt b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/InitialisationStrategy.kt new file mode 100644 index 00000000..869ef3c9 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/InitialisationStrategy.kt @@ -0,0 +1,24 @@ +package com.spotify.confidence.android + +/** + * Strategy for provider initialization. + * + * Determines how the provider behaves during initialization: + * - [FetchAndActivate]: Fetches fresh state from CDN and waits for completion before becoming ready. + * Use this when you want guaranteed fresh flags at startup. + * - [ActivateAndFetchAsync]: Immediately activates with cached state and fetches updates in background. + * Use this for faster startup when stale values are acceptable. + */ +sealed interface InitialisationStrategy { + /** + * Fetch fresh state from CDN and activate it before becoming ready. + * Provides guaranteed fresh flags but may increase startup latency. + */ + object FetchAndActivate : InitialisationStrategy + + /** + * Immediately activate cached state and fetch updates asynchronously. + * Faster startup but flags may be stale until fetch completes. + */ + object ActivateAndFetchAsync : InitialisationStrategy +} diff --git a/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/LocalProviderConfig.kt b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/LocalProviderConfig.kt new file mode 100644 index 00000000..e448fb03 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/LocalProviderConfig.kt @@ -0,0 +1,56 @@ +package com.spotify.confidence.android + +import okhttp3.OkHttpClient +import java.time.Duration + +/** + * Configuration for the Confidence local OpenFeature provider. + * + * @property channelFactory Factory for creating gRPC channels (useful for testing) + * @property httpClient Custom OkHttpClient for CDN requests + * @property useRemoteMaterializationStore Whether to use remote gRPC for sticky assignments + * @property statePollInterval How often to poll for state updates + * @property logFlushInterval How often to flush flag logs + * @property initialisationStrategy Strategy for provider initialization + */ +data class LocalProviderConfig( + val channelFactory: ChannelFactory = DefaultChannelFactory(), + val httpClient: OkHttpClient? = null, + val useRemoteMaterializationStore: Boolean = false, + val statePollInterval: Duration = DEFAULT_POLL_INTERVAL, + val logFlushInterval: Duration = DEFAULT_LOG_FLUSH_INTERVAL, + val initialisationStrategy: InitialisationStrategy = InitialisationStrategy.FetchAndActivate +) { + companion object { + val DEFAULT_POLL_INTERVAL: Duration = Duration.ofSeconds(30) + val DEFAULT_LOG_FLUSH_INTERVAL: Duration = Duration.ofSeconds(10) + } + + /** + * Builder for creating [LocalProviderConfig] instances. + */ + class Builder { + private var channelFactory: ChannelFactory = DefaultChannelFactory() + private var httpClient: OkHttpClient? = null + private var useRemoteMaterializationStore: Boolean = false + private var statePollInterval: Duration = DEFAULT_POLL_INTERVAL + private var logFlushInterval: Duration = DEFAULT_LOG_FLUSH_INTERVAL + private var initialisationStrategy: InitialisationStrategy = InitialisationStrategy.FetchAndActivate + + fun channelFactory(factory: ChannelFactory) = apply { channelFactory = factory } + fun httpClient(client: OkHttpClient) = apply { httpClient = client } + fun useRemoteMaterializationStore(use: Boolean) = apply { useRemoteMaterializationStore = use } + fun statePollInterval(interval: Duration) = apply { statePollInterval = interval } + fun logFlushInterval(interval: Duration) = apply { logFlushInterval = interval } + fun initialisationStrategy(strategy: InitialisationStrategy) = apply { initialisationStrategy = strategy } + + fun build(): LocalProviderConfig = LocalProviderConfig( + channelFactory = channelFactory, + httpClient = httpClient, + useRemoteMaterializationStore = useRemoteMaterializationStore, + statePollInterval = statePollInterval, + logFlushInterval = logFlushInterval, + initialisationStrategy = initialisationStrategy + ) + } +} diff --git a/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/MaterializationStore.kt b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/MaterializationStore.kt new file mode 100644 index 00000000..090b078e --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/MaterializationStore.kt @@ -0,0 +1,127 @@ +package com.spotify.confidence.android + +import java.util.Optional +import java.util.concurrent.CompletableFuture + +/** + * Storage abstraction for materialization data used in flag resolution. + * + * Materializations support two key use cases: + * - **Sticky Assignments**: Maintain consistent variant assignments across evaluations + * even when targeting attributes change. + * - **Custom Targeting via Materialized Segments**: Precomputed sets of identifiers + * from datasets that should be targeted. + * + * Default Behavior: By default, the provider uses [UnsupportedMaterializationStore] + * which triggers remote resolution via gRPC to the Confidence service. + * + * Thread Safety: Implementations must be thread-safe as they may be called + * concurrently from multiple threads resolving flags in parallel. + */ +interface MaterializationStore { + + /** + * Performs a batch read of materialization data. + * + * @param ops the list of read operations to perform + * @return a CompletableFuture that completes with the read results + * @throws MaterializationNotSupportedException if the store doesn't support reads + */ + @Throws(MaterializationNotSupportedException::class) + fun read(ops: List): CompletableFuture> + + /** + * Performs a batch write of materialization data. + * + * @param ops the set of write operations to perform + * @return a CompletableFuture that completes when all writes are finished + * @throws MaterializationNotSupportedException by default if not overridden + */ + @Throws(MaterializationNotSupportedException::class) + fun write(ops: Set): CompletableFuture { + throw MaterializationNotSupportedException() + } + + /** + * Represents a write operation to store materialization data. + */ + sealed interface WriteOp { + val materialization: String + val unit: String + + /** + * A variant assignment write operation. + */ + data class Variant( + override val materialization: String, + override val unit: String, + val rule: String, + val variant: String + ) : WriteOp + } + + /** + * Represents the result of a read operation. + */ + sealed interface ReadResult { + val materialization: String + val unit: String + + /** + * Result indicating whether a unit is included in a materialized segment. + */ + data class Inclusion( + override val materialization: String, + override val unit: String, + val included: Boolean + ) : ReadResult + + /** + * Result containing the variant assignment for a unit and rule. + */ + data class Variant( + override val materialization: String, + override val unit: String, + val rule: String, + val variant: Optional + ) : ReadResult + } + + /** + * Represents a read operation to query materialization data. + */ + sealed interface ReadOp { + val materialization: String + val unit: String + + /** + * Query operation to check if a unit is included in a materialized segment. + */ + data class Inclusion( + override val materialization: String, + override val unit: String + ) : ReadOp { + fun toResult(included: Boolean): ReadResult.Inclusion { + return ReadResult.Inclusion(materialization, unit, included) + } + } + + /** + * Query operation to retrieve the variant assignment for a unit and rule. + */ + data class Variant( + override val materialization: String, + override val unit: String, + val rule: String + ) : ReadOp { + fun toResult(variant: Optional): ReadResult.Variant { + return ReadResult.Variant(materialization, unit, rule, variant) + } + } + } +} + +/** + * Exception thrown when materialization operations are not supported. + */ +class MaterializationNotSupportedException : Exception("Materialization not supported") diff --git a/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/ResolverApi.kt b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/ResolverApi.kt new file mode 100644 index 00000000..5e565648 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/ResolverApi.kt @@ -0,0 +1,35 @@ +package com.spotify.confidence.android + +import com.spotify.confidence.flags.resolver.v1.ResolveFlagsResponse +import com.spotify.confidence.flags.resolver.v1.ResolveWithStickyRequest +import java.util.concurrent.CompletableFuture + +/** + * Interface for the resolver API that handles flag resolution. + */ +internal interface ResolverApi { + /** + * Initializes the resolver with the given state and account ID. + */ + fun init(state: ByteArray, accountId: String) + + /** + * Returns whether the resolver has been initialized. + */ + fun isInitialized(): Boolean + + /** + * Updates the resolver state and flushes any pending logs. + */ + fun updateStateAndFlushLogs(state: ByteArray, accountId: String) + + /** + * Resolves flags with sticky assignment support. + */ + fun resolveWithSticky(request: ResolveWithStickyRequest): CompletableFuture + + /** + * Closes the resolver, flushing any pending logs. + */ + fun close() +} diff --git a/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/StateFetcher.kt b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/StateFetcher.kt new file mode 100644 index 00000000..8af3dd40 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/StateFetcher.kt @@ -0,0 +1,101 @@ +package com.spotify.confidence.android + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient +import okhttp3.Request +import rust_guest.SetResolverStateRequest +import java.nio.charset.StandardCharsets +import java.security.MessageDigest +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicReference + +/** + * Fetches and caches resolver state from the Confidence CDN. + * + * This class handles: + * - Fetching state from the CDN using SHA256(clientSecret) as the path + * - ETag-based conditional GETs to minimize bandwidth + * - Parsing SetResolverStateRequest protobuf from the response + */ +internal class StateFetcher( + private val clientSecret: String, + private val httpClient: OkHttpClient = OkHttpClient.Builder() + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .build() +) { + private val etagHolder = AtomicReference(null) + private val rawResolverStateHolder = AtomicReference(ByteArray(0)) + private var accountId: String = "" + + companion object { + private const val CDN_BASE_URL = "https://confidence-resolver-state-cdn.spotifycdn.com/" + } + + /** + * Returns the current cached resolver state. + */ + fun provide(): ByteArray = rawResolverStateHolder.get() + + /** + * Returns the current account ID. + */ + fun accountId(): String = accountId + + /** + * Reloads the resolver state from the CDN. + * Uses ETag for conditional GET to avoid re-downloading unchanged state. + */ + suspend fun reload() { + withContext(Dispatchers.IO) { + try { + fetchAndUpdateStateIfChanged() + } catch (e: Exception) { + android.util.Log.w("StateFetcher", "Failed to reload, ignoring reload", e) + } + } + } + + private fun fetchAndUpdateStateIfChanged() { + val cdnUrl = CDN_BASE_URL + sha256Hex(clientSecret) + + val requestBuilder = Request.Builder().url(cdnUrl) + etagHolder.get()?.let { previousEtag -> + requestBuilder.header("If-None-Match", previousEtag) + } + + val response = httpClient.newCall(requestBuilder.build()).execute() + response.use { resp -> + if (resp.code == 304) { + // Not modified + return + } + + if (!resp.isSuccessful) { + throw RuntimeException("Failed to fetch state: HTTP ${resp.code}") + } + + val etag = resp.header("ETag") + val bytes = resp.body?.bytes() ?: throw RuntimeException("Empty response body") + + // Parse SetResolverStateRequest from CDN response + val stateRequest = SetResolverStateRequest.parseFrom(bytes) + this.accountId = stateRequest.accountId + + // Store the state bytes + rawResolverStateHolder.set(stateRequest.state.toByteArray()) + etagHolder.set(etag) + + android.util.Log.i("StateFetcher", "Loaded resolver state for account=$accountId, etag=$etag") + } + } + + private fun sha256Hex(input: String): String { + val digest = MessageDigest.getInstance("SHA-256") + val hash = digest.digest(input.toByteArray(StandardCharsets.UTF_8)) + return hash.joinToString("") { byte -> + String.format("%02x", byte) + } + } +} diff --git a/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/SwapWasmResolverApi.kt b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/SwapWasmResolverApi.kt new file mode 100644 index 00000000..3114ce33 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/SwapWasmResolverApi.kt @@ -0,0 +1,106 @@ +package com.spotify.confidence.android + +import com.spotify.confidence.flags.resolver.v1.ResolveFlagsResponse +import com.spotify.confidence.flags.resolver.v1.ResolveWithStickyRequest +import com.spotify.confidence.flags.resolver.v1.ResolveWithStickyResponse +import java.util.concurrent.CompletableFuture +import java.util.concurrent.atomic.AtomicReference + +/** + * Swap-based resolver API that allows hot-swapping WASM resolver instances. + * + * This implementation: + * - Creates new WASM instances with updated state + * - Atomically swaps out old instances + * - Handles retries for closed instances + */ +internal class SwapWasmResolverApi( + private val flagLogger: WasmFlagLogger, + initialState: ByteArray, + accountId: String, + private val materializationStore: MaterializationStore +) : ResolverApi { + + private val wasmResolverApiRef = AtomicReference() + + companion object { + private const val MAX_CLOSED_RETRIES = 10 + } + + private fun createWasmResolver(): WasmResolver { + return WasmResolver { logData -> flagLogger.write(logData) } + } + + init { + // Create initial instance + val initialInstance = createWasmResolver() + initialInstance.setResolverState(initialState, accountId) + wasmResolverApiRef.set(initialInstance) + } + + override fun init(state: ByteArray, accountId: String) { + updateStateAndFlushLogs(state, accountId) + } + + override fun isInitialized(): Boolean = true + + override fun updateStateAndFlushLogs(state: ByteArray, accountId: String) { + // Create new instance with updated state + val newInstance = createWasmResolver() + newInstance.setResolverState(state, accountId) + + // Get current instance before switching + val oldInstance = wasmResolverApiRef.getAndSet(newInstance) + oldInstance?.close() + } + + override fun close() { + val currentInstance = wasmResolverApiRef.getAndSet(null) + currentInstance?.close() + } + + override fun resolveWithSticky(request: ResolveWithStickyRequest): CompletableFuture { + return resolveWithStickyInternal(request, 0) + } + + private fun resolveWithStickyInternal( + request: ResolveWithStickyRequest, + closedRetries: Int + ): CompletableFuture { + val instance = wasmResolverApiRef.get() + ?: return CompletableFuture.failedFuture(RuntimeException("Resolver is closed")) + + val response = try { + instance.resolveWithSticky(request) + } catch (e: IsClosedException) { + if (closedRetries >= MAX_CLOSED_RETRIES) { + return CompletableFuture.failedFuture( + RuntimeException("Max retries exceeded for IsClosedException: $MAX_CLOSED_RETRIES", e) + ) + } + return resolveWithStickyInternal(request, closedRetries + 1) + } + + return when (response.resolveResultCase) { + ResolveWithStickyResponse.ResolveResultCase.SUCCESS -> { + val success = response.success + // For now, we skip materialization updates since the proto types aren't available + // TODO: Add materialization support when proto types are generated + CompletableFuture.completedFuture(success.response) + } + + ResolveWithStickyResponse.ResolveResultCase.MISSING_MATERIALIZATIONS -> { + // For now, return an error since we can't handle materializations + // TODO: Add materialization support when proto types are generated + android.util.Log.w("SwapWasmResolverApi", "Missing materializations - sticky assignments not supported yet") + CompletableFuture.failedFuture(RuntimeException("Materialization support not implemented")) + } + + ResolveWithStickyResponse.ResolveResultCase.RESOLVERESULT_NOT_SET -> + CompletableFuture.failedFuture(RuntimeException("Invalid response: resolve result not set")) + + else -> + CompletableFuture.failedFuture(RuntimeException("Unhandled response case: ${response.resolveResultCase}")) + } + } +} diff --git a/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/TypeMapper.kt b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/TypeMapper.kt new file mode 100644 index 00000000..32f83c93 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/TypeMapper.kt @@ -0,0 +1,168 @@ +@file:OptIn(kotlin.time.ExperimentalTime::class) + +package com.spotify.confidence.android + +import com.google.protobuf.ListValue +import com.google.protobuf.NullValue +import com.google.protobuf.Struct +import com.spotify.confidence.flags.types.v1.FlagSchema +import dev.openfeature.kotlin.sdk.EvaluationContext +import dev.openfeature.kotlin.sdk.Value + +/** + * Utility object for converting between OpenFeature types and Protobuf types. + */ +internal object TypeMapper { + + /** + * Converts a protobuf Value with its schema to an OpenFeature Value. + */ + fun fromProto(value: com.google.protobuf.Value, schema: FlagSchema): Value { + if (schema.schemaTypeCase == FlagSchema.SchemaTypeCase.SCHEMATYPE_NOT_SET) { + throw IllegalArgumentException("schemaType not set in FlagSchema") + } + + val mismatchPrefix = "Mismatch between schema and value:" + + return when (value.kindCase) { + com.google.protobuf.Value.KindCase.NULL_VALUE -> Value.Null + + com.google.protobuf.Value.KindCase.NUMBER_VALUE -> { + when (schema.schemaTypeCase) { + FlagSchema.SchemaTypeCase.INT_SCHEMA -> { + val intVal = value.numberValue.toInt() + if (intVal.toDouble() != value.numberValue) { + throw IllegalArgumentException("$mismatchPrefix value should be an int, but it is a double/long") + } + Value.Integer(intVal) + } + FlagSchema.SchemaTypeCase.DOUBLE_SCHEMA -> Value.Double(value.numberValue) + else -> throw IllegalArgumentException("Number field must have schema type int or double") + } + } + + com.google.protobuf.Value.KindCase.STRING_VALUE -> { + if (schema.schemaTypeCase != FlagSchema.SchemaTypeCase.STRING_SCHEMA) { + throw IllegalArgumentException("$mismatchPrefix value is a String, but it should be something else") + } + Value.String(value.stringValue) + } + + com.google.protobuf.Value.KindCase.BOOL_VALUE -> { + if (schema.schemaTypeCase != FlagSchema.SchemaTypeCase.BOOL_SCHEMA) { + throw IllegalArgumentException("$mismatchPrefix value is a bool, but should be something else") + } + Value.Boolean(value.boolValue) + } + + com.google.protobuf.Value.KindCase.STRUCT_VALUE -> { + if (schema.schemaTypeCase != FlagSchema.SchemaTypeCase.STRUCT_SCHEMA) { + throw IllegalArgumentException("$mismatchPrefix value is a struct, but should be something else") + } + fromProto(value.structValue, schema.structSchema) + } + + com.google.protobuf.Value.KindCase.LIST_VALUE -> { + if (schema.schemaTypeCase != FlagSchema.SchemaTypeCase.LIST_SCHEMA) { + throw IllegalArgumentException("$mismatchPrefix value is a list, but should be something else") + } + val mappedList = value.listValue.valuesList.map { v -> + fromProto(v, schema.listSchema.elementSchema) + } + Value.List(mappedList) + } + + com.google.protobuf.Value.KindCase.KIND_NOT_SET -> + throw IllegalArgumentException("kind not set in com.google.protobuf.Value") + + else -> throw IllegalArgumentException("Unknown value type") + } + } + + /** + * Converts a protobuf Struct with its schema to an OpenFeature Value. + */ + fun fromProto(struct: Struct, schema: FlagSchema.StructFlagSchema): Value { + val map = struct.fieldsMap.entries.associate { (key, value) -> + if (!schema.schemaMap.containsKey(key)) { + throw IllegalArgumentException("Lacking schema for field '$key'") + } + key to fromProto(value, schema.schemaMap[key]!!) + } + return Value.Structure(map) + } + + /** + * Converts an OpenFeature Value to a protobuf Value. + */ + fun toProto(value: Value): com.google.protobuf.Value { + return when (value) { + is Value.Null -> com.google.protobuf.Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build() + + is Value.Boolean -> com.google.protobuf.Value.newBuilder() + .setBoolValue(value.boolean) + .build() + + is Value.Integer -> com.google.protobuf.Value.newBuilder() + .setNumberValue(value.integer.toDouble()) + .build() + + is Value.Double -> com.google.protobuf.Value.newBuilder() + .setNumberValue(value.double) + .build() + + is Value.String -> com.google.protobuf.Value.newBuilder() + .setStringValue(value.string) + .build() + + is Value.List -> com.google.protobuf.Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues(value.list.map { toProto(it) }) + .build() + ) + .build() + + is Value.Structure -> { + val protoMap = value.structure.entries.associate { (key, v) -> + key to toProto(v) + } + com.google.protobuf.Value.newBuilder() + .setStructValue(Struct.newBuilder().putAllFields(protoMap).build()) + .build() + } + + is Value.Instant -> com.google.protobuf.Value.newBuilder() + .setStringValue(value.instant.toString()) + .build() + } + } + + /** + * Converts an OpenFeature EvaluationContext to a protobuf Struct. + */ + fun evaluationContextToStruct(context: EvaluationContext?): Struct { + if (context == null) { + return Struct.getDefaultInstance() + } + + val builder = Struct.newBuilder() + + // Add targeting key + builder.putFields( + "targeting_key", + com.google.protobuf.Value.newBuilder() + .setStringValue(context.getTargetingKey()) + .build() + ) + + // Add all context attributes + context.asMap().forEach { (key, value) -> + builder.putFields(key, toProto(value)) + } + + return builder.build() + } +} diff --git a/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/UnsupportedMaterializationStore.kt b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/UnsupportedMaterializationStore.kt new file mode 100644 index 00000000..f64d2df8 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/UnsupportedMaterializationStore.kt @@ -0,0 +1,18 @@ +package com.spotify.confidence.android + +import java.util.concurrent.CompletableFuture + +/** + * Default materialization store that throws [MaterializationNotSupportedException] + * for all operations, triggering fallback to remote gRPC resolution. + */ +internal class UnsupportedMaterializationStore : MaterializationStore { + + override fun read(ops: List): CompletableFuture> { + throw MaterializationNotSupportedException() + } + + override fun write(ops: Set): CompletableFuture { + throw MaterializationNotSupportedException() + } +} diff --git a/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/ValueConversions.kt b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/ValueConversions.kt new file mode 100644 index 00000000..8cf6a6de --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/ValueConversions.kt @@ -0,0 +1,98 @@ +@file:OptIn(kotlin.time.ExperimentalTime::class) + +package com.spotify.confidence.android + +import dev.openfeature.kotlin.sdk.EvaluationContext +import dev.openfeature.kotlin.sdk.Value +import java.util.Date + +/** + * Extension functions for converting between OpenFeature Value and ConfidenceValue types. + */ + +/** + * Converts an OpenFeature Value to a ConfidenceValue. + */ +fun Value.toConfidenceValue(): ConfidenceValue = when (this) { + is Value.Null -> ConfidenceValue.Null + is Value.Boolean -> ConfidenceValue.Boolean(this.boolean) + is Value.Integer -> ConfidenceValue.Integer(this.integer) + is Value.Double -> ConfidenceValue.Double(this.double) + is Value.String -> ConfidenceValue.String(this.string) + is Value.List -> ConfidenceValue.List(this.list.map { it.toConfidenceValue() }) + is Value.Structure -> ConfidenceValue.Struct(this.structure.mapValues { it.value.toConfidenceValue() }) + is Value.Instant -> ConfidenceValue.Timestamp(Date(this.instant.epochSeconds * 1000)) +} + +/** + * Converts a ConfidenceValue to an OpenFeature Value. + */ +fun ConfidenceValue.toValue(): Value = when (this) { + is ConfidenceValue.Boolean -> Value.Boolean(this.boolean) + is ConfidenceValue.Double -> Value.Double(this.double) + is ConfidenceValue.Integer -> Value.Integer(this.integer) + is ConfidenceValue.List -> Value.List(this.list.map { it.toValue() }) + ConfidenceValue.Null -> Value.Null + is ConfidenceValue.String -> Value.String(this.string) + is ConfidenceValue.Struct -> Value.Structure(this.map.mapValues { it.value.toValue() }) + is ConfidenceValue.Timestamp -> Value.Instant( + kotlin.time.Instant.fromEpochMilliseconds(this.dateTime.time) + ) +} + +/** + * Converts an EvaluationContext to a ConfidenceValue.Struct. + */ +fun EvaluationContext.toConfidenceContext(): ConfidenceValue.Struct { + val map = mutableMapOf() + + // Add targeting key + map["targeting_key"] = ConfidenceValue.String(getTargetingKey()) + + // Add all attributes + asMap().forEach { (key, value) -> + map[key] = value.toConfidenceValue() + } + + return ConfidenceValue.Struct(map) +} + +/** + * Extracts a value at the given path from a ConfidenceValue.Struct. + */ +fun findValueFromPath(value: ConfidenceValue.Struct, path: List): ConfidenceValue? { + if (path.isEmpty()) return value + + val currValue = value.map[path[0]] ?: return null + + return when { + currValue is ConfidenceValue.Struct && path.size > 1 -> { + findValueFromPath(currValue, path.subList(1, path.size)) + } + path.size == 1 -> currValue + else -> null + } +} + +/** + * Converts a ResolveReason to an OpenFeature reason string. + */ +fun ResolveReason.toOpenFeatureReason(): String = when (this) { + ResolveReason.RESOLVE_REASON_MATCH -> "TARGETING_MATCH" + ResolveReason.RESOLVE_REASON_STALE -> "STALE" + ResolveReason.ERROR -> "ERROR" + ResolveReason.RESOLVE_REASON_TARGETING_KEY_ERROR -> "ERROR" + ResolveReason.RESOLVE_REASON_UNSPECIFIED -> "UNKNOWN" + else -> "DEFAULT" +} + +/** + * Converts an ErrorCode to an OpenFeature error code. + */ +fun ErrorCode.toOpenFeatureErrorCode(): dev.openfeature.kotlin.sdk.exceptions.ErrorCode = when (this) { + ErrorCode.FLAG_NOT_FOUND -> dev.openfeature.kotlin.sdk.exceptions.ErrorCode.FLAG_NOT_FOUND + ErrorCode.INVALID_CONTEXT -> dev.openfeature.kotlin.sdk.exceptions.ErrorCode.INVALID_CONTEXT + ErrorCode.PARSE_ERROR -> dev.openfeature.kotlin.sdk.exceptions.ErrorCode.PARSE_ERROR + ErrorCode.PROVIDER_NOT_READY -> dev.openfeature.kotlin.sdk.exceptions.ErrorCode.PROVIDER_NOT_READY + ErrorCode.GENERAL -> dev.openfeature.kotlin.sdk.exceptions.ErrorCode.GENERAL +} diff --git a/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/Version.kt b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/Version.kt new file mode 100644 index 00000000..20ed2743 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/Version.kt @@ -0,0 +1,8 @@ +package com.spotify.confidence.android + +/** + * SDK version information for the Confidence Android OpenFeature provider. + */ +internal object Version { + const val VERSION = "0.1.0" +} diff --git a/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/WasmFlagLogger.kt b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/WasmFlagLogger.kt new file mode 100644 index 00000000..4415614c --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/WasmFlagLogger.kt @@ -0,0 +1,21 @@ +package com.spotify.confidence.android + +/** + * Interface for logging flag resolution events to the Confidence service. + */ +internal interface WasmFlagLogger { + /** + * Asynchronously writes flag logs (raw bytes from WASM). + */ + fun write(logData: ByteArray) + + /** + * Synchronously writes flag logs, blocking until complete. + */ + fun writeSync(logData: ByteArray) + + /** + * Shuts down the logger, waiting for pending writes to complete. + */ + fun shutdown() +} diff --git a/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/WasmResolver.kt b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/WasmResolver.kt new file mode 100644 index 00000000..725aad38 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/kotlin/com/spotify/confidence/android/WasmResolver.kt @@ -0,0 +1,243 @@ +package com.spotify.confidence.android + +import com.dylibso.chicory.runtime.ExportFunction +import com.dylibso.chicory.runtime.ImportFunction +import com.dylibso.chicory.runtime.ImportValues +import com.dylibso.chicory.runtime.Instance +import com.dylibso.chicory.runtime.Memory +import com.dylibso.chicory.wasm.types.ValType +import com.spotify.confidence.wasm.ConfidenceResolver +import com.google.protobuf.ByteString +import com.google.protobuf.InvalidProtocolBufferException +import com.google.protobuf.MessageLite +import com.google.protobuf.Timestamp +import com.spotify.confidence.flags.resolver.v1.ResolveWithStickyRequest +import com.spotify.confidence.flags.resolver.v1.ResolveWithStickyResponse +import rust_guest.LogMessage +import rust_guest.Request +import rust_guest.Response +import rust_guest.SetResolverStateRequest +import rust_guest.Void as WasmVoid +import java.time.Instant +import java.util.concurrent.locks.ReentrantReadWriteLock + +/** + * WASM resolver that interfaces with the Confidence resolver WASM binary using Chicory runtime. + * + * This class handles all WASM memory management and provides methods for: + * - Setting resolver state + * - Resolving flags with sticky assignments + * - Flushing logs + */ +internal class WasmResolver( + private val onFlushLogs: (ByteArray) -> Unit +) { + private val hostParamTypes = listOf(ValType.I32) + private val hostReturnTypes = listOf(ValType.I32) + private val instance: Instance + private val wasmLock = ReentrantReadWriteLock() + + @Volatile + private var isConsumed = false + + // WASM memory interop functions + private val wasmMsgAlloc: ExportFunction + private val wasmMsgFree: ExportFunction + + // WASM API functions + private val wasmMsgGuestSetResolverState: ExportFunction + private val wasmMsgFlushLogs: ExportFunction + private val wasmMsgGuestResolveWithSticky: ExportFunction + + init { + // Use pre-compiled WASM module for better performance on Android + // The module is compiled at build time using Chicory's build-time compiler + val module = ConfidenceResolver.load() + instance = Instance.builder(module) + .withImportValues( + ImportValues.builder() + .addFunction(createImportFunction("current_time", WasmVoid::parseFrom, ::currentTime)) + .addFunction(createImportFunction("log_message", LogMessage::parseFrom, ::log)) + .addFunction( + ImportFunction( + "wasm_msg", + "wasm_msg_current_thread_id", + emptyList(), + listOf(ValType.I32) + ) { _, _ -> longArrayOf(0) } + ) + .build() + ) + // Use pre-compiled machine factory for fast execution + .withMachineFactory(ConfidenceResolver::create) + .build() + + wasmMsgAlloc = instance.export("wasm_msg_alloc") + wasmMsgFree = instance.export("wasm_msg_free") + wasmMsgGuestSetResolverState = instance.export("wasm_msg_guest_set_resolver_state") + wasmMsgFlushLogs = instance.export("wasm_msg_guest_flush_logs") + wasmMsgGuestResolveWithSticky = instance.export("wasm_msg_guest_resolve_with_sticky") + } + + private fun log(message: LogMessage): MessageLite { + android.util.Log.d("WasmResolver", message.message) + return WasmVoid.getDefaultInstance() + } + + private fun currentTime(@Suppress("UNUSED_PARAMETER") unused: WasmVoid): Timestamp { + return Timestamp.newBuilder() + .setSeconds(Instant.now().epochSecond) + .build() + } + + /** + * Sets the resolver state from CDN. + */ + fun setResolverState(state: ByteArray, accountId: String) { + val resolverStateRequest = SetResolverStateRequest.newBuilder() + .setState(ByteString.copyFrom(state)) + .setAccountId(accountId) + .build() + + val request = Request.newBuilder() + .setData(ByteString.copyFrom(resolverStateRequest.toByteArray())) + .build() + .toByteArray() + + val addr = transfer(request) + val respPtr = wasmMsgGuestSetResolverState.apply(addr.toLong())[0].toInt() + consumeResponse(respPtr) { WasmVoid.parseFrom(it) } + } + + /** + * Resolves flags with sticky assignment support. + */ + @Throws(IsClosedException::class) + fun resolveWithSticky(request: ResolveWithStickyRequest): ResolveWithStickyResponse { + if (!wasmLock.writeLock().tryLock() || isConsumed) { + throw IsClosedException() + } + try { + val reqPtr = transferRequest(request) + val respPtr = wasmMsgGuestResolveWithSticky.apply(reqPtr.toLong())[0].toInt() + return consumeResponse(respPtr) { ResolveWithStickyResponse.parseFrom(it) } + } finally { + wasmLock.writeLock().unlock() + } + } + + /** + * Flushes pending logs and closes the resolver. + */ + fun close() { + wasmLock.readLock().lock() + try { + val voidRequest = WasmVoid.getDefaultInstance() + val reqPtr = transferRequest(voidRequest) + val respPtr = wasmMsgFlushLogs.apply(reqPtr.toLong())[0].toInt() + val responseBytes = consumeResponseBytes(respPtr) + onFlushLogs(responseBytes) + isConsumed = true + } finally { + wasmLock.readLock().unlock() + } + } + + private fun consumeResponse(addr: Int, codec: (ByteArray) -> T): T { + try { + val response = Response.parseFrom(consume(addr)) + if (response.hasError()) { + throw RuntimeException(response.error) + } + return codec(response.data.toByteArray()) + } catch (e: InvalidProtocolBufferException) { + throw RuntimeException(e) + } + } + + private fun consumeResponseBytes(addr: Int): ByteArray { + try { + val response = Response.parseFrom(consume(addr)) + if (response.hasError()) { + throw RuntimeException(response.error) + } + return response.data.toByteArray() + } catch (e: InvalidProtocolBufferException) { + throw RuntimeException(e) + } + } + + private fun transferRequest(message: MessageLite): Int { + val request = Request.newBuilder() + .setData(ByteString.copyFrom(message.toByteArray())) + .build() + .toByteArray() + return transfer(request) + } + + private fun transferResponseSuccess(response: MessageLite): Int { + val wrapperBytes = Response.newBuilder() + .setData(ByteString.copyFrom(response.toByteArray())) + .build() + .toByteArray() + return transfer(wrapperBytes) + } + + private fun transferResponseError(error: String): Int { + val wrapperBytes = Response.newBuilder() + .setError(error) + .build() + .toByteArray() + return transfer(wrapperBytes) + } + + private fun consume(addr: Int): ByteArray { + val mem: Memory = instance.memory() + val len = (mem.readU32(addr - 4) - 4L).toInt() + val data = mem.readBytes(addr, len) + wasmMsgFree.apply(addr.toLong()) + return data + } + + private fun transfer(data: ByteArray): Int { + val mem: Memory = instance.memory() + val addr = wasmMsgAlloc.apply(data.size.toLong())[0].toInt() + mem.write(addr, data) + return addr + } + + private fun consumeRequest(addr: Int, codec: (ByteArray) -> T): T { + try { + val request = Request.parseFrom(consume(addr)) + return codec(request.data.toByteArray()) + } catch (e: InvalidProtocolBufferException) { + throw RuntimeException(e) + } + } + + private fun createImportFunction( + name: String, + reqCodec: (ByteArray) -> T, + impl: (T) -> MessageLite + ): ImportFunction { + return ImportFunction( + "wasm_msg", + "wasm_msg_host_$name", + hostParamTypes, + hostReturnTypes + ) { _, args -> + try { + val message = consumeRequest(args[0].toInt(), reqCodec) + val response = impl(message) + longArrayOf(transferResponseSuccess(response).toLong()) + } catch (e: Exception) { + longArrayOf(transferResponseError(e.message ?: "Unknown error").toLong()) + } + } + } +} + +/** + * Exception thrown when the resolver is closed and cannot process more requests. + */ +class IsClosedException : Exception("Resolver is closed") diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/confidence/api/annotations.proto b/openfeature-provider/android/confidence-provider/src/main/proto/confidence/api/annotations.proto new file mode 100644 index 00000000..173c9751 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/confidence/api/annotations.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +package confidence.api; + +import "google/protobuf/descriptor.proto"; + +option java_multiple_files = true; +option java_outer_classname = "ApiAnnotationsProto"; +option java_package = "com.spotify.confidence.api"; + +message Resource { + string type = 1; +} + +// Rate limit specification that is applied on global rates per account +message RateLimitSpec { + int32 rps_limit = 1; +} + +extend google.protobuf.MethodOptions { + Resource resource_method = 4399226; + RateLimitSpec rate_limit = 4399227; +} + +extend google.protobuf.ServiceOptions { + string service_name = 4399229; + repeated string hosts = 4399228; + RateLimitSpec service_rate_limit = 4399230; +} + +message ValidationSpec { + string regex = 1; +} + +extend google.protobuf.FieldOptions { + ValidationSpec validation = 4324223; +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/confidence/flags/resolver/v1/api.proto b/openfeature-provider/android/confidence-provider/src/main/proto/confidence/flags/resolver/v1/api.proto new file mode 100644 index 00000000..d272f0ec --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/confidence/flags/resolver/v1/api.proto @@ -0,0 +1,183 @@ +syntax = "proto3"; + +package confidence.flags.resolver.v1; + +import "google/api/resource.proto"; +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/timestamp.proto"; + +import "confidence/api/annotations.proto"; +import "confidence/flags/types/v1/types.proto"; +import "confidence/flags/resolver/v1/types.proto"; + +option java_package = "com.spotify.confidence.flags.resolver.v1"; +option java_multiple_files = true; +option java_outer_classname = "ApiProto"; + +// The service that allows a client to resolve a flag into a variant and its +// corresponding value. +service FlagResolverService { + option (confidence.api.service_name) = "Resolver"; + option (confidence.api.hosts) = "resolver.eu.confidence.dev"; + option (confidence.api.hosts) = "resolver.us.confidence.dev"; + + // Resolve multiple flags into variants and values. This method resolves + // all flags that are enabled for the given client, or a subset of them + // specified in the request. + // A flag is resolved by evaluating its rules in order, a rule matches if: + // 1) it is enabled, 2) the referred segment is active, and 3) the + // randomization unit is in the population indicated by the segment's + // targeting criteria and population allocation. The first rule that matches + // will assign a variant and value to the unit. Archived flags are not included. + rpc ResolveFlags(ResolveFlagsRequest) returns (ResolveFlagsResponse) { + option (google.api.http) = { + post: "/v1/flags:resolve" + body: "*" + }; + option (confidence.api.resource_method).type = "flags.confidence.dev/Flag"; + } + + // Indicates that resolved values of a set of flags have been used. In many + // situations there is a delay between the moment a flag is resolved and + // when it is actually used in a client. This is often the case in mobile + // clients where you typically batch resolve all flags at startup, but then + // apply them later when the user interacts with a specific view. If the + // `apply` flag is set to false in a resolve, the flag assignment event is + // delayed until the flag is applied. + rpc ApplyFlags(ApplyFlagsRequest) returns (ApplyFlagsResponse) { + option (google.api.http) = { + post: "/v1/flags:apply" + body: "*" + }; + option (confidence.api.resource_method).type = "flags.confidence.dev/Flag"; + } +} + +message ResolveFlagsRequest { + // If non-empty, the specific flags are resolved, otherwise all flags + // available to the client will be resolved. + repeated string flags = 1 [ + (google.api.resource_reference).type = "flags.confidence.dev/Flag", + (google.api.field_behavior) = OPTIONAL + ]; + + // An object that contains data used in the flag resolve. For example, + // the targeting key e.g. the id of the randomization unit, other attributes + // like country or version that are used for targeting. + google.protobuf.Struct evaluation_context = 2 [ + (google.api.field_behavior) = OPTIONAL + ]; + + // Credentials for the client. It is used to identify the client and find + // the flags that are available to it. + string client_secret = 3 [ + (google.api.field_behavior) = REQUIRED + ]; + + // Determines whether the flags should be applied directly as part of the + // resolve, or delayed until `ApplyFlag` is called. A flag is typically + // applied when it is used, if this occurs much later than the resolve, then + // `apply` should likely be set to false. + bool apply = 4 [ + (google.api.field_behavior) = REQUIRED + ]; + + // Information about the SDK used to initiate the request. + Sdk sdk = 5 [ + (google.api.field_behavior) = OPTIONAL + ]; +} + +message ResolveFlagsResponse { + // The list of all flags that could be resolved. Note: if any flag was + // archived it will not be included in this list. + repeated ResolvedFlag resolved_flags = 1; + + // An opaque token that is used when `apply` is set to false in `ResolveFlags`. + // When `apply` is set to false, the token must be passed to `ApplyFlags`. + bytes resolve_token = 2; + + // Unique identifier for this particular resolve request. + string resolve_id = 3; +} + +message ApplyFlagsRequest { + // The flags to apply and information about when they were applied. + repeated AppliedFlag flags = 1 [ + (google.api.field_behavior) = REQUIRED + ]; + + // Credentials for the client. + string client_secret = 2 [ + (google.api.field_behavior) = REQUIRED + ]; + + // An opaque token that was returned from `ResolveFlags`; it must be set. + bytes resolve_token = 3 [ + (google.api.field_behavior) = REQUIRED + ]; + + + // The client time when the this request was sent, used for correcting + // clock skew from the client. + google.protobuf.Timestamp send_time = 4 [ + (google.api.field_behavior) = REQUIRED + ]; + + // Information about the SDK used to initiate the request. + Sdk sdk = 5 [ + (google.api.field_behavior) = OPTIONAL + ]; +} + +message ApplyFlagsResponse { + +} + +message AppliedFlag { + // The id of the flag that should be applied, has the format `flags/*`. + string flag = 1 [ + (google.api.resource_reference).type = "flags.confidence.dev/Flag", + (google.api.field_behavior) = REQUIRED + ]; + + // The client time when the flag was applied. + google.protobuf.Timestamp apply_time = 2 [ + (google.api.field_behavior) = REQUIRED + ]; +} + +message ResolvedFlag { + // The id of the flag that as resolved. + string flag = 1 [ + (google.api.resource_reference).type = "flags.confidence.dev/Flag" + ]; + + // The id of the resolved variant has the format `flags/abc/variants/xyz`. + string variant = 2 [ + (google.api.resource_reference).type = "flags.confidence.dev/Variant" + ]; + + // The value corresponding to the variant. It will always be a json object, + // for example `{ "color": "red", "size": 12 }`. + google.protobuf.Struct value = 3; + + // The schema of the value that was returned. For example: + // ``` + // { + // "schema": { + // "color": { "stringSchema": {} }, + // "size": { "intSchema": {} } + // } + // } + // ``` + types.v1.FlagSchema.StructFlagSchema flag_schema = 4; + + // The reason to why the flag could be resolved or not. + ResolveReason reason = 5; + + // Determines whether the flag should be applied in the clients + bool should_apply = 6 [(google.api.field_behavior) = OUTPUT_ONLY]; +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/confidence/flags/resolver/v1/internal_api.proto b/openfeature-provider/android/confidence-provider/src/main/proto/confidence/flags/resolver/v1/internal_api.proto new file mode 100644 index 00000000..758c0542 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/confidence/flags/resolver/v1/internal_api.proto @@ -0,0 +1,189 @@ +syntax = "proto3"; + +// IMPORTANT: Package name must match backend expectation for gRPC service discovery +// The full gRPC service name is: confidence.flags.resolver.v1.InternalFlagLoggerService +package confidence.flags.resolver.v1; + +import "google/protobuf/timestamp.proto"; +import "confidence/flags/resolver/v1/types.proto"; + +option go_package = "github.com/spotify/confidence-resolver/openfeature-provider/go/confidence/internal/proto/resolverinternal"; +option java_package = "com.spotify.confidence.flags.resolver.v1"; +option java_multiple_files = true; +option java_outer_classname = "InternalApiProto"; + +// Simplified from confidence-resolver/protos/confidence/flags/resolver/v1/internal_api.proto +// Annotations removed for minimal dependencies + +service InternalFlagLoggerService { + // Client writes flag assignment events and resolve logs using client secret authentication. + rpc ClientWriteFlagLogs(WriteFlagLogsRequest) returns (WriteFlagLogsResponse); + + // Stores materializations for units. Uses client secret authentication. + rpc WriteMaterializedOperations(WriteOperationsRequest) returns (WriteOperationsResult) {} + + // Loads materializations for units. Uses client secret authentication. + rpc ReadMaterializedOperations(ReadOperationsRequest) returns (ReadOperationsResult) {} +} + +// The service that allows to report flag assigned and other client-side flag +// operations, useful when the resolve engine runs on the customer's premises +message WriteFlagLogsRequest { + repeated FlagAssigned flag_assigned = 1; + + TelemetryData telemetry_data = 2; + + repeated ClientResolveInfo client_resolve_info = 3; + repeated FlagResolveInfo flag_resolve_info = 4; +} + +message WriteFlagLogsResponse {} + +// Collection of telemetry metrics +message TelemetryData { + // Information about the SDK/provider + Sdk sdk = 2; +} + +message ClientInfo { + string client = 1; + string client_credential = 2; + + // Information about the SDK used to interact with the API. + Sdk sdk = 3; +} + +message FlagAssigned { + string resolve_id = 10; + + ClientInfo client_info = 3; + + repeated AppliedFlag flags = 15; + + message AppliedFlag { + string flag = 1; + + string targeting_key = 2; + string targeting_key_selector = 3; + + oneof assignment { + AssignmentInfo assignment_info = 4; + DefaultAssignment default_assignment = 5; + } + + string assignment_id = 6; + + string rule = 7; + + repeated FallthroughAssignment fallthrough_assignments = 8; + google.protobuf.Timestamp apply_time = 9; + } + + message AssignmentInfo { + string segment = 1; + string variant = 2; + } + + message DefaultAssignment { + DefaultAssignmentReason reason = 1; + enum DefaultAssignmentReason { + DEFAULT_ASSIGNMENT_REASON_UNSPECIFIED = 0; + NO_SEGMENT_MATCH = 1; + NO_TREATMENT_MATCH = 2 [deprecated = true]; + FLAG_ARCHIVED = 3; + } + } +} + +message FallthroughAssignment { + string rule = 1; + + string assignment_id = 2; + + string targeting_key = 3; + string targeting_key_selector = 4; +} + +message ClientResolveInfo { + // Resource reference to a client. + string client = 1; + + // Resource reference to a credential. + string client_credential = 2; + + // The different evaluation context schema of the client that have been seen recently. + repeated EvaluationContextSchemaInstance schema = 3; + + // An instance of a schema that was seen + message EvaluationContextSchemaInstance { + // Schema of each field in the evaluation context. + map schema = 1; + } +} + +message FlagResolveInfo { + // The flag the info is about + string flag = 1; + // Information about how variants were resolved. + repeated VariantResolveInfo variant_resolve_info = 2; + + // Information about how a variant was resolved. + message VariantResolveInfo { + // If there was a variant assigned, otherwise not set + string variant = 1; + // Number of times the variant was resolved in this period + int64 count = 3; + } +} + +message WriteOperationsRequest { + repeated VariantData store_variant_op = 1; +} + +message WriteOperationsResult {} + +message VariantReadOp { + string unit = 1; + string materialization = 2; + string rule = 3; +} + +message InclusionReadOp { + string unit = 1; + string materialization = 2; +} + +message ReadOp { + oneof op { + VariantReadOp variant_read_op = 1; + InclusionReadOp inclusion_read_op = 2; + } +} + +message ReadOperationsRequest { + repeated ReadOp ops = 3; +} + +message VariantData { + string unit = 1; + string materialization = 2; + string rule = 3; + string variant = 4; +} + +message InclusionData { + string unit = 1; + string materialization = 2; + bool is_included = 3; +} + +message ReadResult { + oneof result { + VariantData variant_result = 1; + InclusionData inclusion_result = 2; + } +} + +message ReadOperationsResult { + repeated ReadResult results = 1; +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/confidence/flags/resolver/v1/types.proto b/openfeature-provider/android/confidence-provider/src/main/proto/confidence/flags/resolver/v1/types.proto new file mode 100644 index 00000000..8fc25258 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/confidence/flags/resolver/v1/types.proto @@ -0,0 +1,70 @@ +syntax = "proto3"; + +package confidence.flags.resolver.v1; + +import "google/api/field_behavior.proto"; + +option java_package = "com.spotify.confidence.flags.resolver.v1"; +option java_multiple_files = true; +option java_outer_classname = "TypesProto"; + +// (-- api-linter: core::0123::resource-annotation=disabled +// aip.dev/not-precedent: SDKs are not internal Confidence resources. --) +message Sdk { + // Identifier of the SDK used to interact with the API. + oneof sdk { + // Name of a Confidence SDKs. + SdkId id = 1; + // Custom name for non-Confidence SDKs. + string custom_id = 2; + } + + // Version of the SDK. + string version = 3 [ + (google.api.field_behavior) = OPTIONAL // TODO: Make REQUIRED again when we're not SDK if default + ]; +} + +enum ResolveReason { + // Unspecified enum. + RESOLVE_REASON_UNSPECIFIED = 0; + // The flag was successfully resolved because one rule matched. + RESOLVE_REASON_MATCH = 1; + // The flag could not be resolved because no rule matched. + RESOLVE_REASON_NO_SEGMENT_MATCH = 2; + // The flag could not be resolved because the matching rule had no variant + // that could be assigned. + RESOLVE_REASON_NO_TREATMENT_MATCH = 3 [deprecated = true]; + // The flag could not be resolved because it was archived. + RESOLVE_REASON_FLAG_ARCHIVED = 4; + // The flag could not be resolved because the targeting key field was invalid + RESOLVE_REASON_TARGETING_KEY_ERROR = 5; + // Unknown error occurred during the resolve + RESOLVE_REASON_ERROR = 6; +} + +enum SdkId { + SDK_ID_UNSPECIFIED = 0; + SDK_ID_JAVA_PROVIDER = 1; + SDK_ID_KOTLIN_PROVIDER = 2; + SDK_ID_SWIFT_PROVIDER = 3; + SDK_ID_JS_WEB_PROVIDER = 4; + SDK_ID_JS_SERVER_PROVIDER = 5; + SDK_ID_PYTHON_PROVIDER = 6; + SDK_ID_GO_PROVIDER = 7; + SDK_ID_RUBY_PROVIDER = 8; + SDK_ID_RUST_PROVIDER = 9; + SDK_ID_JAVA_CONFIDENCE = 10; + SDK_ID_KOTLIN_CONFIDENCE = 11; + SDK_ID_SWIFT_CONFIDENCE = 12; + SDK_ID_JS_CONFIDENCE = 13; + SDK_ID_PYTHON_CONFIDENCE = 14; + SDK_ID_GO_CONFIDENCE = 15; + SDK_ID_RUST_CONFIDENCE = 16; + SDK_ID_FLUTTER_IOS_CONFIDENCE = 17; + SDK_ID_FLUTTER_ANDROID_CONFIDENCE = 18; + SDK_ID_DOTNET_CONFIDENCE = 19; + SDK_ID_GO_LOCAL_PROVIDER = 20; + SDK_ID_JAVA_LOCAL_PROVIDER = 21; + SDK_ID_JS_LOCAL_SERVER_PROVIDER = 22; +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/confidence/flags/resolver/v1/wasm_api.proto b/openfeature-provider/android/confidence-provider/src/main/proto/confidence/flags/resolver/v1/wasm_api.proto new file mode 100644 index 00000000..63ecf6bb --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/confidence/flags/resolver/v1/wasm_api.proto @@ -0,0 +1,75 @@ +syntax = "proto3"; + +package confidence.flags.resolver.v1; + +import "google/api/resource.proto"; +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/timestamp.proto"; + +import "confidence/api/annotations.proto"; +import "confidence/flags/types/v1/types.proto"; +import "confidence/flags/resolver/v1/types.proto"; +import "confidence/flags/resolver/v1/api.proto"; + +option java_package = "com.spotify.confidence.flags.resolver.v1"; +option java_multiple_files = true; +option java_outer_classname = "WasmApiProto"; + + +message ResolveWithStickyRequest { + ResolveFlagsRequest resolve_request = 1; + + // Context about the materialization required for the resolve + map materializations_per_unit = 2; + + // if a materialization info is missing, we want tor return to the caller immediately + bool fail_fast_on_sticky = 3; + // if we should support sticky or completely skip the flag if they had sticky rules + bool not_process_sticky = 4; +} + +message MaterializationMap { + // materialization name to info + map info_map = 1; +} + +message MaterializationInfo { + bool unit_in_info = 1; + map rule_to_variant = 2; +} + +message LogMessage { + string message = 1; +} + +message ResolveWithStickyResponse { + oneof resolve_result { + Success success = 1; + MissingMaterializations missing_materializations = 2; + } + + message Success { + ResolveFlagsResponse response = 1; + repeated MaterializationUpdate updates = 2; + } + + message MissingMaterializations { + repeated MissingMaterializationItem items = 1; + } + + message MissingMaterializationItem { + string unit = 1; + string rule = 2; + string read_materialization = 3; + } + + message MaterializationUpdate { + string unit = 1; + string write_materialization = 2; + string rule = 3; + string variant = 4; + } +} + diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/confidence/flags/types/v1/types.proto b/openfeature-provider/android/confidence-provider/src/main/proto/confidence/flags/types/v1/types.proto new file mode 100644 index 00000000..88d67e10 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/confidence/flags/types/v1/types.proto @@ -0,0 +1,73 @@ +syntax = "proto3"; + +package confidence.flags.types.v1; + +option java_package = "com.spotify.confidence.flags.types.v1"; +option java_multiple_files = true; +option java_outer_classname = "TypesProto"; + +// Schema for the value of a flag. +// +// The value of a flag is always a struct with one or more nested fields. +// Example of a struct schema with two fields, `color` (a string) and `len` (an int): +// +// ``` +// { +// "schema": { +// "color": { +// "stringSchema": {} +// }, +// "len": { +// "intSchema": {} +// } +// } +// } +// ``` +message FlagSchema { + oneof schema_type { + // Schema if this is a struct + StructFlagSchema struct_schema = 1; + // Schema if this is a list + ListFlagSchema list_schema = 2; + // Schema if this is an int + IntFlagSchema int_schema = 3; + // Schema if this is a double + DoubleFlagSchema double_schema = 4; + //Schema if this is a string + StringFlagSchema string_schema = 5; + //Schema if this is a bool + BoolFlagSchema bool_schema = 6; + } + + // A schema of nested fields. The length of the field name is limited to + // 32 characters and can only contain alphanumeric characters, hyphens and + // underscores. The number of fields in a struct is limited to 64. + // Structs can not be nested more than four (4) levels. + message StructFlagSchema { + // Map of field name to the schema for the field + map schema = 1; + } + + // A number that has a decimal place. + message DoubleFlagSchema { + } + + // A whole number without a decimal point. + message IntFlagSchema { + } + + // A string. The length is limited to 250 characters. + message StringFlagSchema { + } + + // A boolean: true or false. + message BoolFlagSchema { + } + + // A list of values. The values have the same data types which + // is defined by `element_schema`. + message ListFlagSchema { + // The schema for the elements in the list + FlagSchema element_schema = 1; + } +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/google/api/annotations.proto b/openfeature-provider/android/confidence-provider/src/main/proto/google/api/annotations.proto new file mode 100644 index 00000000..efdab3db --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/google/api/client.proto b/openfeature-provider/android/confidence-provider/src/main/proto/google/api/client.proto new file mode 100644 index 00000000..3b3fd0c4 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/google/api/client.proto @@ -0,0 +1,99 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "ClientProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // A definition of a client library method signature. + // + // In client libraries, each proto RPC corresponds to one or more methods + // which the end user is able to call, and calls the underlying RPC. + // Normally, this method receives a single argument (a struct or instance + // corresponding to the RPC request object). Defining this field will + // add one or more overloads providing flattened or simpler method signatures + // in some languages. + // + // The fields on the method signature are provided as a comma-separated + // string. + // + // For example, the proto RPC and annotation: + // + // rpc CreateSubscription(CreateSubscriptionRequest) + // returns (Subscription) { + // option (google.api.method_signature) = "name,topic"; + // } + // + // Would add the following Java overload (in addition to the method accepting + // the request object): + // + // public final Subscription createSubscription(String name, String topic) + // + // The following backwards-compatibility guidelines apply: + // + // * Adding this annotation to an unannotated method is backwards + // compatible. + // * Adding this annotation to a method which already has existing + // method signature annotations is backwards compatible if and only if + // the new method signature annotation is last in the sequence. + // * Modifying or removing an existing method signature annotation is + // a breaking change. + // * Re-ordering existing method signature annotations is a breaking + // change. + repeated string method_signature = 1051; +} + +extend google.protobuf.ServiceOptions { + // The hostname for this service. + // This should be specified with no prefix or protocol. + // + // Example: + // + // service Foo { + // option (google.api.default_host) = "foo.googleapi.com"; + // ... + // } + string default_host = 1049; + + // OAuth scopes needed for the client. + // + // Example: + // + // service Foo { + // option (google.api.oauth_scopes) = \ + // "https://www.googleapis.com/auth/cloud-platform"; + // ... + // } + // + // If there is more than one scope, use a comma-separated string: + // + // Example: + // + // service Foo { + // option (google.api.oauth_scopes) = \ + // "https://www.googleapis.com/auth/cloud-platform," + // "https://www.googleapis.com/auth/monitoring"; + // ... + // } + string oauth_scopes = 1050; +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/google/api/field_behavior.proto b/openfeature-provider/android/confidence-provider/src/main/proto/google/api/field_behavior.proto new file mode 100644 index 00000000..344cb0b1 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/google/api/http.proto b/openfeature-provider/android/confidence-provider/src/main/proto/google/api/http.proto new file mode 100644 index 00000000..113fa936 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/google/api/http.proto @@ -0,0 +1,375 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/google/api/httpbody.proto b/openfeature-provider/android/confidence-provider/src/main/proto/google/api/httpbody.proto new file mode 100644 index 00000000..028fa5db --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/google/api/httpbody.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package google.api; + +import "google/protobuf/any.proto"; + +option java_package = "com.google.api"; +option java_multiple_files = true; +option java_outer_classname = "HttpBodyProto"; + +// Minimal HttpBody to satisfy imports during local builds +message HttpBody { + string content_type = 1; + bytes data = 2; + repeated google.protobuf.Any extensions = 3; +} + + diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/google/api/resource.proto b/openfeature-provider/android/confidence-provider/src/main/proto/google/api/resource.proto new file mode 100644 index 00000000..0ce0344f --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/google/api/resource.proto @@ -0,0 +1,238 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "ResourceProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // An annotation that describes a resource reference, see + // [ResourceReference][]. + google.api.ResourceReference resource_reference = 1055; +} + +extend google.protobuf.FileOptions { + // An annotation that describes a resource definition without a corresponding + // message; see [ResourceDescriptor][]. + repeated google.api.ResourceDescriptor resource_definition = 1053; +} + +extend google.protobuf.MessageOptions { + // An annotation that describes a resource definition, see + // [ResourceDescriptor][]. + google.api.ResourceDescriptor resource = 1053; +} + +// A simple descriptor of a resource type. +// +// ResourceDescriptor annotates a resource message (either by means of a +// protobuf annotation or use in the service config), and associates the +// resource's schema, the resource type, and the pattern of the resource name. +// +// Example: +// +// message Topic { +// // Indicates this message defines a resource schema. +// // Declares the resource type in the format of {service}/{kind}. +// // For Kubernetes resources, the format is {api group}/{kind}. +// option (google.api.resource) = { +// type: "pubsub.googleapis.com/Topic" +// pattern: "projects/{project}/topics/{topic}" +// }; +// } +// +// The ResourceDescriptor Yaml config will look like: +// +// resources: +// - type: "pubsub.googleapis.com/Topic" +// pattern: "projects/{project}/topics/{topic}" +// +// Sometimes, resources have multiple patterns, typically because they can +// live under multiple parents. +// +// Example: +// +// message LogEntry { +// option (google.api.resource) = { +// type: "logging.googleapis.com/LogEntry" +// pattern: "projects/{project}/logs/{log}" +// pattern: "folders/{folder}/logs/{log}" +// pattern: "organizations/{organization}/logs/{log}" +// pattern: "billingAccounts/{billing_account}/logs/{log}" +// }; +// } +// +// The ResourceDescriptor Yaml config will look like: +// +// resources: +// - type: 'logging.googleapis.com/LogEntry' +// pattern: "projects/{project}/logs/{log}" +// pattern: "folders/{folder}/logs/{log}" +// pattern: "organizations/{organization}/logs/{log}" +// pattern: "billingAccounts/{billing_account}/logs/{log}" +message ResourceDescriptor { + // A description of the historical or future-looking state of the + // resource pattern. + enum History { + // The "unset" value. + HISTORY_UNSPECIFIED = 0; + + // The resource originally had one pattern and launched as such, and + // additional patterns were added later. + ORIGINALLY_SINGLE_PATTERN = 1; + + // The resource has one pattern, but the API owner expects to add more + // later. (This is the inverse of ORIGINALLY_SINGLE_PATTERN, and prevents + // that from being necessary once there are multiple patterns.) + FUTURE_MULTI_PATTERN = 2; + } + + // A flag representing a specific style that a resource claims to conform to. + enum Style { + // The unspecified value. Do not use. + STYLE_UNSPECIFIED = 0; + + // This resource is intended to be "declarative-friendly". + // + // Declarative-friendly resources must be more strictly consistent, and + // setting this to true communicates to tools that this resource should + // adhere to declarative-friendly expectations. + // + // Note: This is used by the API linter (linter.aip.dev) to enable + // additional checks. + DECLARATIVE_FRIENDLY = 1; + } + + // The resource type. It must be in the format of + // {service_name}/{resource_type_kind}. The `resource_type_kind` must be + // singular and must not include version numbers. + // + // Example: `storage.googleapis.com/Bucket` + // + // The value of the resource_type_kind must follow the regular expression + // /[A-Za-z][a-zA-Z0-9]+/. It should start with an upper case character and + // should use PascalCase (UpperCamelCase). The maximum number of + // characters allowed for the `resource_type_kind` is 100. + string type = 1; + + // Optional. The relative resource name pattern associated with this resource + // type. The DNS prefix of the full resource name shouldn't be specified here. + // + // The path pattern must follow the syntax, which aligns with HTTP binding + // syntax: + // + // Template = Segment { "/" Segment } ; + // Segment = LITERAL | Variable ; + // Variable = "{" LITERAL "}" ; + // + // Examples: + // + // - "projects/{project}/topics/{topic}" + // - "projects/{project}/knowledgeBases/{knowledge_base}" + // + // The components in braces correspond to the IDs for each resource in the + // hierarchy. It is expected that, if multiple patterns are provided, + // the same component name (e.g. "project") refers to IDs of the same + // type of resource. + repeated string pattern = 2; + + // Optional. The field on the resource that designates the resource name + // field. If omitted, this is assumed to be "name". + string name_field = 3; + + // Optional. The historical or future-looking state of the resource pattern. + // + // Example: + // + // // The InspectTemplate message originally only supported resource + // // names with organization, and project was added later. + // message InspectTemplate { + // option (google.api.resource) = { + // type: "dlp.googleapis.com/InspectTemplate" + // pattern: + // "organizations/{organization}/inspectTemplates/{inspect_template}" + // pattern: "projects/{project}/inspectTemplates/{inspect_template}" + // history: ORIGINALLY_SINGLE_PATTERN + // }; + // } + History history = 4; + + // The plural name used in the resource name and permission names, such as + // 'projects' for the resource name of 'projects/{project}' and the permission + // name of 'cloudresourcemanager.googleapis.com/projects.get'. It is the same + // concept of the `plural` field in k8s CRD spec + // https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/ + // + // Note: The plural form is required even for singleton resources. See + // https://aip.dev/156 + string plural = 5; + + // The same concept of the `singular` field in k8s CRD spec + // https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/ + // Such as "project" for the `resourcemanager.googleapis.com/Project` type. + string singular = 6; + + // Style flag(s) for this resource. + // These indicate that a resource is expected to conform to a given + // style. See the specific style flags for additional information. + repeated Style style = 10; +} + +// Defines a proto annotation that describes a string field that refers to +// an API resource. +message ResourceReference { + // The resource type that the annotated field references. + // + // Example: + // + // message Subscription { + // string topic = 2 [(google.api.resource_reference) = { + // type: "pubsub.googleapis.com/Topic" + // }]; + // } + // + // Occasionally, a field may reference an arbitrary resource. In this case, + // APIs use the special value * in their resource reference. + // + // Example: + // + // message GetIamPolicyRequest { + // string resource = 2 [(google.api.resource_reference) = { + // type: "*" + // }]; + // } + string type = 1; + + // The resource type of a child collection that the annotated field + // references. This is useful for annotating the `parent` field that + // doesn't have a fixed resource type. + // + // Example: + // + // message ListLogEntriesRequest { + // string parent = 1 [(google.api.resource_reference) = { + // child_type: "logging.googleapis.com/LogEntry" + // }; + // } + string child_type = 2; +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/google/api/visibility.proto b/openfeature-provider/android/confidence-provider/src/main/proto/google/api/visibility.proto new file mode 100644 index 00000000..0be4581d --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/google/api/visibility.proto @@ -0,0 +1,113 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/visibility;visibility"; +option java_multiple_files = true; +option java_outer_classname = "VisibilityProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.EnumOptions { + // See `VisibilityRule`. + google.api.VisibilityRule enum_visibility = 72295727; +} + +extend google.protobuf.EnumValueOptions { + // See `VisibilityRule`. + google.api.VisibilityRule value_visibility = 72295727; +} + +extend google.protobuf.FieldOptions { + // See `VisibilityRule`. + google.api.VisibilityRule field_visibility = 72295727; +} + +extend google.protobuf.MessageOptions { + // See `VisibilityRule`. + google.api.VisibilityRule message_visibility = 72295727; +} + +extend google.protobuf.MethodOptions { + // See `VisibilityRule`. + google.api.VisibilityRule method_visibility = 72295727; +} + +extend google.protobuf.ServiceOptions { + // See `VisibilityRule`. + google.api.VisibilityRule api_visibility = 72295727; +} + +// `Visibility` restricts service consumer's access to service elements, +// such as whether an application can call a visibility-restricted method. +// The restriction is expressed by applying visibility labels on service +// elements. The visibility labels are elsewhere linked to service consumers. +// +// A service can define multiple visibility labels, but a service consumer +// should be granted at most one visibility label. Multiple visibility +// labels for a single service consumer are not supported. +// +// If an element and all its parents have no visibility label, its visibility +// is unconditionally granted. +// +// Example: +// +// visibility: +// rules: +// - selector: google.calendar.Calendar.EnhancedSearch +// restriction: PREVIEW +// - selector: google.calendar.Calendar.Delegate +// restriction: INTERNAL +// +// Here, all methods are publicly visible except for the restricted methods +// EnhancedSearch and Delegate. +message Visibility { + // A list of visibility rules that apply to individual API elements. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated VisibilityRule rules = 1; +} + +// A visibility rule provides visibility configuration for an individual API +// element. +message VisibilityRule { + // Selects methods, messages, fields, enums, etc. to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // A comma-separated list of visibility labels that apply to the `selector`. + // Any of the listed labels can be used to grant the visibility. + // + // If a rule has multiple labels, removing one of the labels but not all of + // them can break clients. + // + // Example: + // + // visibility: + // rules: + // - selector: google.calendar.Calendar.EnhancedSearch + // restriction: INTERNAL, PREVIEW + // + // Removing INTERNAL from this restriction will break clients that rely on + // this method and only had access to it through INTERNAL. + string restriction = 2; +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/any.proto b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/any.proto new file mode 100644 index 00000000..eff44e50 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/any.proto @@ -0,0 +1,162 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option go_package = "google.golang.org/protobuf/types/known/anypb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "AnyProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// `Any` contains an arbitrary serialized protocol buffer message along with a +// URL that describes the type of the serialized message. +// +// Protobuf library provides support to pack/unpack Any values in the form +// of utility functions or additional generated methods of the Any type. +// +// Example 1: Pack and unpack a message in C++. +// +// Foo foo = ...; +// Any any; +// any.PackFrom(foo); +// ... +// if (any.UnpackTo(&foo)) { +// ... +// } +// +// Example 2: Pack and unpack a message in Java. +// +// Foo foo = ...; +// Any any = Any.pack(foo); +// ... +// if (any.is(Foo.class)) { +// foo = any.unpack(Foo.class); +// } +// // or ... +// if (any.isSameTypeAs(Foo.getDefaultInstance())) { +// foo = any.unpack(Foo.getDefaultInstance()); +// } +// +// Example 3: Pack and unpack a message in Python. +// +// foo = Foo(...) +// any = Any() +// any.Pack(foo) +// ... +// if any.Is(Foo.DESCRIPTOR): +// any.Unpack(foo) +// ... +// +// Example 4: Pack and unpack a message in Go +// +// foo := &pb.Foo{...} +// any, err := anypb.New(foo) +// if err != nil { +// ... +// } +// ... +// foo := &pb.Foo{} +// if err := any.UnmarshalTo(foo); err != nil { +// ... +// } +// +// The pack methods provided by protobuf library will by default use +// 'type.googleapis.com/full.type.name' as the type URL and the unpack +// methods only use the fully qualified type name after the last '/' +// in the type URL, for example "foo.bar.com/x/y.z" will yield type +// name "y.z". +// +// JSON +// ==== +// The JSON representation of an `Any` value uses the regular +// representation of the deserialized, embedded message, with an +// additional field `@type` which contains the type URL. Example: +// +// package google.profile; +// message Person { +// string first_name = 1; +// string last_name = 2; +// } +// +// { +// "@type": "type.googleapis.com/google.profile.Person", +// "firstName": , +// "lastName": +// } +// +// If the embedded message type is well-known and has a custom JSON +// representation, that representation will be embedded adding a field +// `value` which holds the custom JSON in addition to the `@type` +// field. Example (for message [google.protobuf.Duration][]): +// +// { +// "@type": "type.googleapis.com/google.protobuf.Duration", +// "value": "1.212s" +// } +// +message Any { + // A URL/resource name that uniquely identifies the type of the serialized + // protocol buffer message. This string must contain at least + // one "/" character. The last segment of the URL's path must represent + // the fully qualified name of the type (as in + // `path/google.protobuf.Duration`). The name should be in a canonical form + // (e.g., leading "." is not accepted). + // + // In practice, teams usually precompile into the binary all types that they + // expect it to use in the context of Any. However, for URLs which use the + // scheme `http`, `https`, or no scheme, one can optionally set up a type + // server that maps type URLs to message definitions as follows: + // + // * If no scheme is provided, `https` is assumed. + // * An HTTP GET on the URL must yield a [google.protobuf.Type][] + // value in binary format, or produce an error. + // * Applications are allowed to cache lookup results based on the + // URL, or have them precompiled into a binary to avoid any + // lookup. Therefore, binary compatibility needs to be preserved + // on changes to types. (Use versioned type names to manage + // breaking changes.) + // + // Note: this functionality is not currently available in the official + // protobuf release, and it is not used for type URLs beginning with + // type.googleapis.com. As of May 2023, there are no widely used type server + // implementations and no plans to implement one. + // + // Schemes other than `http`, `https` (or the empty scheme) might be + // used with implementation specific semantics. + // + string type_url = 1; + + // Must be a valid serialized protocol buffer of the above specified type. + bytes value = 2; +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/api.proto b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/api.proto new file mode 100644 index 00000000..42223516 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/api.proto @@ -0,0 +1,207 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +import "google/protobuf/source_context.proto"; +import "google/protobuf/type.proto"; + +option java_package = "com.google.protobuf"; +option java_outer_classname = "ApiProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option go_package = "google.golang.org/protobuf/types/known/apipb"; + +// Api is a light-weight descriptor for an API Interface. +// +// Interfaces are also described as "protocol buffer services" in some contexts, +// such as by the "service" keyword in a .proto file, but they are different +// from API Services, which represent a concrete implementation of an interface +// as opposed to simply a description of methods and bindings. They are also +// sometimes simply referred to as "APIs" in other contexts, such as the name of +// this message itself. See https://cloud.google.com/apis/design/glossary for +// detailed terminology. +message Api { + // The fully qualified name of this interface, including package name + // followed by the interface's simple name. + string name = 1; + + // The methods of this interface, in unspecified order. + repeated Method methods = 2; + + // Any metadata attached to the interface. + repeated Option options = 3; + + // A version string for this interface. If specified, must have the form + // `major-version.minor-version`, as in `1.10`. If the minor version is + // omitted, it defaults to zero. If the entire version field is empty, the + // major version is derived from the package name, as outlined below. If the + // field is not empty, the version in the package name will be verified to be + // consistent with what is provided here. + // + // The versioning schema uses [semantic + // versioning](http://semver.org) where the major version number + // indicates a breaking change and the minor version an additive, + // non-breaking change. Both version numbers are signals to users + // what to expect from different versions, and should be carefully + // chosen based on the product plan. + // + // The major version is also reflected in the package name of the + // interface, which must end in `v`, as in + // `google.feature.v1`. For major versions 0 and 1, the suffix can + // be omitted. Zero major versions must only be used for + // experimental, non-GA interfaces. + // + string version = 4; + + // Source context for the protocol buffer service represented by this + // message. + SourceContext source_context = 5; + + // Included interfaces. See [Mixin][]. + repeated Mixin mixins = 6; + + // The source syntax of the service. + Syntax syntax = 7; +} + +// Method represents a method of an API interface. +message Method { + // The simple name of this method. + string name = 1; + + // A URL of the input message type. + string request_type_url = 2; + + // If true, the request is streamed. + bool request_streaming = 3; + + // The URL of the output message type. + string response_type_url = 4; + + // If true, the response is streamed. + bool response_streaming = 5; + + // Any metadata attached to the method. + repeated Option options = 6; + + // The source syntax of this method. + Syntax syntax = 7; +} + +// Declares an API Interface to be included in this interface. The including +// interface must redeclare all the methods from the included interface, but +// documentation and options are inherited as follows: +// +// - If after comment and whitespace stripping, the documentation +// string of the redeclared method is empty, it will be inherited +// from the original method. +// +// - Each annotation belonging to the service config (http, +// visibility) which is not set in the redeclared method will be +// inherited. +// +// - If an http annotation is inherited, the path pattern will be +// modified as follows. Any version prefix will be replaced by the +// version of the including interface plus the [root][] path if +// specified. +// +// Example of a simple mixin: +// +// package google.acl.v1; +// service AccessControl { +// // Get the underlying ACL object. +// rpc GetAcl(GetAclRequest) returns (Acl) { +// option (google.api.http).get = "/v1/{resource=**}:getAcl"; +// } +// } +// +// package google.storage.v2; +// service Storage { +// rpc GetAcl(GetAclRequest) returns (Acl); +// +// // Get a data record. +// rpc GetData(GetDataRequest) returns (Data) { +// option (google.api.http).get = "/v2/{resource=**}"; +// } +// } +// +// Example of a mixin configuration: +// +// apis: +// - name: google.storage.v2.Storage +// mixins: +// - name: google.acl.v1.AccessControl +// +// The mixin construct implies that all methods in `AccessControl` are +// also declared with same name and request/response types in +// `Storage`. A documentation generator or annotation processor will +// see the effective `Storage.GetAcl` method after inherting +// documentation and annotations as follows: +// +// service Storage { +// // Get the underlying ACL object. +// rpc GetAcl(GetAclRequest) returns (Acl) { +// option (google.api.http).get = "/v2/{resource=**}:getAcl"; +// } +// ... +// } +// +// Note how the version in the path pattern changed from `v1` to `v2`. +// +// If the `root` field in the mixin is specified, it should be a +// relative path under which inherited HTTP paths are placed. Example: +// +// apis: +// - name: google.storage.v2.Storage +// mixins: +// - name: google.acl.v1.AccessControl +// root: acls +// +// This implies the following inherited HTTP annotation: +// +// service Storage { +// // Get the underlying ACL object. +// rpc GetAcl(GetAclRequest) returns (Acl) { +// option (google.api.http).get = "/v2/acls/{resource=**}:getAcl"; +// } +// ... +// } +message Mixin { + // The fully qualified name of the interface which is included. + string name = 1; + + // If non-empty specifies a path under which inherited HTTP paths + // are rooted. + string root = 2; +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/descriptor.proto b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/descriptor.proto new file mode 100644 index 00000000..47486435 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/descriptor.proto @@ -0,0 +1,1218 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: kenton@google.com (Kenton Varda) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. +// +// The messages in this file describe the definitions found in .proto files. +// A valid .proto file can be translated directly to a FileDescriptorProto +// without any other information (e.g. without reading its imports). + +syntax = "proto2"; + +package google.protobuf; + +option go_package = "google.golang.org/protobuf/types/descriptorpb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "DescriptorProtos"; +option csharp_namespace = "Google.Protobuf.Reflection"; +option objc_class_prefix = "GPB"; +option cc_enable_arenas = true; + +// descriptor.proto must be optimized for speed because reflection-based +// algorithms don't work during bootstrapping. +option optimize_for = SPEED; + +// The protocol compiler can output a FileDescriptorSet containing the .proto +// files it parses. +message FileDescriptorSet { + repeated FileDescriptorProto file = 1; +} + +// The full set of known editions. +enum Edition { + // A placeholder for an unknown edition value. + EDITION_UNKNOWN = 0; + + // Legacy syntax "editions". These pre-date editions, but behave much like + // distinct editions. These can't be used to specify the edition of proto + // files, but feature definitions must supply proto2/proto3 defaults for + // backwards compatibility. + EDITION_PROTO2 = 998; + EDITION_PROTO3 = 999; + + // Editions that have been released. The specific values are arbitrary and + // should not be depended on, but they will always be time-ordered for easy + // comparison. + EDITION_2023 = 1000; + + // Placeholder editions for testing feature resolution. These should not be + // used or relyed on outside of tests. + EDITION_1_TEST_ONLY = 1; + EDITION_2_TEST_ONLY = 2; + EDITION_99997_TEST_ONLY = 99997; + EDITION_99998_TEST_ONLY = 99998; + EDITION_99999_TEST_ONLY = 99999; +} + +// Describes a complete .proto file. +message FileDescriptorProto { + optional string name = 1; // file name, relative to root of source tree + optional string package = 2; // e.g. "foo", "foo.bar", etc. + + // Names of files imported by this file. + repeated string dependency = 3; + // Indexes of the public imported files in the dependency list above. + repeated int32 public_dependency = 10; + // Indexes of the weak imported files in the dependency list. + // For Google-internal migration only. Do not use. + repeated int32 weak_dependency = 11; + + // All top-level definitions in this file. + repeated DescriptorProto message_type = 4; + repeated EnumDescriptorProto enum_type = 5; + repeated ServiceDescriptorProto service = 6; + repeated FieldDescriptorProto extension = 7; + + optional FileOptions options = 8; + + // This field contains optional information about the original source code. + // You may safely remove this entire field without harming runtime + // functionality of the descriptors -- the information is needed only by + // development tools. + optional SourceCodeInfo source_code_info = 9; + + // The syntax of the proto file. + // The supported values are "proto2", "proto3", and "editions". + // + // If `edition` is present, this value must be "editions". + optional string syntax = 12; + + // The edition of the proto file. + optional Edition edition = 14; +} + +// Describes a message type. +message DescriptorProto { + optional string name = 1; + + repeated FieldDescriptorProto field = 2; + repeated FieldDescriptorProto extension = 6; + + repeated DescriptorProto nested_type = 3; + repeated EnumDescriptorProto enum_type = 4; + + message ExtensionRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. + + optional ExtensionRangeOptions options = 3; + } + repeated ExtensionRange extension_range = 5; + + repeated OneofDescriptorProto oneof_decl = 8; + + optional MessageOptions options = 7; + + // Range of reserved tag numbers. Reserved tag numbers may not be used by + // fields or extension ranges in the same message. Reserved ranges may + // not overlap. + message ReservedRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. + } + repeated ReservedRange reserved_range = 9; + // Reserved field names, which may not be used by fields in the same message. + // A given name may only be reserved once. + repeated string reserved_name = 10; +} + +message ExtensionRangeOptions { + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + message Declaration { + // The extension number declared within the extension range. + optional int32 number = 1; + + // The fully-qualified name of the extension field. There must be a leading + // dot in front of the full name. + optional string full_name = 2; + + // The fully-qualified type name of the extension field. Unlike + // Metadata.type, Declaration.type must have a leading dot for messages + // and enums. + optional string type = 3; + + // If true, indicates that the number is reserved in the extension range, + // and any extension field with the number will fail to compile. Set this + // when a declared extension field is deleted. + optional bool reserved = 5; + + // If true, indicates that the extension must be defined as repeated. + // Otherwise the extension must be defined as optional. + optional bool repeated = 6; + + reserved 4; // removed is_repeated + } + + // For external users: DO NOT USE. We are in the process of open sourcing + // extension declaration and executing internal cleanups before it can be + // used externally. + repeated Declaration declaration = 2 [retention = RETENTION_SOURCE]; + + // Any features defined in the specific edition. + optional FeatureSet features = 50; + + // The verification state of the extension range. + enum VerificationState { + // All the extensions of the range must be declared. + DECLARATION = 0; + UNVERIFIED = 1; + } + + // The verification state of the range. + // TODO: flip the default to DECLARATION once all empty ranges + // are marked as UNVERIFIED. + optional VerificationState verification = 3 [default = UNVERIFIED]; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +// Describes a field within a message. +message FieldDescriptorProto { + enum Type { + // 0 is reserved for errors. + // Order is weird for historical reasons. + TYPE_DOUBLE = 1; + TYPE_FLOAT = 2; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if + // negative values are likely. + TYPE_INT64 = 3; + TYPE_UINT64 = 4; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if + // negative values are likely. + TYPE_INT32 = 5; + TYPE_FIXED64 = 6; + TYPE_FIXED32 = 7; + TYPE_BOOL = 8; + TYPE_STRING = 9; + // Tag-delimited aggregate. + // Group type is deprecated and not supported after google.protobuf. However, Proto3 + // implementations should still be able to parse the group wire format and + // treat group fields as unknown fields. In Editions, the group wire format + // can be enabled via the `message_encoding` feature. + TYPE_GROUP = 10; + TYPE_MESSAGE = 11; // Length-delimited aggregate. + + // New in version 2. + TYPE_BYTES = 12; + TYPE_UINT32 = 13; + TYPE_ENUM = 14; + TYPE_SFIXED32 = 15; + TYPE_SFIXED64 = 16; + TYPE_SINT32 = 17; // Uses ZigZag encoding. + TYPE_SINT64 = 18; // Uses ZigZag encoding. + } + + enum Label { + // 0 is reserved for errors + LABEL_OPTIONAL = 1; + LABEL_REPEATED = 3; + // The required label is only allowed in google.protobuf. In proto3 and Editions + // it's explicitly prohibited. In Editions, the `field_presence` feature + // can be used to get this behavior. + LABEL_REQUIRED = 2; + } + + optional string name = 1; + optional int32 number = 3; + optional Label label = 4; + + // If type_name is set, this need not be set. If both this and type_name + // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP. + optional Type type = 5; + + // For message and enum types, this is the name of the type. If the name + // starts with a '.', it is fully-qualified. Otherwise, C++-like scoping + // rules are used to find the type (i.e. first the nested types within this + // message are searched, then within the parent, on up to the root + // namespace). + optional string type_name = 6; + + // For extensions, this is the name of the type being extended. It is + // resolved in the same manner as type_name. + optional string extendee = 2; + + // For numeric types, contains the original text representation of the value. + // For booleans, "true" or "false". + // For strings, contains the default text contents (not escaped in any way). + // For bytes, contains the C escaped value. All bytes >= 128 are escaped. + optional string default_value = 7; + + // If set, gives the index of a oneof in the containing type's oneof_decl + // list. This field is a member of that oneof. + optional int32 oneof_index = 9; + + // JSON name of this field. The value is set by protocol compiler. If the + // user has set a "json_name" option on this field, that option's value + // will be used. Otherwise, it's deduced from the field's name by converting + // it to camelCase. + optional string json_name = 10; + + optional FieldOptions options = 8; + + // If true, this is a proto3 "optional". When a proto3 field is optional, it + // tracks presence regardless of field type. + // + // When proto3_optional is true, this field must be belong to a oneof to + // signal to old proto3 clients that presence is tracked for this field. This + // oneof is known as a "synthetic" oneof, and this field must be its sole + // member (each proto3 optional field gets its own synthetic oneof). Synthetic + // oneofs exist in the descriptor only, and do not generate any API. Synthetic + // oneofs must be ordered after all "real" oneofs. + // + // For message fields, proto3_optional doesn't create any semantic change, + // since non-repeated message fields always track presence. However it still + // indicates the semantic detail of whether the user wrote "optional" or not. + // This can be useful for round-tripping the .proto file. For consistency we + // give message fields a synthetic oneof also, even though it is not required + // to track presence. This is especially important because the parser can't + // tell if a field is a message or an enum, so it must always create a + // synthetic oneof. + // + // Proto2 optional fields do not set this flag, because they already indicate + // optional with `LABEL_OPTIONAL`. + optional bool proto3_optional = 17; +} + +// Describes a oneof. +message OneofDescriptorProto { + optional string name = 1; + optional OneofOptions options = 2; +} + +// Describes an enum type. +message EnumDescriptorProto { + optional string name = 1; + + repeated EnumValueDescriptorProto value = 2; + + optional EnumOptions options = 3; + + // Range of reserved numeric values. Reserved values may not be used by + // entries in the same enum. Reserved ranges may not overlap. + // + // Note that this is distinct from DescriptorProto.ReservedRange in that it + // is inclusive such that it can appropriately represent the entire int32 + // domain. + message EnumReservedRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Inclusive. + } + + // Range of reserved numeric values. Reserved numeric values may not be used + // by enum values in the same enum declaration. Reserved ranges may not + // overlap. + repeated EnumReservedRange reserved_range = 4; + + // Reserved enum value names, which may not be reused. A given name may only + // be reserved once. + repeated string reserved_name = 5; +} + +// Describes a value within an enum. +message EnumValueDescriptorProto { + optional string name = 1; + optional int32 number = 2; + + optional EnumValueOptions options = 3; +} + +// Describes a service. +message ServiceDescriptorProto { + optional string name = 1; + repeated MethodDescriptorProto method = 2; + + optional ServiceOptions options = 3; +} + +// Describes a method of a service. +message MethodDescriptorProto { + optional string name = 1; + + // Input and output type names. These are resolved in the same way as + // FieldDescriptorProto.type_name, but must refer to a message type. + optional string input_type = 2; + optional string output_type = 3; + + optional MethodOptions options = 4; + + // Identifies if client streams multiple client messages + optional bool client_streaming = 5 [default = false]; + // Identifies if server streams multiple server messages + optional bool server_streaming = 6 [default = false]; +} + +// =================================================================== +// Options + +// Each of the definitions above may have "options" attached. These are +// just annotations which may cause code to be generated slightly differently +// or may contain hints for code that manipulates protocol messages. +// +// Clients may define custom options as extensions of the *Options messages. +// These extensions may not yet be known at parsing time, so the parser cannot +// store the values in them. Instead it stores them in a field in the *Options +// message called uninterpreted_option. This field must have the same name +// across all *Options messages. We then use this field to populate the +// extensions when we build a descriptor, at which point all protos have been +// parsed and so all extensions are known. +// +// Extension numbers for custom options may be chosen as follows: +// * For options which will only be used within a single application or +// organization, or for experimental options, use field numbers 50000 +// through 99999. It is up to you to ensure that you do not use the +// same number for multiple options. +// * For options which will be published and used publicly by multiple +// independent entities, e-mail protobuf-global-extension-registry@google.com +// to reserve extension numbers. Simply provide your project name (e.g. +// Objective-C plugin) and your project website (if available) -- there's no +// need to explain how you intend to use them. Usually you only need one +// extension number. You can declare multiple options with only one extension +// number by putting them in a sub-message. See the Custom Options section of +// the docs for examples: +// https://developers.google.com/protocol-buffers/docs/proto#options +// If this turns out to be popular, a web service will be set up +// to automatically assign option numbers. + +message FileOptions { + + // Sets the Java package where classes generated from this .proto will be + // placed. By default, the proto package is used, but this is often + // inappropriate because proto packages do not normally start with backwards + // domain names. + optional string java_package = 1; + + // Controls the name of the wrapper Java class generated for the .proto file. + // That class will always contain the .proto file's getDescriptor() method as + // well as any top-level extensions defined in the .proto file. + // If java_multiple_files is disabled, then all the other classes from the + // .proto file will be nested inside the single wrapper outer class. + optional string java_outer_classname = 8; + + // If enabled, then the Java code generator will generate a separate .java + // file for each top-level message, enum, and service defined in the .proto + // file. Thus, these types will *not* be nested inside the wrapper class + // named by java_outer_classname. However, the wrapper class will still be + // generated to contain the file's getDescriptor() method as well as any + // top-level extensions defined in the file. + optional bool java_multiple_files = 10 [default = false]; + + // This option does nothing. + optional bool java_generate_equals_and_hash = 20 [deprecated=true]; + + // If set true, then the Java2 code generator will generate code that + // throws an exception whenever an attempt is made to assign a non-UTF-8 + // byte sequence to a string field. + // Message reflection will do the same. + // However, an extension field still accepts non-UTF-8 byte sequences. + // This option has no effect on when used with the lite runtime. + optional bool java_string_check_utf8 = 27 [default = false]; + + // Generated classes can be optimized for speed or code size. + enum OptimizeMode { + SPEED = 1; // Generate complete code for parsing, serialization, + // etc. + CODE_SIZE = 2; // Use ReflectionOps to implement these methods. + LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime. + } + optional OptimizeMode optimize_for = 9 [default = SPEED]; + + // Sets the Go package where structs generated from this .proto will be + // placed. If omitted, the Go package will be derived from the following: + // - The basename of the package import path, if provided. + // - Otherwise, the package statement in the .proto file, if present. + // - Otherwise, the basename of the .proto file, without extension. + optional string go_package = 11; + + // Should generic services be generated in each language? "Generic" services + // are not specific to any particular RPC system. They are generated by the + // main code generators in each language (without additional plugins). + // Generic services were the only kind of service generation supported by + // early versions of google.protobuf. + // + // Generic services are now considered deprecated in favor of using plugins + // that generate code specific to your particular RPC system. Therefore, + // these default to false. Old code which depends on generic services should + // explicitly set them to true. + optional bool cc_generic_services = 16 [default = false]; + optional bool java_generic_services = 17 [default = false]; + optional bool py_generic_services = 18 [default = false]; + optional bool php_generic_services = 42 [default = false]; + + // Is this file deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for everything in the file, or it will be completely ignored; in the very + // least, this is a formalization for deprecating files. + optional bool deprecated = 23 [default = false]; + + // Enables the use of arenas for the proto messages in this file. This applies + // only to generated classes for C++. + optional bool cc_enable_arenas = 31 [default = true]; + + // Sets the objective c class prefix which is prepended to all objective c + // generated classes from this .proto. There is no default. + optional string objc_class_prefix = 36; + + // Namespace for generated classes; defaults to the package. + optional string csharp_namespace = 37; + + // By default Swift generators will take the proto package and CamelCase it + // replacing '.' with underscore and use that to prefix the types/symbols + // defined. When this options is provided, they will use this value instead + // to prefix the types/symbols defined. + optional string swift_prefix = 39; + + // Sets the php class prefix which is prepended to all php generated classes + // from this .proto. Default is empty. + optional string php_class_prefix = 40; + + // Use this option to change the namespace of php generated classes. Default + // is empty. When this option is empty, the package name will be used for + // determining the namespace. + optional string php_namespace = 41; + + // Use this option to change the namespace of php generated metadata classes. + // Default is empty. When this option is empty, the proto file name will be + // used for determining the namespace. + optional string php_metadata_namespace = 44; + + // Use this option to change the package of ruby generated classes. Default + // is empty. When this option is not set, the package name will be used for + // determining the ruby package. + optional string ruby_package = 45; + + // Any features defined in the specific edition. + optional FeatureSet features = 50; + + // The parser stores options it doesn't recognize here. + // See the documentation for the "Options" section above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. + // See the documentation for the "Options" section above. + extensions 1000 to max; + + reserved 38; +} + +message MessageOptions { + // Set true to use the old proto1 MessageSet wire format for extensions. + // This is provided for backwards-compatibility with the MessageSet wire + // format. You should not use this for any other reason: It's less + // efficient, has fewer features, and is more complicated. + // + // The message must be defined exactly as follows: + // message Foo { + // option message_set_wire_format = true; + // extensions 4 to max; + // } + // Note that the message cannot have any defined fields; MessageSets only + // have extensions. + // + // All extensions of your type must be singular messages; e.g. they cannot + // be int32s, enums, or repeated messages. + // + // Because this is an option, the above two restrictions are not enforced by + // the protocol compiler. + optional bool message_set_wire_format = 1 [default = false]; + + // Disables the generation of the standard "descriptor()" accessor, which can + // conflict with a field of the same name. This is meant to make migration + // from proto1 easier; new code should avoid fields named "descriptor". + optional bool no_standard_descriptor_accessor = 2 [default = false]; + + // Is this message deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the message, or it will be completely ignored; in the very least, + // this is a formalization for deprecating messages. + optional bool deprecated = 3 [default = false]; + + reserved 4, 5, 6; + + // NOTE: Do not set the option in .proto files. Always use the maps syntax + // instead. The option should only be implicitly set by the proto compiler + // parser. + // + // Whether the message is an automatically generated map entry type for the + // maps field. + // + // For maps fields: + // map map_field = 1; + // The parsed descriptor looks like: + // message MapFieldEntry { + // option map_entry = true; + // optional KeyType key = 1; + // optional ValueType value = 2; + // } + // repeated MapFieldEntry map_field = 1; + // + // Implementations may choose not to generate the map_entry=true message, but + // use a native map in the target language to hold the keys and values. + // The reflection APIs in such implementations still need to work as + // if the field is a repeated message field. + optional bool map_entry = 7; + + reserved 8; // javalite_serializable + reserved 9; // javanano_as_lite + + // Enable the legacy handling of JSON field name conflicts. This lowercases + // and strips underscored from the fields before comparison in proto3 only. + // The new behavior takes `json_name` into account and applies to proto2 as + // well. + // + // This should only be used as a temporary measure against broken builds due + // to the change in behavior for JSON field name conflicts. + // + // TODO This is legacy behavior we plan to remove once downstream + // teams have had time to migrate. + optional bool deprecated_legacy_json_field_conflicts = 11 [deprecated = true]; + + // Any features defined in the specific edition. + optional FeatureSet features = 12; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message FieldOptions { + // The ctype option instructs the C++ code generator to use a different + // representation of the field than it normally would. See the specific + // options below. This option is only implemented to support use of + // [ctype=CORD] and [ctype=STRING] (the default) on non-repeated fields of + // type "bytes" in the open source release -- sorry, we'll try to include + // other types in a future version! + optional CType ctype = 1 [default = STRING]; + enum CType { + // Default mode. + STRING = 0; + + // The option [ctype=CORD] may be applied to a non-repeated field of type + // "bytes". It indicates that in C++, the data should be stored in a Cord + // instead of a string. For very large strings, this may reduce memory + // fragmentation. It may also allow better performance when parsing from a + // Cord, or when parsing with aliasing enabled, as the parsed Cord may then + // alias the original buffer. + CORD = 1; + + STRING_PIECE = 2; + } + // The packed option can be enabled for repeated primitive fields to enable + // a more efficient representation on the wire. Rather than repeatedly + // writing the tag and type for each element, the entire array is encoded as + // a single length-delimited blob. In proto3, only explicit setting it to + // false will avoid using packed encoding. This option is prohibited in + // Editions, but the `repeated_field_encoding` feature can be used to control + // the behavior. + optional bool packed = 2; + + // The jstype option determines the JavaScript type used for values of the + // field. The option is permitted only for 64 bit integral and fixed types + // (int64, uint64, sint64, fixed64, sfixed64). A field with jstype JS_STRING + // is represented as JavaScript string, which avoids loss of precision that + // can happen when a large value is converted to a floating point JavaScript. + // Specifying JS_NUMBER for the jstype causes the generated JavaScript code to + // use the JavaScript "number" type. The behavior of the default option + // JS_NORMAL is implementation dependent. + // + // This option is an enum to permit additional types to be added, e.g. + // goog.math.Integer. + optional JSType jstype = 6 [default = JS_NORMAL]; + enum JSType { + // Use the default type. + JS_NORMAL = 0; + + // Use JavaScript strings. + JS_STRING = 1; + + // Use JavaScript numbers. + JS_NUMBER = 2; + } + + // Should this field be parsed lazily? Lazy applies only to message-type + // fields. It means that when the outer message is initially parsed, the + // inner message's contents will not be parsed but instead stored in encoded + // form. The inner message will actually be parsed when it is first accessed. + // + // This is only a hint. Implementations are free to choose whether to use + // eager or lazy parsing regardless of the value of this option. However, + // setting this option true suggests that the protocol author believes that + // using lazy parsing on this field is worth the additional bookkeeping + // overhead typically needed to implement it. + // + // This option does not affect the public interface of any generated code; + // all method signatures remain the same. Furthermore, thread-safety of the + // interface is not affected by this option; const methods remain safe to + // call from multiple threads concurrently, while non-const methods continue + // to require exclusive access. + // + // Note that implementations may choose not to check required fields within + // a lazy sub-message. That is, calling IsInitialized() on the outer message + // may return true even if the inner message has missing required fields. + // This is necessary because otherwise the inner message would have to be + // parsed in order to perform the check, defeating the purpose of lazy + // parsing. An implementation which chooses not to check required fields + // must be consistent about it. That is, for any particular sub-message, the + // implementation must either *always* check its required fields, or *never* + // check its required fields, regardless of whether or not the message has + // been parsed. + // + // As of May 2022, lazy verifies the contents of the byte stream during + // parsing. An invalid byte stream will cause the overall parsing to fail. + optional bool lazy = 5 [default = false]; + + // unverified_lazy does no correctness checks on the byte stream. This should + // only be used where lazy with verification is prohibitive for performance + // reasons. + optional bool unverified_lazy = 15 [default = false]; + + // Is this field deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for accessors, or it will be completely ignored; in the very least, this + // is a formalization for deprecating fields. + optional bool deprecated = 3 [default = false]; + + // For Google-internal migration only. Do not use. + optional bool weak = 10 [default = false]; + + // Indicate that the field value should not be printed out when using debug + // formats, e.g. when the field contains sensitive credentials. + optional bool debug_redact = 16 [default = false]; + + // If set to RETENTION_SOURCE, the option will be omitted from the binary. + // Note: as of January 2023, support for this is in progress and does not yet + // have an effect (b/264593489). + enum OptionRetention { + RETENTION_UNKNOWN = 0; + RETENTION_RUNTIME = 1; + RETENTION_SOURCE = 2; + } + + optional OptionRetention retention = 17; + + // This indicates the types of entities that the field may apply to when used + // as an option. If it is unset, then the field may be freely used as an + // option on any kind of entity. Note: as of January 2023, support for this is + // in progress and does not yet have an effect (b/264593489). + enum OptionTargetType { + TARGET_TYPE_UNKNOWN = 0; + TARGET_TYPE_FILE = 1; + TARGET_TYPE_EXTENSION_RANGE = 2; + TARGET_TYPE_MESSAGE = 3; + TARGET_TYPE_FIELD = 4; + TARGET_TYPE_ONEOF = 5; + TARGET_TYPE_ENUM = 6; + TARGET_TYPE_ENUM_ENTRY = 7; + TARGET_TYPE_SERVICE = 8; + TARGET_TYPE_METHOD = 9; + } + + repeated OptionTargetType targets = 19; + + message EditionDefault { + optional Edition edition = 3; + optional string value = 2; // Textproto value. + } + repeated EditionDefault edition_defaults = 20; + + // Any features defined in the specific edition. + optional FeatureSet features = 21; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; + + reserved 4; // removed jtype + reserved 18; // reserve target, target_obsolete_do_not_use +} + +message OneofOptions { + // Any features defined in the specific edition. + optional FeatureSet features = 1; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message EnumOptions { + + // Set this option to true to allow mapping different tag names to the same + // value. + optional bool allow_alias = 2; + + // Is this enum deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum, or it will be completely ignored; in the very least, this + // is a formalization for deprecating enums. + optional bool deprecated = 3 [default = false]; + + reserved 5; // javanano_as_lite + + // Enable the legacy handling of JSON field name conflicts. This lowercases + // and strips underscored from the fields before comparison in proto3 only. + // The new behavior takes `json_name` into account and applies to proto2 as + // well. + // TODO Remove this legacy behavior once downstream teams have + // had time to migrate. + optional bool deprecated_legacy_json_field_conflicts = 6 [deprecated = true]; + + // Any features defined in the specific edition. + optional FeatureSet features = 7; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message EnumValueOptions { + // Is this enum value deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum value, or it will be completely ignored; in the very least, + // this is a formalization for deprecating enum values. + optional bool deprecated = 1 [default = false]; + + // Any features defined in the specific edition. + optional FeatureSet features = 2; + + // Indicate that fields annotated with this enum value should not be printed + // out when using debug formats, e.g. when the field contains sensitive + // credentials. + optional bool debug_redact = 3 [default = false]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message ServiceOptions { + + // Any features defined in the specific edition. + optional FeatureSet features = 34; + + // Note: Field numbers 1 through 32 are reserved for Google's internal RPC + // framework. We apologize for hoarding these numbers to ourselves, but + // we were already using them long before we decided to release Protocol + // Buffers. + + // Is this service deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the service, or it will be completely ignored; in the very least, + // this is a formalization for deprecating services. + optional bool deprecated = 33 [default = false]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message MethodOptions { + + // Note: Field numbers 1 through 32 are reserved for Google's internal RPC + // framework. We apologize for hoarding these numbers to ourselves, but + // we were already using them long before we decided to release Protocol + // Buffers. + + // Is this method deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the method, or it will be completely ignored; in the very least, + // this is a formalization for deprecating methods. + optional bool deprecated = 33 [default = false]; + + // Is this method side-effect-free (or safe in HTTP parlance), or idempotent, + // or neither? HTTP based RPC implementation may choose GET verb for safe + // methods, and PUT verb for idempotent methods instead of the default POST. + enum IdempotencyLevel { + IDEMPOTENCY_UNKNOWN = 0; + NO_SIDE_EFFECTS = 1; // implies idempotent + IDEMPOTENT = 2; // idempotent, but may have side effects + } + optional IdempotencyLevel idempotency_level = 34 + [default = IDEMPOTENCY_UNKNOWN]; + + // Any features defined in the specific edition. + optional FeatureSet features = 35; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +// A message representing a option the parser does not recognize. This only +// appears in options protos created by the compiler::Parser class. +// DescriptorPool resolves these when building Descriptor objects. Therefore, +// options protos in descriptor objects (e.g. returned by Descriptor::options(), +// or produced by Descriptor::CopyTo()) will never have UninterpretedOptions +// in them. +message UninterpretedOption { + // The name of the uninterpreted option. Each string represents a segment in + // a dot-separated name. is_extension is true iff a segment represents an + // extension (denoted with parentheses in options specs in .proto files). + // E.g.,{ ["foo", false], ["bar.baz", true], ["moo", false] } represents + // "foo.(bar.baz).moo". + message NamePart { + required string name_part = 1; + required bool is_extension = 2; + } + repeated NamePart name = 2; + + // The value of the uninterpreted option, in whatever type the tokenizer + // identified it as during parsing. Exactly one of these should be set. + optional string identifier_value = 3; + optional uint64 positive_int_value = 4; + optional int64 negative_int_value = 5; + optional double double_value = 6; + optional bytes string_value = 7; + optional string aggregate_value = 8; +} + +// =================================================================== +// Features + +// TODO Enums in C++ gencode (and potentially other languages) are +// not well scoped. This means that each of the feature enums below can clash +// with each other. The short names we've chosen maximize call-site +// readability, but leave us very open to this scenario. A future feature will +// be designed and implemented to handle this, hopefully before we ever hit a +// conflict here. +message FeatureSet { + enum FieldPresence { + FIELD_PRESENCE_UNKNOWN = 0; + EXPLICIT = 1; + IMPLICIT = 2; + LEGACY_REQUIRED = 3; + } + optional FieldPresence field_presence = 1 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + edition_defaults = { edition: EDITION_PROTO2, value: "EXPLICIT" }, + edition_defaults = { edition: EDITION_PROTO3, value: "IMPLICIT" }, + edition_defaults = { edition: EDITION_2023, value: "EXPLICIT" } + ]; + + enum EnumType { + ENUM_TYPE_UNKNOWN = 0; + OPEN = 1; + CLOSED = 2; + } + optional EnumType enum_type = 2 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_FILE, + edition_defaults = { edition: EDITION_PROTO2, value: "CLOSED" }, + edition_defaults = { edition: EDITION_PROTO3, value: "OPEN" } + ]; + + enum RepeatedFieldEncoding { + REPEATED_FIELD_ENCODING_UNKNOWN = 0; + PACKED = 1; + EXPANDED = 2; + } + optional RepeatedFieldEncoding repeated_field_encoding = 3 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + edition_defaults = { edition: EDITION_PROTO2, value: "EXPANDED" }, + edition_defaults = { edition: EDITION_PROTO3, value: "PACKED" } + ]; + + enum Utf8Validation { + UTF8_VALIDATION_UNKNOWN = 0; + NONE = 1; + VERIFY = 2; + } + optional Utf8Validation utf8_validation = 4 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + edition_defaults = { edition: EDITION_PROTO2, value: "NONE" }, + edition_defaults = { edition: EDITION_PROTO3, value: "VERIFY" } + ]; + + enum MessageEncoding { + MESSAGE_ENCODING_UNKNOWN = 0; + LENGTH_PREFIXED = 1; + DELIMITED = 2; + } + optional MessageEncoding message_encoding = 5 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + edition_defaults = { edition: EDITION_PROTO2, value: "LENGTH_PREFIXED" } + ]; + + enum JsonFormat { + JSON_FORMAT_UNKNOWN = 0; + ALLOW = 1; + LEGACY_BEST_EFFORT = 2; + } + optional JsonFormat json_format = 6 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_MESSAGE, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_FILE, + edition_defaults = { edition: EDITION_PROTO2, value: "LEGACY_BEST_EFFORT" }, + edition_defaults = { edition: EDITION_PROTO3, value: "ALLOW" } + ]; + + reserved 999; + + extensions 1000; // for Protobuf C++ + extensions 1001; // for Protobuf Java + + extensions 9995 to 9999; // For internal testing +} + +// A compiled specification for the defaults of a set of features. These +// messages are generated from FeatureSet extensions and can be used to seed +// feature resolution. The resolution with this object becomes a simple search +// for the closest matching edition, followed by proto merges. +message FeatureSetDefaults { + // A map from every known edition with a unique set of defaults to its + // defaults. Not all editions may be contained here. For a given edition, + // the defaults at the closest matching edition ordered at or before it should + // be used. This field must be in strict ascending order by edition. + message FeatureSetEditionDefault { + optional Edition edition = 3; + optional FeatureSet features = 2; + } + repeated FeatureSetEditionDefault defaults = 1; + + // The minimum supported edition (inclusive) when this was constructed. + // Editions before this will not have defaults. + optional Edition minimum_edition = 4; + + // The maximum known edition (inclusive) when this was constructed. Editions + // after this will not have reliable defaults. + optional Edition maximum_edition = 5; +} + +// =================================================================== +// Optional source code info + +// Encapsulates information about the original source file from which a +// FileDescriptorProto was generated. +message SourceCodeInfo { + // A Location identifies a piece of source code in a .proto file which + // corresponds to a particular definition. This information is intended + // to be useful to IDEs, code indexers, documentation generators, and similar + // tools. + // + // For example, say we have a file like: + // message Foo { + // optional string foo = 1; + // } + // Let's look at just the field definition: + // optional string foo = 1; + // ^ ^^ ^^ ^ ^^^ + // a bc de f ghi + // We have the following locations: + // span path represents + // [a,i) [ 4, 0, 2, 0 ] The whole field definition. + // [a,b) [ 4, 0, 2, 0, 4 ] The label (optional). + // [c,d) [ 4, 0, 2, 0, 5 ] The type (string). + // [e,f) [ 4, 0, 2, 0, 1 ] The name (foo). + // [g,h) [ 4, 0, 2, 0, 3 ] The number (1). + // + // Notes: + // - A location may refer to a repeated field itself (i.e. not to any + // particular index within it). This is used whenever a set of elements are + // logically enclosed in a single code segment. For example, an entire + // extend block (possibly containing multiple extension definitions) will + // have an outer location whose path refers to the "extensions" repeated + // field without an index. + // - Multiple locations may have the same path. This happens when a single + // logical declaration is spread out across multiple places. The most + // obvious example is the "extend" block again -- there may be multiple + // extend blocks in the same scope, each of which will have the same path. + // - A location's span is not always a subset of its parent's span. For + // example, the "extendee" of an extension declaration appears at the + // beginning of the "extend" block and is shared by all extensions within + // the block. + // - Just because a location's span is a subset of some other location's span + // does not mean that it is a descendant. For example, a "group" defines + // both a type and a field in a single declaration. Thus, the locations + // corresponding to the type and field and their components will overlap. + // - Code which tries to interpret locations should probably be designed to + // ignore those that it doesn't understand, as more types of locations could + // be recorded in the future. + repeated Location location = 1; + message Location { + // Identifies which part of the FileDescriptorProto was defined at this + // location. + // + // Each element is a field number or an index. They form a path from + // the root FileDescriptorProto to the place where the definition occurs. + // For example, this path: + // [ 4, 3, 2, 7, 1 ] + // refers to: + // file.message_type(3) // 4, 3 + // .field(7) // 2, 7 + // .name() // 1 + // This is because FileDescriptorProto.message_type has field number 4: + // repeated DescriptorProto message_type = 4; + // and DescriptorProto.field has field number 2: + // repeated FieldDescriptorProto field = 2; + // and FieldDescriptorProto.name has field number 1: + // optional string name = 1; + // + // Thus, the above path gives the location of a field name. If we removed + // the last element: + // [ 4, 3, 2, 7 ] + // this path refers to the whole field declaration (from the beginning + // of the label to the terminating semicolon). + repeated int32 path = 1 [packed = true]; + + // Always has exactly three or four elements: start line, start column, + // end line (optional, otherwise assumed same as start line), end column. + // These are packed into a single field for efficiency. Note that line + // and column numbers are zero-based -- typically you will want to add + // 1 to each before displaying to a user. + repeated int32 span = 2 [packed = true]; + + // If this SourceCodeInfo represents a complete declaration, these are any + // comments appearing before and after the declaration which appear to be + // attached to the declaration. + // + // A series of line comments appearing on consecutive lines, with no other + // tokens appearing on those lines, will be treated as a single comment. + // + // leading_detached_comments will keep paragraphs of comments that appear + // before (but not connected to) the current element. Each paragraph, + // separated by empty lines, will be one comment element in the repeated + // field. + // + // Only the comment content is provided; comment markers (e.g. //) are + // stripped out. For block comments, leading whitespace and an asterisk + // will be stripped from the beginning of each line other than the first. + // Newlines are included in the output. + // + // Examples: + // + // optional int32 foo = 1; // Comment attached to foo. + // // Comment attached to bar. + // optional int32 bar = 2; + // + // optional string baz = 3; + // // Comment attached to baz. + // // Another line attached to baz. + // + // // Comment attached to moo. + // // + // // Another line attached to moo. + // optional double moo = 4; + // + // // Detached comment for corge. This is not leading or trailing comments + // // to moo or corge because there are blank lines separating it from + // // both. + // + // // Detached comment for corge paragraph 2. + // + // optional string corge = 5; + // /* Block comment attached + // * to corge. Leading asterisks + // * will be removed. */ + // /* Block comment attached to + // * grault. */ + // optional int32 grault = 6; + // + // // ignored detached comments. + optional string leading_comments = 3; + optional string trailing_comments = 4; + repeated string leading_detached_comments = 6; + } +} + +// Describes the relationship between generated code and its original source +// file. A GeneratedCodeInfo message is associated with only one generated +// source file, but may contain references to different source .proto files. +message GeneratedCodeInfo { + // An Annotation connects some span of text in generated code to an element + // of its generating .proto file. + repeated Annotation annotation = 1; + message Annotation { + // Identifies the element in the original source .proto file. This field + // is formatted the same as SourceCodeInfo.Location.path. + repeated int32 path = 1 [packed = true]; + + // Identifies the filesystem path to the original source .proto. + optional string source_file = 2; + + // Identifies the starting offset in bytes in the generated code + // that relates to the identified object. + optional int32 begin = 3; + + // Identifies the ending offset in bytes in the generated code that + // relates to the identified object. The end offset should be one past + // the last relevant byte (so the length of the text = end - begin). + optional int32 end = 4; + + // Represents the identified object's effect on the element in the original + // .proto file. + enum Semantic { + // There is no effect or the effect is indescribable. + NONE = 0; + // The element is set or otherwise mutated. + SET = 1; + // An alias to the element is returned. + ALIAS = 2; + } + optional Semantic semantic = 5; + } +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/duration.proto b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/duration.proto new file mode 100644 index 00000000..41f40c22 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/duration.proto @@ -0,0 +1,115 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/durationpb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "DurationProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// A Duration represents a signed, fixed-length span of time represented +// as a count of seconds and fractions of seconds at nanosecond +// resolution. It is independent of any calendar and concepts like "day" +// or "month". It is related to Timestamp in that the difference between +// two Timestamp values is a Duration and it can be added or subtracted +// from a Timestamp. Range is approximately +-10,000 years. +// +// # Examples +// +// Example 1: Compute Duration from two Timestamps in pseudo code. +// +// Timestamp start = ...; +// Timestamp end = ...; +// Duration duration = ...; +// +// duration.seconds = end.seconds - start.seconds; +// duration.nanos = end.nanos - start.nanos; +// +// if (duration.seconds < 0 && duration.nanos > 0) { +// duration.seconds += 1; +// duration.nanos -= 1000000000; +// } else if (duration.seconds > 0 && duration.nanos < 0) { +// duration.seconds -= 1; +// duration.nanos += 1000000000; +// } +// +// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. +// +// Timestamp start = ...; +// Duration duration = ...; +// Timestamp end = ...; +// +// end.seconds = start.seconds + duration.seconds; +// end.nanos = start.nanos + duration.nanos; +// +// if (end.nanos < 0) { +// end.seconds -= 1; +// end.nanos += 1000000000; +// } else if (end.nanos >= 1000000000) { +// end.seconds += 1; +// end.nanos -= 1000000000; +// } +// +// Example 3: Compute Duration from datetime.timedelta in Python. +// +// td = datetime.timedelta(days=3, minutes=10) +// duration = Duration() +// duration.FromTimedelta(td) +// +// # JSON Mapping +// +// In JSON format, the Duration type is encoded as a string rather than an +// object, where the string ends in the suffix "s" (indicating seconds) and +// is preceded by the number of seconds, with nanoseconds expressed as +// fractional seconds. For example, 3 seconds with 0 nanoseconds should be +// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should +// be expressed in JSON format as "3.000000001s", and 3 seconds and 1 +// microsecond should be expressed in JSON format as "3.000001s". +// +message Duration { + // Signed seconds of the span of time. Must be from -315,576,000,000 + // to +315,576,000,000 inclusive. Note: these bounds are computed from: + // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years + int64 seconds = 1; + + // Signed fractions of a second at nanosecond resolution of the span + // of time. Durations less than one second are represented with a 0 + // `seconds` field and a positive or negative `nanos` field. For durations + // of one second or more, a non-zero value for the `nanos` field must be + // of the same sign as the `seconds` field. Must be from -999,999,999 + // to +999,999,999 inclusive. + int32 nanos = 2; +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/empty.proto b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/empty.proto new file mode 100644 index 00000000..b87c89dc --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/empty.proto @@ -0,0 +1,51 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option go_package = "google.golang.org/protobuf/types/known/emptypb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "EmptyProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; + +// A generic empty message that you can re-use to avoid defining duplicated +// empty messages in your APIs. A typical example is to use it as the request +// or the response type of an API method. For instance: +// +// service Foo { +// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); +// } +// +message Empty {} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/field_mask.proto b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/field_mask.proto new file mode 100644 index 00000000..b28334b9 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/field_mask.proto @@ -0,0 +1,245 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option java_package = "com.google.protobuf"; +option java_outer_classname = "FieldMaskProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option go_package = "google.golang.org/protobuf/types/known/fieldmaskpb"; +option cc_enable_arenas = true; + +// `FieldMask` represents a set of symbolic field paths, for example: +// +// paths: "f.a" +// paths: "f.b.d" +// +// Here `f` represents a field in some root message, `a` and `b` +// fields in the message found in `f`, and `d` a field found in the +// message in `f.b`. +// +// Field masks are used to specify a subset of fields that should be +// returned by a get operation or modified by an update operation. +// Field masks also have a custom JSON encoding (see below). +// +// # Field Masks in Projections +// +// When used in the context of a projection, a response message or +// sub-message is filtered by the API to only contain those fields as +// specified in the mask. For example, if the mask in the previous +// example is applied to a response message as follows: +// +// f { +// a : 22 +// b { +// d : 1 +// x : 2 +// } +// y : 13 +// } +// z: 8 +// +// The result will not contain specific values for fields x,y and z +// (their value will be set to the default, and omitted in proto text +// output): +// +// +// f { +// a : 22 +// b { +// d : 1 +// } +// } +// +// A repeated field is not allowed except at the last position of a +// paths string. +// +// If a FieldMask object is not present in a get operation, the +// operation applies to all fields (as if a FieldMask of all fields +// had been specified). +// +// Note that a field mask does not necessarily apply to the +// top-level response message. In case of a REST get operation, the +// field mask applies directly to the response, but in case of a REST +// list operation, the mask instead applies to each individual message +// in the returned resource list. In case of a REST custom method, +// other definitions may be used. Where the mask applies will be +// clearly documented together with its declaration in the API. In +// any case, the effect on the returned resource/resources is required +// behavior for APIs. +// +// # Field Masks in Update Operations +// +// A field mask in update operations specifies which fields of the +// targeted resource are going to be updated. The API is required +// to only change the values of the fields as specified in the mask +// and leave the others untouched. If a resource is passed in to +// describe the updated values, the API ignores the values of all +// fields not covered by the mask. +// +// If a repeated field is specified for an update operation, new values will +// be appended to the existing repeated field in the target resource. Note that +// a repeated field is only allowed in the last position of a `paths` string. +// +// If a sub-message is specified in the last position of the field mask for an +// update operation, then new value will be merged into the existing sub-message +// in the target resource. +// +// For example, given the target message: +// +// f { +// b { +// d: 1 +// x: 2 +// } +// c: [1] +// } +// +// And an update message: +// +// f { +// b { +// d: 10 +// } +// c: [2] +// } +// +// then if the field mask is: +// +// paths: ["f.b", "f.c"] +// +// then the result will be: +// +// f { +// b { +// d: 10 +// x: 2 +// } +// c: [1, 2] +// } +// +// An implementation may provide options to override this default behavior for +// repeated and message fields. +// +// In order to reset a field's value to the default, the field must +// be in the mask and set to the default value in the provided resource. +// Hence, in order to reset all fields of a resource, provide a default +// instance of the resource and set all fields in the mask, or do +// not provide a mask as described below. +// +// If a field mask is not present on update, the operation applies to +// all fields (as if a field mask of all fields has been specified). +// Note that in the presence of schema evolution, this may mean that +// fields the client does not know and has therefore not filled into +// the request will be reset to their default. If this is unwanted +// behavior, a specific service may require a client to always specify +// a field mask, producing an error if not. +// +// As with get operations, the location of the resource which +// describes the updated values in the request message depends on the +// operation kind. In any case, the effect of the field mask is +// required to be honored by the API. +// +// ## Considerations for HTTP REST +// +// The HTTP kind of an update operation which uses a field mask must +// be set to PATCH instead of PUT in order to satisfy HTTP semantics +// (PUT must only be used for full updates). +// +// # JSON Encoding of Field Masks +// +// In JSON, a field mask is encoded as a single string where paths are +// separated by a comma. Fields name in each path are converted +// to/from lower-camel naming conventions. +// +// As an example, consider the following message declarations: +// +// message Profile { +// User user = 1; +// Photo photo = 2; +// } +// message User { +// string display_name = 1; +// string address = 2; +// } +// +// In proto a field mask for `Profile` may look as such: +// +// mask { +// paths: "user.display_name" +// paths: "photo" +// } +// +// In JSON, the same mask is represented as below: +// +// { +// mask: "user.displayName,photo" +// } +// +// # Field Masks and Oneof Fields +// +// Field masks treat fields in oneofs just as regular fields. Consider the +// following message: +// +// message SampleMessage { +// oneof test_oneof { +// string name = 4; +// SubMessage sub_message = 9; +// } +// } +// +// The field mask can be: +// +// mask { +// paths: "name" +// } +// +// Or: +// +// mask { +// paths: "sub_message" +// } +// +// Note that oneof type names ("test_oneof" in this case) cannot be used in +// paths. +// +// ## Field Mask Verification +// +// The implementation of any API method which has a FieldMask type field in the +// request should verify the included field paths, and return an +// `INVALID_ARGUMENT` error if any path is unmappable. +message FieldMask { + // The set of field mask paths. + repeated string paths = 1; +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/source_context.proto b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/source_context.proto new file mode 100644 index 00000000..135f50fe --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/source_context.proto @@ -0,0 +1,48 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option java_package = "com.google.protobuf"; +option java_outer_classname = "SourceContextProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option go_package = "google.golang.org/protobuf/types/known/sourcecontextpb"; + +// `SourceContext` represents information about the source of a +// protobuf element, like the file in which it is defined. +message SourceContext { + // The path-qualified name of the .proto file that contained the associated + // protobuf element. For example: `"google/protobuf/source_context.proto"`. + string file_name = 1; +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/struct.proto b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/struct.proto new file mode 100644 index 00000000..1bf0c1ad --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/struct.proto @@ -0,0 +1,95 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/structpb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "StructProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// `Struct` represents a structured data value, consisting of fields +// which map to dynamically typed values. In some languages, `Struct` +// might be supported by a native representation. For example, in +// scripting languages like JS a struct is represented as an +// object. The details of that representation are described together +// with the proto support for the language. +// +// The JSON representation for `Struct` is JSON object. +message Struct { + // Unordered map of dynamically typed values. + map fields = 1; +} + +// `Value` represents a dynamically typed value which can be either +// null, a number, a string, a boolean, a recursive struct value, or a +// list of values. A producer of value is expected to set one of these +// variants. Absence of any variant indicates an error. +// +// The JSON representation for `Value` is JSON value. +message Value { + // The kind of value. + oneof kind { + // Represents a null value. + NullValue null_value = 1; + // Represents a double value. + double number_value = 2; + // Represents a string value. + string string_value = 3; + // Represents a boolean value. + bool bool_value = 4; + // Represents a structured value. + Struct struct_value = 5; + // Represents a repeated `Value`. + ListValue list_value = 6; + } +} + +// `NullValue` is a singleton enumeration to represent the null value for the +// `Value` type union. +// +// The JSON representation for `NullValue` is JSON `null`. +enum NullValue { + // Null value. + NULL_VALUE = 0; +} + +// `ListValue` is a wrapper around a repeated field of values. +// +// The JSON representation for `ListValue` is JSON array. +message ListValue { + // Repeated field of dynamically typed values. + repeated Value values = 1; +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/timestamp.proto b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/timestamp.proto new file mode 100644 index 00000000..fd0bc07d --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/timestamp.proto @@ -0,0 +1,144 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/timestamppb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "TimestampProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// A Timestamp represents a point in time independent of any time zone or local +// calendar, encoded as a count of seconds and fractions of seconds at +// nanosecond resolution. The count is relative to an epoch at UTC midnight on +// January 1, 1970, in the proleptic Gregorian calendar which extends the +// Gregorian calendar backwards to year one. +// +// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap +// second table is needed for interpretation, using a [24-hour linear +// smear](https://developers.google.com/time/smear). +// +// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By +// restricting to that range, we ensure that we can convert to and from [RFC +// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. +// +// # Examples +// +// Example 1: Compute Timestamp from POSIX `time()`. +// +// Timestamp timestamp; +// timestamp.set_seconds(time(NULL)); +// timestamp.set_nanos(0); +// +// Example 2: Compute Timestamp from POSIX `gettimeofday()`. +// +// struct timeval tv; +// gettimeofday(&tv, NULL); +// +// Timestamp timestamp; +// timestamp.set_seconds(tv.tv_sec); +// timestamp.set_nanos(tv.tv_usec * 1000); +// +// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. +// +// FILETIME ft; +// GetSystemTimeAsFileTime(&ft); +// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; +// +// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z +// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. +// Timestamp timestamp; +// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); +// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); +// +// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. +// +// long millis = System.currentTimeMillis(); +// +// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) +// .setNanos((int) ((millis % 1000) * 1000000)).build(); +// +// Example 5: Compute Timestamp from Java `Instant.now()`. +// +// Instant now = Instant.now(); +// +// Timestamp timestamp = +// Timestamp.newBuilder().setSeconds(now.getEpochSecond()) +// .setNanos(now.getNano()).build(); +// +// Example 6: Compute Timestamp from current time in Python. +// +// timestamp = Timestamp() +// timestamp.GetCurrentTime() +// +// # JSON Mapping +// +// In JSON format, the Timestamp type is encoded as a string in the +// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the +// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" +// where {year} is always expressed using four digits while {month}, {day}, +// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional +// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), +// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone +// is required. A proto3 JSON serializer should always use UTC (as indicated by +// "Z") when printing the Timestamp type and a proto3 JSON parser should be +// able to accept both UTC and other timezones (as indicated by an offset). +// +// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past +// 01:30 UTC on January 15, 2017. +// +// In JavaScript, one can convert a Date object to this format using the +// standard +// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) +// method. In Python, a standard `datetime.datetime` object can be converted +// to this format using +// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with +// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use +// the Joda Time's [`ISODateTimeFormat.dateTime()`]( +// http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime() +// ) to obtain a formatter capable of generating timestamps in this format. +// +message Timestamp { + // Represents seconds of UTC time since Unix epoch + // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + // 9999-12-31T23:59:59Z inclusive. + int64 seconds = 1; + + // Non-negative fractions of a second at nanosecond resolution. Negative + // second values with fractions must still have non-negative nanos values + // that count forward in time. Must be from 0 to 999,999,999 + // inclusive. + int32 nanos = 2; +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/type.proto b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/type.proto new file mode 100644 index 00000000..48cb11e7 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/type.proto @@ -0,0 +1,193 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +import "google/protobuf/any.proto"; +import "google/protobuf/source_context.proto"; + +option cc_enable_arenas = true; +option java_package = "com.google.protobuf"; +option java_outer_classname = "TypeProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option go_package = "google.golang.org/protobuf/types/known/typepb"; + +// A protocol buffer message type. +message Type { + // The fully qualified message name. + string name = 1; + // The list of fields. + repeated Field fields = 2; + // The list of types appearing in `oneof` definitions in this type. + repeated string oneofs = 3; + // The protocol buffer options. + repeated Option options = 4; + // The source context. + SourceContext source_context = 5; + // The source syntax. + Syntax syntax = 6; + // The source edition string, only valid when syntax is SYNTAX_EDITIONS. + string edition = 7; +} + +// A single field of a message type. +message Field { + // Basic field types. + enum Kind { + // Field type unknown. + TYPE_UNKNOWN = 0; + // Field type double. + TYPE_DOUBLE = 1; + // Field type float. + TYPE_FLOAT = 2; + // Field type int64. + TYPE_INT64 = 3; + // Field type uint64. + TYPE_UINT64 = 4; + // Field type int32. + TYPE_INT32 = 5; + // Field type fixed64. + TYPE_FIXED64 = 6; + // Field type fixed32. + TYPE_FIXED32 = 7; + // Field type bool. + TYPE_BOOL = 8; + // Field type string. + TYPE_STRING = 9; + // Field type group. Proto2 syntax only, and deprecated. + TYPE_GROUP = 10; + // Field type message. + TYPE_MESSAGE = 11; + // Field type bytes. + TYPE_BYTES = 12; + // Field type uint32. + TYPE_UINT32 = 13; + // Field type enum. + TYPE_ENUM = 14; + // Field type sfixed32. + TYPE_SFIXED32 = 15; + // Field type sfixed64. + TYPE_SFIXED64 = 16; + // Field type sint32. + TYPE_SINT32 = 17; + // Field type sint64. + TYPE_SINT64 = 18; + } + + // Whether a field is optional, required, or repeated. + enum Cardinality { + // For fields with unknown cardinality. + CARDINALITY_UNKNOWN = 0; + // For optional fields. + CARDINALITY_OPTIONAL = 1; + // For required fields. Proto2 syntax only. + CARDINALITY_REQUIRED = 2; + // For repeated fields. + CARDINALITY_REPEATED = 3; + } + + // The field type. + Kind kind = 1; + // The field cardinality. + Cardinality cardinality = 2; + // The field number. + int32 number = 3; + // The field name. + string name = 4; + // The field type URL, without the scheme, for message or enumeration + // types. Example: `"type.googleapis.com/google.protobuf.Timestamp"`. + string type_url = 6; + // The index of the field type in `Type.oneofs`, for message or enumeration + // types. The first type has index 1; zero means the type is not in the list. + int32 oneof_index = 7; + // Whether to use alternative packed wire representation. + bool packed = 8; + // The protocol buffer options. + repeated Option options = 9; + // The field JSON name. + string json_name = 10; + // The string value of the default value of this field. Proto2 syntax only. + string default_value = 11; +} + +// Enum type definition. +message Enum { + // Enum type name. + string name = 1; + // Enum value definitions. + repeated EnumValue enumvalue = 2; + // Protocol buffer options. + repeated Option options = 3; + // The source context. + SourceContext source_context = 4; + // The source syntax. + Syntax syntax = 5; + // The source edition string, only valid when syntax is SYNTAX_EDITIONS. + string edition = 6; +} + +// Enum value definition. +message EnumValue { + // Enum value name. + string name = 1; + // Enum value number. + int32 number = 2; + // Protocol buffer options. + repeated Option options = 3; +} + +// A protocol buffer option, which can be attached to a message, field, +// enumeration, etc. +message Option { + // The option's name. For protobuf built-in options (options defined in + // descriptor.proto), this is the short name. For example, `"map_entry"`. + // For custom options, it should be the fully-qualified name. For example, + // `"google.api.http"`. + string name = 1; + // The option's value packed in an Any message. If the value is a primitive, + // the corresponding wrapper type defined in google/protobuf/wrappers.proto + // should be used. If the value is an enum, it should be stored as an int32 + // value using the google.protobuf.Int32Value type. + Any value = 2; +} + +// The syntax in which a protocol buffer element is defined. +enum Syntax { + // Syntax `proto2`. + SYNTAX_PROTO2 = 0; + // Syntax `proto3`. + SYNTAX_PROTO3 = 1; + // Syntax `editions`. + SYNTAX_EDITIONS = 2; +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/wrappers.proto b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/wrappers.proto new file mode 100644 index 00000000..1959fa55 --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/google/protobuf/wrappers.proto @@ -0,0 +1,123 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Wrappers for primitive (non-message) types. These types are useful +// for embedding primitives in the `google.protobuf.Any` type and for places +// where we need to distinguish between the absence of a primitive +// typed field and its default value. +// +// These wrappers have no meaningful use within repeated fields as they lack +// the ability to detect presence on individual elements. +// These wrappers have no meaningful use within a map or a oneof since +// individual entries of a map or fields of a oneof can already detect presence. + +syntax = "proto3"; + +package google.protobuf; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/wrapperspb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "WrappersProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// Wrapper message for `double`. +// +// The JSON representation for `DoubleValue` is JSON number. +message DoubleValue { + // The double value. + double value = 1; +} + +// Wrapper message for `float`. +// +// The JSON representation for `FloatValue` is JSON number. +message FloatValue { + // The float value. + float value = 1; +} + +// Wrapper message for `int64`. +// +// The JSON representation for `Int64Value` is JSON string. +message Int64Value { + // The int64 value. + int64 value = 1; +} + +// Wrapper message for `uint64`. +// +// The JSON representation for `UInt64Value` is JSON string. +message UInt64Value { + // The uint64 value. + uint64 value = 1; +} + +// Wrapper message for `int32`. +// +// The JSON representation for `Int32Value` is JSON number. +message Int32Value { + // The int32 value. + int32 value = 1; +} + +// Wrapper message for `uint32`. +// +// The JSON representation for `UInt32Value` is JSON number. +message UInt32Value { + // The uint32 value. + uint32 value = 1; +} + +// Wrapper message for `bool`. +// +// The JSON representation for `BoolValue` is JSON `true` and `false`. +message BoolValue { + // The bool value. + bool value = 1; +} + +// Wrapper message for `string`. +// +// The JSON representation for `StringValue` is JSON string. +message StringValue { + // The string value. + string value = 1; +} + +// Wrapper message for `bytes`. +// +// The JSON representation for `BytesValue` is JSON string. +message BytesValue { + // The bytes value. + bytes value = 1; +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/google/type/decimal.proto b/openfeature-provider/android/confidence-provider/src/main/proto/google/type/decimal.proto new file mode 100644 index 00000000..beb18a5d --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/google/type/decimal.proto @@ -0,0 +1,95 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.type; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/type/decimal;decimal"; +option java_multiple_files = true; +option java_outer_classname = "DecimalProto"; +option java_package = "com.google.type"; +option objc_class_prefix = "GTP"; + +// A representation of a decimal value, such as 2.5. Clients may convert values +// into language-native decimal formats, such as Java's [BigDecimal][] or +// Python's [decimal.Decimal][]. +// +// [BigDecimal]: +// https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/math/BigDecimal.html +// [decimal.Decimal]: https://docs.python.org/3/library/decimal.html +message Decimal { + // The decimal value, as a string. + // + // The string representation consists of an optional sign, `+` (`U+002B`) + // or `-` (`U+002D`), followed by a sequence of zero or more decimal digits + // ("the integer"), optionally followed by a fraction, optionally followed + // by an exponent. + // + // The fraction consists of a decimal point followed by zero or more decimal + // digits. The string must contain at least one digit in either the integer + // or the fraction. The number formed by the sign, the integer and the + // fraction is referred to as the significand. + // + // The exponent consists of the character `e` (`U+0065`) or `E` (`U+0045`) + // followed by one or more decimal digits. + // + // Services **should** normalize decimal values before storing them by: + // + // - Removing an explicitly-provided `+` sign (`+2.5` -> `2.5`). + // - Replacing a zero-length integer value with `0` (`.5` -> `0.5`). + // - Coercing the exponent character to lower-case (`2.5E8` -> `2.5e8`). + // - Removing an explicitly-provided zero exponent (`2.5e0` -> `2.5`). + // + // Services **may** perform additional normalization based on its own needs + // and the internal decimal implementation selected, such as shifting the + // decimal point and exponent value together (example: `2.5e-1` <-> `0.25`). + // Additionally, services **may** preserve trailing zeroes in the fraction + // to indicate increased precision, but are not required to do so. + // + // Note that only the `.` character is supported to divide the integer + // and the fraction; `,` **should not** be supported regardless of locale. + // Additionally, thousand separators **should not** be supported. If a + // service does support them, values **must** be normalized. + // + // The ENBF grammar is: + // + // DecimalString = + // [Sign] Significand [Exponent]; + // + // Sign = '+' | '-'; + // + // Significand = + // Digits ['.'] [Digits] | [Digits] '.' Digits; + // + // Exponent = ('e' | 'E') [Sign] Digits; + // + // Digits = { '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' }; + // + // Services **should** clearly document the range of supported values, the + // maximum supported precision (total number of digits), and, if applicable, + // the scale (number of digits after the decimal point), as well as how it + // behaves when receiving out-of-bounds values. + // + // Services **may** choose to accept values passed as input even when the + // value has a higher precision or scale than the service supports, and + // **should** round the value to fit the supported scale. Alternatively, the + // service **may** error with `400 Bad Request` (`INVALID_ARGUMENT` in gRPC) + // if precision would be lost. + // + // Services **should** error with `400 Bad Request` (`INVALID_ARGUMENT` in + // gRPC) if the service receives a value outside of the supported range. + string value = 1; +} diff --git a/openfeature-provider/android/confidence-provider/src/main/proto/messages.proto b/openfeature-provider/android/confidence-provider/src/main/proto/messages.proto new file mode 100644 index 00000000..dc4c169f --- /dev/null +++ b/openfeature-provider/android/confidence-provider/src/main/proto/messages.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +package rust_guest; + +import "google/protobuf/struct.proto"; + +option java_package = "rust_guest"; +option java_multiple_files = true; + +message Void {} + +message SetResolverStateRequest { + bytes state = 1; + string account_id = 2; +} + +message ResolveSimpleRequest { + string client_secret = 1; + google.protobuf.Struct evaluation_context = 2; + string name = 3; +} + + +message Request { + bytes data = 1; +} + +message Response { + oneof result { + bytes data = 1; + string error = 2; + } +} + +message LogMessage { + string message = 1; +} diff --git a/openfeature-provider/android/confidence-provider/src/main/resources/wasm/confidence_resolver.wasm b/openfeature-provider/android/confidence-provider/src/main/resources/wasm/confidence_resolver.wasm new file mode 100755 index 0000000000000000000000000000000000000000..fa0b2bc1cddff29324f9e819738e1c7e5d528af8 GIT binary patch literal 451028 zcmeFa4V+zddGEX4_S@{4WF?tENK##Ulh8>rBSB)43s|$J0-A9Zs;d8H_OKhRV zfhvU5(sQ_EfPk@0HEPtf#hO+u*n&+hR&1$`8Z;`Ni!EAgX&X;t#Tqo$v`v*G_xpRE z|Jr-cWI*i8dHLKjWUsaU>;LlnpSS<>JpY#!z3RrFj-x1we?J-DmhI%fXj{y`DBig( zwWnm~&T!9#2fgR6ro22PJ8!98-{KYU7)9Z|6}g36w~)1NMYzdsxrIt^+1Bs`Qd;3o zHKx4fCBUdqafE2@A5U6lo}D+v7xTs2;KFZ422 zwE+~`h0C0-+uLLc3ipR z)jxf8)JWgb+}o_z>nZ;=8jXzq<91eWb6@9Q_^)2i8ga&7BOXo~jVK;z*P3y=R*M_; z1+{usZ`2n?sv=T>ETfv3y0Uyx8pqsa^{AfLn@xV|aXm??zLrv~M%Kk?9A))177%fo z)$?>ov$41tWm#&8^*E-I`Y`XgsJ^%!C23l3CIG`zG#pWQQKQyg5;v)$nT-PPlE`v4 zNQ-GFU94K-tX5B{hNLtbjkBevWtph)Tjji~pHC3&;nNS7tcTEl0gpuvkb>dQfh{;z>2Lmemqe>5GX zNgl08htlB@(A=hz_7{WP1`R+b>c|9CEsMdVz(|Lq9N?N!vteLE4}&)4mwzz?7b&up zJjYP2kvE%Zyyod;G zBlV7U_%Dy+l_|W_T5O1i<`DJ=Uhu+tT66$-s0(I6)DS)50&RrUl)R0Vi|c2mQ9W9j z3q24Vl7o<>-fT3Rc?8sCgDX-fSgSYM^$4^^Nz{(BW|q)d3d19mYPZQxb_&Gwrv^V7 z8yTVbsmE%kE)-FYyCl*-Zb4SUKVHOfB3|?V9L9EBLjc+!FNS#h#)yUftJT_VxUn4? zskgJ*NUdE@fxB5>$zR$eZ+$WFNeZ%|-v9A}ti~UmOB+j&zgoNsy0laJD3s6vUNuv3 zQUu|edA2%@M+)AhX-?O&<&=)k%3;7COtWE_qDdk|g-Np7*qXHDJ#$VCvopQw#v5P#Q?H3WkS^$Dd)0NiR2x{8cwzohGe2lQ=5l_N{SwIQj8+#BT?gUyJuN?#$lM{7!OvI-P#1 z@yW*1@sZ@#^mz8&;u{FWlv?_$!@RhtL?8nRC~1c_1f3#$LrIL z+Z%T_-q1K!d#d)$+Tq5ZH2%Et7mZ_$ryAdA9BqbVRRQWfJS-(?Uu_(ePsy`)pK<{YGYnU(9y zy>ESZI?A6^&NAvWcpq&{j>G_(j3vWefPNx8G}YI~c_<%`raCR*BiT~ai}c@(s27jN zQ)N_GrTJH_VJbKhtI9Fz=rq;4I(=>zjiNr)87eYB2ffXrQLevks?($r0Pkd6_j6VM z_i!Bs>q&Vuo}yzrLI-ae=wDKvi1e z8$8Em$TRrrZeEF|Gwj^Bg44WfGT-$@QM~iC$Lj2D2U}>s2Mvm%rQju6!t- z%HNCpK`8P1u*=j%#;0Ny(sKQb1`&ZnuM(SH)2ji{9*d^nCSwEjt_fK}tFZK%WJix; zs%h1;G9J~^kFn(PUfqvSaZ?pn^}$AS>l-7-qb|-(K|;%YSV8Mpvbk5^hVNb7O2Jt4 zYk;JBuJE<;d(`l3QbHv#!B{fht7$CYtm;}HRRA#FO(iu(Z-m7u&duRD{ew5gl9%`D zoB>}F@4zS~=VG(8LR%YoJc>Gv3RMp2j+#1^0y*AOpr4Nu424_Hlt@Hn(bw`xnHfi9 z!co(4RNhLin>uOWpKj&}a8M$pmklzM>)%0slBP2_fH^LQr@ArF_c%6QlP~~Cy{}0| zrIBIAPKxGNLME=Fe-fbc8a#6_PEJW*}l{;jIQbD zq<`&l-`^_tokDEXUMTpfKR@((d;VU9cMTSPb)4*E7>fCqs8>K7_2hSIK@wv?D`hl) zgIen(He`ap{OA2yedIj=YSfMLUr@4fMkoPk0B)+iLSTZX?LSq%@W!%rUHK_wy#BhW zGM;KL_c!}Gs_G8$kJJsN_POQsVC()&VX-74jr5fjI zvK{~~hBjchi1J@Fh*N<8)AHrY z#dWJ)u`w8=_{sMwi;0@Bpg~t$mT9mq_w5XY_S;P_uj<=>%M_s5727aPkeAqcmV3{d z>KXP+Crx3xBE8%gBAwiMbCKS@DHUb_=Re(3rmwrUJQ-i7+LSnRy>(73oxsK2%y}-M zs>z*qOitf=+pgVv56n#OyuF*Mnhe55?e-Sq?Q&V*Vc1&MRRNktxiHjVPfgNQ|8M6e z7}=jSywOzqBCA079YzL~?IsNZPzjO;&ov#Z$-FL>#a>i}d1=q>RN%2|8#&J(9DOMQwX+oirUbeDMgXQA7I` zpc^!8^~+|ztn!OcD23N$guUGFGjU)TbhkL7FA$SkU*U-#3B04PNLdS}D z#m3|n+yn5%s_W&t-zYP{UPruM&wUhL7ycS5Vn284bY0xRPd=5u1!5sxt{kA56$!GnLvd_rLjJRL#4^{_xkmgHMo>B6R~^ zz$KlAjWig%$&M)lrAEi={AQt^qz>ImwO!2DHhctQVU13;{|{kNVqkm|%tdK5Jr7zqpavh8#94-A zRIakbF})f0;_d%)J|Zk2NSp9qs^J`Y&3{Q)*Jz=KU)DqS+3=5fk~j^{BuoCs&zZUX zlF0i{eK2Bg@cm=6ik_{B_B}+*{;A}BRzg6GY!)$UeO9RjR>=aZjEP+1&qXebRw#B} z54$Q5m5Ii;D1Ya3BE_-y%URs22BEz~6k?Awh@tyzOVtGS8nhCQ*H%|f zNTH3y_@kTs#d?1MFH|oIf3eJ8z)sbRX1LX`eYwBAf(x~8Gc-t#>1KTw#iEDB)hmul}y{S#uyaJ&7S8b&$7dFT+b8luJhrRv}4g!{_EOi95nR zpn`Bk7*gse>26barXe3lI<3CvNnX@q(SiiZ6i8YZNu{-$Q~BL$gV$O99z!N^0R=<4 zbqQeoRCQA+b(Q@Ml~5D>%cv4OjOu4ux6$XL#vDE>lmqAV)Esruac|`w73gM0-l+n% zs!z3NWYW?O-K^BxDo6Z0+-;5p&8%0B4f82LQ$#`~ri(J2%2l;WMcTj!zl{c+fF`G0 zP-2@!8H}r7?N^$sW?-jNWg_;Nh%Q($)jpHE{L{CAvS(E!?bUOH2L!u;80EiaC7362^&VO0_;=a2p z!}j}`RFoiuF@Br$!#1-vf=UQhC9-7RgNeCh-@HRHYj8D;rFlUHb3M3@4w|FNxKasR z3%kbQIritl8plFLi?~sfKgS+{8_c*NWZWET^yiqs^&A*yJk}JIAt-~-<|%PV8K{+5 zR@b89mkSXfz-emcJt2P8ttA zg7T0Om!9J25u+xqdcN@73gfLYy2n(ZezFkj;+A-i!Wrh6>UNoaR`#D=4fmg2>Gq#5 zQ2s6b=gYdWQZCQmAl}w3BX<5Jl7|ChB9y<$Qd9JwW>om&|?IN9D&W<^YlAzv)240%9>eDQ{B7Xhr^dYw|z+tJ&Gh+`lkXU?Yct zq#CKP*#02t`~K*z*cDBFzW`3Ql=SWH_dF~k0Sn`OO5ErP43PeM=GS@MP4eH?ON*!? zT$#}=QE_`O{HC4Eoz>JL%}iVoY`myjGji255tD!xbNEUtlpj=;vbGGb0TR@3RV%Ij zj>9P`$=e|if;9t8%v+`*wUAm1yvGA2qdAGRln2hps#dLi)ia#mV=X5d>v7$vvhV5v zO+FRW@dbvWiReIuqB1_uAF#qIO!@7-%Hp^8>9*Z|OTsjt`f`_^J5;5=bV~u-#lXp% z4P|1SIhI@?2@uQ3q!HUEV)G4x&67$Jl_G9M5(voSLDC9}lmGMIyIs^#&8lW&f?Ud= z-N1$DFXsIuJ)T~_DJ{4quWpg`>K0eAx+OYW-O@I-jU$ii`~8U31BF>O3*27131}>! zt-F$zi#w2~gS%|rw+ZO$dVWrzcKN{X&rC=8mz*A|%@C;=0w{tpz>ftmQ3DEYBFhSb z$yaaOfPJ3qAiRg*mh#DQ!fmt#u$rk6s|(NkN+e!&8>^Q`KHh6m+KT9s=r)!Z-541Z zaI*VC$V|3(34;_sBTG8YKPgNPkY6fziuwWV_^gTvAFL9$6`fRU#(U~5xU919EER$t zk-z4KLt5T^gY^ZpnWM3uH^!YI5CM}$?GKBq&J-~j>HjJ6JS}dVCC@;TsQ>mxnj}TQ zG^5g0&kX^7lFmnfWfUvDg)*HCPOY^y^y|XKqwDoFN`sb8&Y8?f+72 z=hT846FFH;W*Srh1Nj5iP&|xBqalg;m^A-@(mZMymp^~&48&Pd{`D_>lxr>jJ-r;R zTn=$jlz&iZl!hf3f+ecfg5nX@$b(gd@ItS`-?oYuhYDd0wZBsBf2hhX0#xn4)3T3* z?2HM@j-6$+&40ftB5hL@`AsWQfo$X7m<8XYamIYC5zE%|KkyEiT2cIens_+Ifj#4u zkIBJG@`t<;O4KlnoT-xh50%P$Vs%9s!*RM_la6(5@}98p{r`~?nWBwoBdo_xiwiN^ zAxO};=h{v?-JJe9DR_@Jg!F_^?Hzx#G7 zk+EcQ)_{tcBBoI&eMypCf%`a{$~EbcUOkLteYB1w z>aE=T`aSu_MKYMFh8uTE&tVkkuuAhd*#INd+J|DShrk4k=xN0RCF(l=h;e}pgFaTI zm0VjP|Yk{W^#RN%IN@mysnV`BZNIaQK3rucNRhNArInwEz+s<@_^7J`om;INdB zS_JX3z3Z|l84$^1khGUY>L1l0c!h?={LdvO?e?cnH@P{jLfLdOAU6Xz(>Pm6k?uh{ zYr2feH76hqK#>_tN*jWL)`l?-#{h{6Vd~LFq99ypd5aB5kosCiB&(3&pHazgv+W(x zmLR86hr=zYs3y2si3@DtvZ~Xf#R3Po1u%=2WM%f?6%7-ub)W;heP}g18{piOZXQ$& z)%|SAR?laP_Dnpokp9mr+tKi%AlnEv<5Ucg7(U{^(ITt(PyK^AwRVtI1jyYXV^z_s zWFB>x*_CI3-Uh%^v5Zv#UQ_j;k0p@f%Runk>~f03beDN$YJrB-!eVb>k+o3dR=XQQ7mYN%*Jb;X5oUG=Xa+(FB$l;H zOzIYvHTs=O`pW;T>DfdITHYWF6+tfX#f`~>x}_fvNJENkG9fnqgh;0>M`FrzJsN{G z6z0Zcze)i5K3%DdWs+HW!b+kSxMy*qNSc4Ls^etbIYV`vh&#)5eLC)}Aj@%E16Z_0 zaV_AY9ZSRAXroDHrNL2c*b3rEhh|0F-d?ZTW>}b|>yt6$<$5gcoG!Yj<>h)k<-dpQ6l~}Kr{m;|uLHelAaIR)tY6Tj3O?tgn?TmonE(XjoQB@K7;a$+CD0I6{uu zGQKz7l+K8o!0$0#!S9p0!evKwh3H3O*8->#6kNdok0!_uS1g8_Dafe-4Iq&smC;}; ztuD46DwY+e=buu3SyINySE(W`(@;3%Iv%7Py2$o5#p%UDmIftTid6bVlEwfn?Gm&j zPOl5u=?EDs0T7O8P{@GXgpA5e!T@THgnhaK9SR!%O2S-U!MCTww<`ta%I9AZOluV( z01J!LrFRW;PX|n%)D<{Ra2=4>;bK|-8G~ifpeQaLl)=&G5I*!x;5qDtx%y(ZR1WYY znlf>Pe!pPoHVN-)Ty;RSPDrD%BM zU%5$H92%!#?4BRMu-h}E78vmS465J5rfyA9n)*G2Iu>sb^4iAbjE%`Fxh&t9Tp=-%j@W9!5_Yk8eNkt$fLqmS&<`3XWkUNz zNToe&jif0FD#fkQV38I}s@G=XYPGB_Ms$kJSc@_~gZ;s_usKeT7O9D6L5O%oMhRx8 zOcWwXvSM1DsN4jLL zIq&9s)-&G~r1Z%hc|B%gNh_^M<%0lg)=kWSHv-_@6t#Q0Vh;XYvLgQt$pMLA&9V+A zr(5PEcL`ToT$U!W={|Ow$`T!Qr%y$%rF{kDXZwz#tH`hYC}!-7#X6DXxU3$`?*4YEcefLf%$+%MImgE@mtq zDz|L)x`xVaHx}c(|1|{J;f0 z{9LBM(j#)S%dhyn$LWhksDof81ZT_jU3s0X9L95QNp zPI%P5iuup^A_=fqh$NU-4A`@Ndiq10;NzRLgXiQdxN0Mx58PmU7^&6LW3z1s+8&4*i zG_PWTO;RrC-!eDG!+O+bVRu8^ZMM5McPtRIm4(t=AJf=o&tbfR47LOzK$Wz=k|i0- z(SgQ=5)0>~g|Ze@kv~zzttc+pLK7urCWr*-6!!fQm_I3pE`^53#uToOXi74jW+Lem zQXyz};t%;JO`U*kuQ9uS@|lb8n+2l&Hh8uyiMg!Lhc31 zZQ1>G42$wQW$sL(u+(1by}~mTmxC;mW#`oWMDfU(c~@jA20&_a0r8rp9LQgPmWCCa z<8>pv?l-I!X}DgpEJz(vF7&teC7p;`l!%2yru=pQUe;gyrhe4N%nC-?VK>ZHIt z11SwskmZ4trXU6Uxl~pZwtaGrU?dd_7TEo>rY4-&2tZAf6Y)I@>NYb~N6pB#O+2j1 zZl~{%gc{lk5+ zG|k~Lk|H697W?u>V;2$7^d~Bll}ftzDF}i1fuI<;M^ir`UDbXm_1hMalX^t&%A|l7 zB&xpsBnDst1?XKZ^`hHVu(pWX-_Rlu>EmWu-$8ra;R=$ilcifs=CE#}Nv7BGhgCm; z(_hu~{we`Hrp#LlWG%X~wM*T4ZWu*{q{t1;r6_RItFSo4%8_ccQx7oR=*zCAc zYUX-Yb?2Y#)x;>`?{GeNad_axW()I|T4@sJpRyMP8sh>b$E8g|Wucp=?V zfZJHDPfo|L5aEmH9u)ZY{G+DywN*nTnQZJf16Hacy2oF(+p8l<8T5GAGvBXU!3oUb z4zoksPbaLO4FkVM*EA>*6-`3X*q5i?uLQ54yn`#@TMzrg(NrlGsdy6?=mOHc*cN@rnNRIq-s>{|&Yc`fGZiUK(rN~WDS`#Kw{u#?xYs1)8r)x!X zs)qtETw-ET-0C2oQ&%;BJfuATBh_M?U99nUct-{l*_Swv;(KG?HV*WfrX#)?#n2$^ z{i|XAElzD=p7>C}JXi(unpbhmn_n;o^H8UUd5WPEeg9Gq^D-vq!+e}SrZ$=CdJfD} z_*pSOxwuELINz_jdWe&@(qGV9(wqw#5l7{4>Pf^%Xxv0;QxgpBgH>r3$u@iRY7^R7 zLqH`Lj?@N#jdR<@GJ*4cnsm`9^vz-eL*B`ckPPIM1hxHcUo zsfJ5Q58f}b$b;LiykYD!G0{N_-ctMhr!MSg%aS7h8iI_GP;Q+XEo46AF!T0odlM=J zD?@dvTayoj1WT#AO3!*KoytCI9BAf8=DEU_qWV6&rfh z#)3MOhux9Zv;6;6^LUkv5&(#k4tnxOmDm-*=G*8sE9>XJM^+41Osu#)8VLeK$#ygO zMuqlLn&3}cNT>2IswTSDUge-OCh1)tS8-6BGGnoeY@eAj*{TXGOK8I`Rf7i(QOxfY z=rw+6S)24Tq#^1JD>I}{wf|5%6$VD8IItk2GrR^S-56mIjSW%Q`hae_%7!2GjyrLT zJNS1#{E$_{4;wtU1 z31k9>1=fwe3M6j?RvPfGG6kf$u_IIm<;d%_r~V3RSNH&|%|r9vZX3$F$j|aY#fxWx z3|D~9$~G)#9`wiAzYCZmUobt@z5F*cXvs$vZe8IC531c;W*u8^|1rHGdK5Mzw?Ch- zZ;Q@@6xlOYfs^Vv_T~hMoF|7%Bz4uit})fcNez8|thO;$3+7nE$|9cma`;{y>Z*HP z2HeZ%-+kmcwsjlob)&bVhr-afBRhgtuF%gbyRAMjt)5k(SC{GM^88OE3_6lVk3p@> zv&)jeb1c&PexW++ai*|7iWY~lO!Z}yvuN_qnoWVYyff}CkgbqCutH1|viTz>iL$Cr z5km!%s@+jxtuz3-nT7uuMEO^&0?aNg!#bsoP7JHEWnq>mo7@B5PPBu1U1;^1s~pAv zWSBzNmcy^1S4lV|Lnd?vbm zvN{?dzNG{80N&U`X96{dJFX$HGg7qg=r%b=fI`pwx9|MJH~#V?&)jnJ?SrQZbb{Yb zspjR~1=w6P%uW!ZwRTtmH5IKJx(lz>K}6lWe1%~tFR#$mH4kwZ+PJxB-p)xz(xxND z$YgxytY$8^rEAoxn!U1O!HV+oDSqiW`kX07S=2bQEqZD7)X|3JJ8zqrj>^;Bw=|5I zSfJNdO1Txm+-`^hVFt@l$;(yHM0rrz%CEDA>OQ+kOAF|LLRffrvLW!me5p@nt|@CQ z;EIODY-}Ky$v^#hYfiy-TCWIyD* z02~a0QUD5V&^u;Gpd}QbztDCY$wgyO!|DhF6(73X%z;S z$!~r7bDz0q&llf+BDzDX>y`A>U%dHSZ-3;84<1z#)64~B3SB0@bo6cC`ogg%mi%p@$nmweGGi5L%wYFCwR zr>ZtJrMf_t=}>vG-e0|e4b(Sv1k;E?-Qr<1AQd{pIRxH-j>Iw8Y)i9)My)`s7Sk<| z+osc*o1nL=7%do_|5lMFm0Bfeto%5DlQ8D9O|r(S(-Gv3r7ow)rWVv#^L6S*QloCe z9}zHYG%~kAhb$eLiy<{#j|5gPe8UZnFm%9f~ zZzxaheHi`=6VX8OEM23|{XQv(?;PHKdQtxzx`RVKo}-$=w8LLY>Un~(3QNq7D5aB> z0ByUBU@O!N?6c9IpU2iG=ysFlgEV*6g%v0Yk*GvR9;ssA(Nic-{%KcFt_ zq(^n*5-KR=hy;p#ff-QB4vx#mTHHQCvT$@jm1>o_3ZA^UP+Tg1G78ChWMdk2%cosF=m7X0(=wD7#+N*JETn`U~Nb; zA|a;NP2ZCx5v&(2B1zKgiXc#srXOputVHXiaU~c!WC1ybgbavSG&G->*wKC3rd!lI zEoy*`b*tG@pf$8LOIE9^UO;4{pg<{F)X9Uo9Z2hQ?f#}|^9@A1Y2S${ZNL(C?X;>% zrh81bXK1QCB+@#D4;z(_I)cI zJrC;HfM5-g(qMjm9{{MY2q-3U)U$^7__0>c_9#~gQX&=db;?zP2U8009CC3P)m*ll z3iL;nm%&yr!F*J$1JxtCLYl*Z;3Sz?rDs5J10GFmES+K5(|fD>fPM{-Z%R*sQs;QG z2*p;D_rg(G!5-5YgYGc$A|^acpOHcJBb&B`}sTBaDT?VvN-a<2Kx2+=0ncTj@RuyIFI+~HmborXKurNhl!?qI=VrfFLiJ|GRgj{l%W zA*bvEgdv`Ca;o$}*sQIhuzb=y@>#M=8!oDU53#b~dk?8g~P%nYZoHRzj7 z>F!q1*6xZ(`l8?h>d`6E7VLDDc%oV)tpq3JX=tL1SjaFt_B zAs`6L^Rze3fpjUCvXXHqv0?yDrJ<=fy>UfDfPwT4ffTWo z{C!DEYzKcg7~M=&#%>sqkyFb~-fAN^{;kv%9xhIqb|mKi=m7;7IRCcaYP>PS5!0VM zxM#Z(t^hdAwcs2)mOz4~P3du6&2KXg0KW{kXuvUhj+XZ`$x48Wl9Of*a9GCynMGmU$kv zUp}i?7K*_Tk^>-8G41my_Z|ioZm~rCRxy)LcqI?9;Gln|>8$2y>Cbo361Zd}L^ayZ zY-HiOuH2_oM;=*v{wKmKD9EDzhn=}mLF58{3-pa8hia;?e(aZg#a44I!gsD-IJ|88d_Rph*jNxrTksM z0!Ax?)k$Tt@D>+K^RF9V>VUu09*@n%V$~J8py6V(!a%@gjT7Ng7xQnZXun14NoY-` zg=#;l#`WOUo8=H%5*ld*YaO)N3u|v{cQKJ#_O(EtX0iC%?jp9qG+Cy38cxLGsm>B2 za*M^}!ZkU_bLk36`6!OY;vTux-&W@|^)f9+*-K?Z1a!xoE{fB7q|XW?C(LJS6=_Vv z2n!#!`v{VkasV8ewxlK_k(F4jw2yDuA&XlpZ_S440Li?|?| z;8~z`Jj%yTh5{p7`M+tX94^}VUwcUssnIie)e<{6WKD8CF)n#>i`DDJ>^UM3F$#k5 z%ofoOPfLVOr`YTU)RtGvwz%Fp6{~f#Y6UtP(f%gMxaYJIF4bClB>WNUpn>v%f1H6k zLx`agjB$*3wilhbDrVYd$Ja|s)WpB?ip<;xx^gy~mN40D72t`gatm_WP zw1cdqTVy|MEk=G6UbRCQwssrE0_VfdVj3SQ8qSL&@Zyp_pA`#aVJraW^kZ?cB(R!b z234c5?~;nyiZiz3$O(xst&T%F-IVpH?J($~LBw;A6|g0bH{HOdv_)l~{{~nOYHx@8 zd;%?ZQkLQ+CMss}9$UN?m8n$>(&5Gg9&=^1$dJJPxzYS9#&nemARwW92%N}}239?V z8;aAEG{jHH))HOOQi_@_y4DnyGgEp!G-cD+&9sQdGcx3Vq&5xnE(xYzo~-2NrtTuC z4J&pXC3JG%SM|~~h2{MldqlQ|i_^96*u@LJJT%Cc9>Pe0Qrpf%@+Dj}TA)G$=|@pT z!n4#=Uz^7R+T2*8(&ncJ1m@zDIoh1zpZEy$k3 z`$q*OI+%oIem=(}Z1PUNhuNq%bHS>brZSoL*Kxs3yM|JDP8{i6-gPTxsGSt8_1>=} z1wfR-7Q^2C9u-e?ejnR&?DolsTlQQk`DxwnR(2ps^mzkc_bu3Xps#p;UpDU#8+T)J z6$FAG_9orf296lHRAfxM`3;Hg^pjvdo(09g?G|y5AvQ(x*=&ln^|v;Z(&_a=8FSne z=Q2ipGB%>h6|%9|ka81X263RA_ppCbisli*ElQlvKaC^2#m)IJl>P7=Ulq@Mc6c zmx*rnKl?JxXJ2%(X-$UloIiW_EzgfsSMA$WLJ9}z>M zzbx0VYczmTEj=IUmCw=)y+Z-1Au9T?DoVCkV>r8Vv3+ZqwNxH|+ryY+Rciy4|4?G* z`^Sn@G`k7Y+wR&Wld)VoWC=_|m7M}AE!hVY(Q2B1ON=_-{tD{}uf=1N`6oHwO2A^WUU!&AIIb#PG!b&42sn_-`K@%If%U z13~VYxjr0r4~EMBU@s2lm#3D03EN9n@x3gf)Xu*Ai)ICyA??ZepDLwn+C;3pnUsGG zI)UlAH+COHc9wiPe23;K-h&K%i>wg0s?2`kEb=F0Ynp97j4#Q@m3Ffb&aFwD2fM>; zZ0atN8-YVp`FcFb*TV~wk#6vF({mz}ZuT_&QsX|+XSVabv~fAE1Gc;DTwLhjtIEBz zz6!41smO94aA(JoYutUnE5Wj4Vt3GexK6W%IBwcvPB%1NL^#O+V|f4$gh!u-943+s zAJ#8bX3qBY*sZt_wnN0)lSfkdnXJ9&Ihq`{!AgvF&)wY5YHV{iwHSWL)|n^>$>gNW z;i00s&A*l^uzou|Mk!98^RVP|z}1u|t2JNdXv*Kj50Jk(1bNgx z6`tyI!UCkr>HM?6OoCOYrIFc8Bn~R(_@B?gLFPu zfv{Pw>5%bOG0Qa_R%DQmZU9_hF0l#_z)8KsfHbMTjzSJz~A#! zXL_FM8o7pI-oA2)ia58`YTDxB$%>@CD`h#UmVQnr&|UM^?6!QHJC`8ewh6{oRX6p} zxlLeiL6_~+l7I5B&XI5_4cbz@2CFvFWevr{e4RgF@M!U_-kN6g=66kRHp&C=YuM+2 zNkP1u<-rP0*d9(Qhh+J?Ot@;5Xy>=tGf)#O#IeqAuby{j)(eUEf2VHjvZnd+EZX~f zb>qOZbwg9?tglI`j+FbD%l!j|7uuK2iUuUBJm`>=CqFzR(*q$bxM*Bp@OV^iKetRA z`)yo4t$fww@g@CJE@!f9yHTi3vpf8s`$Eo0>PIUMNI4=H45c&KRLCuvXg}DFhpH73 z1G`<5ErIcLh<5HD9f57NR)-7095KnLeSDKUcA7j$N-kb<;&?eOzZzvCMv`2BwL(ppA zGW}eh|DB34rStc8O;JrWoSq#`oF5ayAz&J Sq_f&|W5k`$L`Ee$ zW%x2&bUqW5I&@fM6b0EP;cHi#UC^9C4`JH}IkwNIP>kiuLJ!HVUwpW?V4Ts37ii(O zZ?3RNHM`%HsIO=1s?Ph>)((VS7@#xJZWwLuzPzUw%F|^<5XU@Shw<2q31`lD(n4(# z*%Vo)*Fs8$x)D!?G#Uii~WEu;mD{Xda5 zT2N0Sw$~29Dh$OU1gXPR!3EiJsKJ5(w-WT zttyE&$-gtF+=#6@6Ig|@IqQGfhNHeQ-iKDg2^HiBjLa%u)ejs7t~VnB8h|K+{OP&P z*eL4Fgi0i{r&QAG<-C<>DjG-;K{yfK(zD z9?j~@^Y1FB&ccpLlLbsk?!;#loQPKHfQ@j3Q2F);W~Oz*&@(C%B#Bvhhz#%cO6g1v zsLV#lX$^f&;-bBvR;ZG#}Qoi`9v-LF4AlHpg=b*qaUF}wDsEd#SA_mQm1fC zCO;+PFdBqPg zBSV3K$RmJP^4jMyqzH9jH(iAdLf7DPct^^N!A1FT1Ekitx9tF=~4ree*8tr#3P8+r_ zF|5L8%N(V(-S8(_bM;;^`{sSm1qWPuUm@j41 zuALS$yma2w(RovsfQpe3&THhNy-S98-){kbgN1oZF`*t$M6BpG-2h9vHuDI=0RdC( zITK%ABZ}B^o^61i?LeQWm2iBU{L^(TT-+I zNCV%M3uoe;7Zr3JKpZIt4Udm&ZV9wquklrWV&#fZD{N5L*Gc9l(g?&1bkSuPQF_B8 z&L?lxaDm7pKl;Ql`Be8Vdt2*SN$3{)0I}Z92pBlbN)j>QT`aC~av*NjginbE>rK=B z8suyaRV0TME~lR;0$W=yT?kdsg>pdAg?d8jCm$2DXB=(b!v?XfZwn844NVZ^zy`Ja zle!kz3uy%o1PuoncqBT~GZu5E!>G`pK3#?F56Z?oFJb1gf zxnD`SpLj@G`j_i$s#4r?L6;@ykag%D4e?ktf^%P--=(Fq&Pn?Ty-4$<8V6qKoxZ}L zNre@()$>9KdR=|b|Iwj!jO18Ym1^w26%4}?F%dz7xbYT?56k1mT5t2wVItb5C8?%0 zukq|xnTSHKR|rppFPw5l@X+;_LNS(s+%75! z5S=GCJ$)$BGAEEEK~MAVs4t+rVkefWJzN#oOhyA76J-`fF@Y*3C{Jj;NVzCK?9AXu z*=#hFIBACe4rAa^&*I1un;^0VL~r6=s!G#X+gud_@*umF(_)9JC9j4qSS?;1n!DnP zbQ?U}tH#06vAF{0%~sDWaCkL8I4R)lH7Ny}y|HzRMd9-zMCk^N*ER^v-dIdJEhgo- z8%JJRQouQ1)X_RoUYr0i_s-?}+1LtES;v;@-PYrMDbMcEWU>1mm#TTdvA#hE)1sC6V zh>>WvvS3ia6~%pB?E?kwYo|X}@adB>r43HX4rA9KTYitS6f%F@z8s+MQ5*Bk9=_2L zS|Y*zEtxky!opKy=n5%DKCe_z?8##@io$S2*W>|G7i?1J3?fWO?&Ec7(`wJZBQdu= zW8Dl1nFW4I(k}NtMHS^Ha*4Ln_HGT8JhXn|v{e zj{D07_g}#NsJ8~N`dVKziOO7GiU;0&%l;-*7*>gCkx&-_JC$E zVwrBk9b1mZVl(Z*LdTfB#&CmLDeH>lfD1yUZ3anfbULpp2mth5X0@sjTZ46~_rx>t z;_Li`@f&B>Ugjd%802s`7ILCOmA%R-_&EQ7D7LB@aJoI_JH{X{Xo?}(0Wyyj3?5UQ zpk1<|X+(1j_JDRD4wVM$b6{e@P~9vr864xRnG@hMl_}ajO&ksp^65LEpEh|zim3hN znDqXiOgBu2C`6K|4|X#o*yqe-WAvUynqGh8>3lzVW#Wji`WX(Zr6L^~X{QyHb|fSf z;+8l+iR)o0H8X!v{8%rlipdgUC5Wq-?gD*=kC?-Cips$gNtbQxQfGMn@-d0c2`vn5 zj#INI2}`7$Ce;31oNA|4e9L5_;?WTU1udYTvW!qo0;lF zU}$E0$H@NL%Jr4&BS)Hk47k2@!CYS|k?Whl4=5hLvoo|c}Q`!I`$wwQe&0$S)@(_q1LHMQ29(OhCi3f zed}ma%A6R|2itP}EVFl<-J+abzNDh|I!xioxFE>Ki7q2Wd!7?r7AOl}BLH7{x`h?z zBa(EM#xJ;|+t!IL?PopF#RhT8v^jKznYV>S+fQ;CL0oUhkC}qS+0>CO?c4E;H7mm= zj#G|o(PGgN=IPNDl5`-&gb?u=wIBN6%ru|5n;pu*sB4(jh8r3ri0>LJC4Wk-qFBr* z!+XLgb4-FuP_w=};+l`}v}RvfAF1V}C?X~wxg-QOb%xe9?i=M{t(Lupk|Q3DY$z`A zQjNF`ou*Q?`E_q|B6=xWxxrDJiZ&2P*F_9r@hZ5}i^EQ$30S{Qf(Y%dCoAJWyvW9c zS){wF3sNDSuz2cWJVv^0CN~NkG9r9`q!VHS`RJoudPJ}yVlq`Myy05(1`52qjv!V9 z7OvMdgn`M~qpU5e=2ch2me@(7lg%&zx)Ea)7l=_fnF#gO%3m?_G|u1b=#v9*FD{@c za(4_n3cZZw2V#J8;=aXL3i8rQNcx5s*5F4=GU*A`8RdkJ^03)!X40EDL7lDc4~c>$ z8^B4qRO3&hd>9kVN|HR`&M8cAc67=4F#vEEUv_@~Rqm1$%Qfh8lHIZ$MxKIkkaz22$|>MEqqUUqEBc~djGEPIkT$0Xl6Mn%)YLbY zY!R0(6D!UqQNCfyz}%wG4;oS#Z?%#fHZPC&p=Blc0JN{*CBwnIfEVWPs5fJw2OOz8 zrw6pbs_tr9@$7h|Q-x*CdNJBv=>3!JVI?I)XU8unb0*xk=lexWs6E0_BhF3f>$Ht- z+hn$Ta%R^{Z@Z`4{C5;Zq(m}tq{6GHM?QR8pguS(F-P^@0*)3R$+E`9h0tKtyaBiFi+xBu$ls^MGxe81CWcjqb%Jwwp zsqBnzK2tTYgS&SOri7&WD*%MLY!*vldqzs*4O`coIw2K%OD*=6G6SbBn>DU#_3=~t zUWUw;@?i_fFY;I%h(T<0a{np$)lI0UV^wU685l^g*}Jtrw68MiJES_2%&yNo^AFEN z{{epBaFTbvN=H@QVosT)#m7Hu#i4Q$z9-M8Tf4$K*8 zKc2=5E0>_Zkys5lNo1m~CLo~x=sat2iPDyJYg6SC&F;vg#0lWi^e|ygq*lX?ECoJ5 zS83NgAR($jt1H>5$QU9r4zWcL(HJQfP*~za z=$cA}I>dCfbra;R^5Fm^6`+6%RTzRvz;IGRcL5F*Zi-h(C#X;)=VPfE7f~LTt@aDh z9kw8p$M!uu9p%PJQG4& z$c`^n$*zQ%=Ao9RWEpEL{B;6j>Np#j%kw+{h%PHRi^a2Y?$3GE;XDBlRuFI`x)qn+ z6Yc#;UiR^&8o7YULS+Ya<@o`w<f<{4J6@B_dC0fWUsD*$+?U( ztl-fiMwki9y{7%T0`T7D@FBiFJYpQh@d)u>K{Meiq-LEsbDWMeD`g2e15qJttV+U4X@T7W%I=n6X; zTGu;TMR%v+dP68;YAEivDwl;W35ia5$VOOS$CzYcc@-~DEcKl~RObyV3!C$;b#kIN zB$R}NhR`iKkftq$w`x0frG_C29o82nKnxGyvhon8UYKA6 zq{~=j;f{iD1O_)HyR{^Mg?5%Xn|tWrZkzGvaFy6Tm)vAB%jcm-5cF^m24BSE^Q2N% zmtncE;DTMHCLc`UE#g-4#_i&ey4m-y&MdO9%xK6!ga%?;F>X)ec3&c4vn{X6yK0Kh z5T>PWnZ-OK+)^*xXvH2ykFMbU@%j{Bj!VmfTz`P;-CSS5bwAe^a+T!XMc#t@Y0^5n zno8(&!!64;&I~#6+E;~JfLX0xZta|KVx%)BT|f9&j1}m@s2D@!I%`xX z=v@dc*3Sx1jj4NMFqTCucg3Bx;)Ch9yG}d-U)%X`47dtRLDuP;?BLk5K)0QA-yQc< znle5cFUcdT7*+5D~>n4 zTs!(!OGSAuqExJ`wKSVk5dMh#RB6oVX|Fu;>>#-_Zm2IB`8z7tNxEmEzoT{RCydk1{#GtI_4X#Cqu%;7=Gu|fk(vmYdK&? z*L53{$Hn`A$(b_gq>wj5r#MiCPBQ&>H(Y3`dLuhcYN?m)xO5XMlMb8)6l++$5LW{Y zj4Nm~R^-1Rar8zWqp@D;N3h%4Ds~`dIN^#fcQU??gZV_UIW zFI{o%<6y=|fU~1-F&ldOaz5&4CGuYkofWzmH23MC0EU+g1f>(vdrtGiOC|`J*^E*_ zNbYX1SB4ogr?l|4*@wy{&TxBxtvAKO*7r4#2;M`p>C3gIC<3I1A{E4eF?q-|tai_a z5$!w&Q_P+5a>Tij6@B-`o$;RHE?}v?(pClY{h9u^V`-$!ox3j@SwQ>y4N4OdXMNaX zwas+w$10XwSGjF9;Um3AtuH!a$76OG;$V_7_*80i9jsuZqO+PCQXH6K~AqowSyBaD68jgzb(BQc1BI=smu)eX@k{Ii_ z#z5DJ=qSP_4Vz6wUycVx-{M889cnzzy7@OcJ5Io|;yjF?iRhc$UOa-=3PpCu<*MsY zy4<3CTYxe?)!BgFU3=k3Qz)fjQ;>LWwADf>s<*AuyFZT4!*h3Cu|bz}z4rp9rNOFI ztSw(T)mbaBCE)vGPAlp(bi0pp_XtaxlBX5)9F(@b;tl&e7p4%$Uw6hm=D3z829WLq z3QfcXsq2c+8mhBhOq37ub#bk|lDLxlE}0!T(>^ZCY-A2 zfCi5_%xBScSN*%gNP=pV$*BJL;W`vWO;|O-_+!cl#>DO#5A9@ff4VOg2V~{l+^!ef zwdMo}Qw0bgl)=vc`GB3FNO`bw(Da1Yl~*A`Aox&CBLM?Yt#hvRpw>C3r%_0k@R&(~ zzO1=Ey0_-0%07l!&BYsI6{`@ub0zWm1cLC!2nS!k#I^r-drDJvOhiv-oegS6L!D;e zB3+7e<;ZfORxM&Y2Zc5sMf%EP1KIaC=gGlG%}-`t_K1a zQRg}Xg7cqiU1O|+weC7p0WcyawPKx`IJa1*>KQ4-J3N<9e@D7&pZG@;A!hLNVr_Y! zDYX!z76;96_)4&J(PtbYHJ|+=cVR%Ojm}bPhZa4z`65V9l}dR|$)QD?I0P5)2o;Nf zRjl|p_d0e2GI|&g$&*K2?n3gp2ceV~W$-h}!###WpkrFzt9w~j@^kn_pL_gFpv)}l zXujRdB>ewH-Ipl!@S$zYy}RsMVp3!-G+3WLD%n(VULo zD%KY*l;OB56;|s6dlpwP)(A3XN;0^U;{qa3+Yj#DSe#>B2*edl)%3FNx*oLywv5`m zU01&FXZS&@Oy4wh18cRD*n%W-Iwu18CkDwcj7%@@jKc=O^_Hiw2Km(<$a564J%^I8 ztne-O*L-8}>2=_8M&z!HOv#YT@6+3SgFV@j<=sK-v z07ryj6@e1LIaSLS0T{J+fN;or1>OK}F7H@|t({VdTRQ8NxVbZ_%Qc;s>9Vc!!$8S) zM&-DviwA(`IG04%bkBj{Q0RrC5aTNpS__4+KbVA-Q!2waF3yBF<2an0Y9romu?(-r zr1ZE28f^)Pg&Ji++mi(xTbRuDbg$H)j=rO$f{^wUmcH1K*0OBVL>GZndVYd71Ey!p z%fq=4W2{9aXE3J6ErOnwM`YEN_qk&t-!Jef9Ur4BAfxEKT$g2?3xy3-jGmgEi*$)P z7t4%2!C+2fCsNh*VCCdIU~AZPI2m>dg{hNEWq@#@LA zi`B!BS{_9?w*<(i3|>>^O7`@Ob7awl_skg; zQ)XvlsGlE;B$8~%z3$95{ zr0UBHJSQaY=OlW#CONEFL-7Ot8k+ZC6GA#KI!XkhxR|4lHD+GKvBSC|!P5fbLe8zy z_2sO=)D=13Un^F6UC_N>7XiyP$x#f4s8}I-RD_WjSlWPksnRHt9yFSmUHDMFI72my z=|zV#icZj=#7$8{)(>`qS>?0(VL??(XFTh}WZLT?)>6+bDqt1hvnrN*6%28`tf(k- z#J&u@!F5k&#$ZX@CjVt)Xr$Pq_bfrhdL_6aAY_|)3?J0sAY%ILvw?JvEj!*@ciV!> z&v$jMQG8?Z()?FcX>oqBF~7_1z{7zWdYPQIQVJd(gNq!RCu{jTJr{A68Zn+<)f37b z+s@j#E3~7OyDjD3fs})m@<5fcGJjW9*+X@7pbg4`%X&UjWuvOYR`8LK0&GAROcKVT z*1%X4>(hUP1-}0X0UrZ_*r+v+5UeFaVAc@E(;SWYM`Nz&75W2z7EPAz-cpQ#|>B9$}d` z8ZTF}CWm_0;K53SP_Z`3ihEZD!O#dqS7idgnKwIKy?+R}%Heta!L|#@QK_6XDQ!mXYo7=m^1?xLPPkZ*g=DyU={MU@(m`JJO@SN}| zsNLuYJz3;EQQAOH2mzo3)ACkU#N4e|fF4i|cS#wsB@AU`vw-_*oVsh3K1-|>0ammX z!P2x0ze1TuTiXSz?0rEqiF!3FuF~tPdegI$_gbI+Ic*Z6Q-2Kqv#HZ3j+&h9zd3mM zuUqrA5q<3a`DPchK9|2kn6G@umLI=cwIkBZ_UCt7OTe)wv1zd**kC+yQY7Ydxp{Ct zlZq(-I$7qF6_&tUtGTXp!^W;YG}T5D=Nb zZX=vjOfcWX0s-=Bra{Os&NsFY>o6ZB3DoVdwr1u)G|^d|Vqlgp(JX+BC4#Y1I%k!p z``k+L$gZcDuZ3j{XLitm#_?JRLLmxUcsW=nIyc^9s@ZJY+a^!o$8nfa3~p8lwz63) znjk=I#{;9=yNuAS4SO;=tu#|m3MuzWkK3IlZOpK@1^D7u&Gb5CHpLL64Y|NiH5);j zvO=A1pwbO(%nE5pn-9@C2eQ|8NW&61FM5xJ>@1f=1?3bPpxF&Rt8zC{6rTX8=n*DA zG;^!9D-G;xXtf$+x4T;5-Z9^fbI`_LDK*$Y%3)^Frj&1Dz(vj@B@3B$y@%;`;}d8) zxO9R}O*&q6k@2#|=Jhua1G;1KZ71LR?RR|eFMj1$ci!yVw9A}>2h=-ZjD_x#FW6nw z@0k41XWsgi&pq|#`#!SsmhQ_YSL`lccE{w`-hJqoe&>U4d2*kny-;aVzxCObf&i_D zwi$Nd+$}g^()>M=7cAgN%K!}elI|&Hz{H#~>9IVSIo9_nMVUGG(W>Ab@*^9}X4=^9 zK=_jSEu;KRL{DWpC_{9E-7@2bc-k#&2w%sYbzrd>CgIMq==@ovtVWb13dQ)1-PQ0w zYsZa<3&Hvu(d;DLh#c86ipI}^q|Ryv`S#Ct*FrqTo*TO@h`Cz+)kk&`iUewzi3S3< z@I}=I!6_iQyhbc$Rh6#fxT57>1bdW8#L{ zpkB*B$zknP9Xu_h+*&~O80!+UZN3jL4Ybs)`#7p z=s^VTr260j>6@xj!|5L3ne=IWxK|FpG#$l(J#;1b{D5r6)X}h}3m+=~O#=?LcrxjX znG{fV?9^}p{3Cs;uc^Z0jI8+NwQ{TG)fK})h6f`FIb-S}PF@L-sk~ULfLPAzW5kUq z9>#GVwy*|c?y?RbxdQz#ZqcrY|QhU8gc8Px-nC`fsihDx>Oq z@R3eg(*QEe26p*4giu`THi4p&XemK9U=y$uOV}C5`|=ZR0CYwRHsy`sN%)+5Zo>{M zcvJ%(8Ip)1%YREq_7Ds6sZ6S6=e3MejRJ~8s2T~JuCi#QO5{;XmB_=cgjB*=G3Eo5)4hiAnQ*_KO{R~lzQ=l? zDa#&`A5n5of(E(R6pkrW(?Mtx&q;^)as#}8vX`QHe1t_#bJqg7oI zmKLkS6eZK>XKBSk7@cw?E7<{UA8!GyMJ`LC$$dc<6KrrcOQrKbhs=_@gbWl&T;8W` zEY<2j_3^SU_6=WhGW=|{C6U~U3!b*szJoRJ$rA5eV(nd~cvS3@$_n1qrUFpnJO!G>c?vXDoL6zBxK{6sJ1MIzm&T!# z6L;#}Z2KhCp^@&vkYPqw3LNIjwCi#xWEeQOyabzkSCQWFVamNMP?C+XM^_NFo2%g5 zWzQ_Lk(QednV!~qC1I1RGTGWn-fL}T3a2TG$}d8l|GN6DcD87(ANUwcUTzDMoO`;A zMl$6YRTw`+x$2wu*jm-VB|UB8bhj!q<)<*cW`R{Ge8ziR8c96b$lqi6TJ1@#6YLWV ztvq!KrHo1jh3X3VKcNa(RSeB%SJBA7pjUHi(J2a)Mn8vja$QytD5En$R)BN^#483+ z9dlomFh6FngpTEzr-ZK&Q5)u=43?0Jou`C_)eR~OtPhqzThCVl%2i;wNikSPHtDIY zj3wJ_9cNkRwRiJVhCk~jqgz$s6Mj2@8hvnjc7@usayi2YYKXhb!sM zX+H}KwZn=YKkP?q4XefqyBlb0Xl`2}$}c_r8-f>b;m&=?gRc9R5BB_2{3_3ZkoWFc zc~8YGU?0*PEHRkWYkxYeVl-IJDd1!Hc$}N%&3isFgX(&{+ah9oxQn-6T&lxFsx{)# z=a+_YOtH^ufcM4bI!81_kiwXe+jnIJf&6$~@#}?7MWOkBk8S#UVF0RES{Wa)b=PCb zqbx6`fa2fT_E|7O!=S~j7ak#JDI$8nKtp}@hhwHj5EMe{N0t_KPN^tCy%w5U(PMuV#nzuz8->;&?uAFf3s9w$0@<%fL=+jl9PBP?4}B3(1^;RrT5>~mAuE;svtk5 z58Uvu^c1fiTiWkDij0uX;_9o}sL~_BLV1E~!M^0jb;X8$Ojk-j0Y)Cx{c67KsVj{i z&Zs#@<@g9*wl5iBUqt1@n>1>72jf3GQ?OAR5;BhZc4XIPM5 zDbn9@oGSe7Y;@Gt@o0g)&$WeR3Djh6gfBgC+gp=dX0+t*uWDh{E{J8`B-1$^<3&oDoMovMe+Wg212t|i;fsf$@bk(mid(1Gv3 zmyaArAC*P<&^Ni}A5bO493E0?^}RE6t6J!epO5Ki5Bg&JT1`qfsTMqEp(o0C6lcix z2^ect9irH{j6Sh6kmL58AM>^D)#?cYGZF(YD>@8tkF~^ps{W{K5$ZpJ?NOmhmartQ zS2G=Br6w81)i3o5t5jC2+A2cII6T_K&5MG%hAHU}q zHj>xeQifC4!l}796@hxb6~L)A;xX9806}-qs{BeaR_7n|R$Oj`8Oc$v`;xi7^z^vS zypBOZQo%tE1$(krogbySogWpvfq~|Qf%%F2K>;Ib$PzS;a%OTCs+H{@Wz8l-1n3UT zQNy#4wngwYBPu0i{@mbo`(-y#m>OUh%q_JpJoons~} zL3F54@X61cqZ9dNo+yq5!F&}6;}lv>j#EUzkD#2kEl(d3#^p#Tl4Wwh9f2&^Qx2{e zMp1dzY?^#)ttX!-=RR~5-zlex>8Mg6?A)@>GS^x*J1=5@IFo;4P@d&KMq17mVi4zP zaF#&;@Kt(UF6g`n{GT-a%`#XMzLS;ymgjBr5KI%<6a;`W4{Y45IFP&a1118D!DKW+ zbD~ms4(3w0{*y~#pc{>Pyd6VKqIY9d>+{8u0YaJv@<7~TQRhX_y$7caNFsa>R27=) zjv2h{lu*>%%|un*In#DLI!1hCdmOE5pOemi|Nmp}{iE!bEttJPK+OGdC9BDx(~ z5T~OAClb?k<4H{7Bnp(l26y5RCviZ4023S{8V5uyh@hQ#KHt61z3;wPRgz3d`k%q5 z-o59Xdw%S*f1Q2y*+-M&W76!wJ#b(u7vZ*z0FIN!X;)6xc}5BUr5T5RSk|wnRfnO5 zB#4=7-d;^v@E{Vkllt^bKISQWYPF5kYciOjxmgX*&UmA)YHHL5dJ%e$bBJ{nt>qgW zU}BKXYD+vus7aEa;gMG7-K>ABt(CA$yx%Vz^&V+SNqs2mz<3msz&25X*2OfS)~?K7 z8x$K}wn;uoyRjk8W=*$EiCtB0`9=$E|Y0O zfuMt8Bi?!$${hL9$~A0O9%H6~;!XByhGx*XY!-*OX2Id& zsCe?;$H9;b(sv6}(w{SYi7ad{93k0RoOmDm6>5~AEj}y#S9dw(a9KDwaTyl3LBs!q zbX!I7ySbJ=$-6B;`L+GK(9tV-KVjALzzvmyntqrB)DRMQ0GxxI%L-o(tTj;deLUqc?c+eI7)OZCk*GoCZk`eH;q)^jg6%u-TIqxfNBwkAT zN*jqtKtH6xuZ`a8ft#3(m5?wcF$5dynD$v@6|0t~rocZ>U!?~;r(dk?$Js9zO}Y0? z*v6K1tHuv;Adn46aad*E&Rz{M@hda8ESVWNz&^{OvM$qhA)Z4OmwPP+b>U*b)RNO- zWtXLKi`d& zb?|-gVJ7L3$%0*=Cq7W^n=RR+wi@yEmy2#7ax7?f;&2apyI5Rtthh+Qx-Vy2?|1K} z2W*y&r$_aaUExmOCPdQTQ_{*!NE;jHkQSU1yH2QvMyt}SSiu);Vb(nr(^BdRZ>B26 zvV#$XZd0K=&f!9+gnKmDj(NEtO5nIBdPq6Fe0>G z+iiCeXYhgnjva&`VjH0e{GT$8BGe8O`&5v&raRnBlrjZE37r=kWTz;hU#xU&c-BRE zh+BWCn+(mX*V`&yD1F9B@nu*`4nmJ|A1^gO%9hS+ARm)cNDzCfi$uu^gyp%l%{{Ij>v3sb0oJ?VavHJ?iZ+fp2Q%W;Eg zktnhQo*E>hLet$j_1~Tb{a3gZ`8^POfsH@Z28G^~d}oVO3MA~oR3FJ~xu9!&M*ITs zadWfM$qhU9igZ3L_YC-x@gt@!0k!svsh{RpG$#zT3BYC8zEahS?uc|#xyfFQGpKxL z%>1BKnY9l|$ofh|q87y}ecVY~&6`CY9|lk|YwC)EcB_diHB)Q~^Op38CPIo^aa7?6 zt5vG07JE6Fao%q9ewfEv68Pk&ei|=Cg_8c zH)(4~UoizuFz`)^jHEb*!1%K3CnrLdRhwT5ix*nzCULsQ&LyY`yR=x`+L-nzu16+; zv39gZTXaXXEeq5#!b>H6MF><&Xy)Ni6ecdyhkTBZSEb$T*%Se96R(I z=0~<0&5vw4wgfp?pC7o}{^SjLN5c%bB{{o4DYucBi}VwkDLMgRYz1po7}d)vs|pH6 zIYL_y9s-5>ZQuZASHSBdL_70~3B13S%5*wiqlNASE(E~KasM@9&W;#{61q%PGaeLj zpX0-tRV`hzweSY!MXQ{(*=umL2=XlS)d#kk67o}rsv&-bn!IXBu+n)7| zaX;NmKWTt6X~RYh)DBc-hdC&P1m?*C8oc9Qt4HXTkx{`-s0Dmigls(y@O`b}`xI;p zG`E-ZDK+KCXz4pHm0YjmSR*_i9B(SX)#W&~G=pOXIMrd}^isReBpx?n9ZdJ7nr>#O z!VfJ2v@oisTrTKvXDn1q?WI2T`YV1V5t9CA^^x9@hGF+o@fE!ekZo;M+2!f)hr-Ie zd=$F`#6>u#Y`kz{e%Zji!pO%HwHPueOMx{&bMdeMO-&#)Y}?{t%U~3GT-d5XJax3k zN_j5yH_1ZJd?M8caddHZ<|_2%dHT}LE`>5V_#%}tFbq)nbrE5ttt2+S)ey$C2%M|} z!uU`$kp9|&kod?)4Fh(}Ff4kmEM8~e7z%DP#4)1SFOqP)m%YGPv5*ipd5(Nzf1PhQ z`#FWd9rmL>X~+Y{X4~7it@>%iWjUx_YMF_V#St9TW~MOat0tpR77;{Y*c{*MLq4`S zvsO-`Y=O^EI+F|9?3h+t)(BN6omYALI$@l`Jl=Spn)PoKn_8%6l zVW&yg^_7=vNnV6+v??%sIZ=6&2ikE2$i}9cNfdEDt$5_?ON+#IEshz?2Zl&PIVu!h z8QTE7fJ0id&9;7QON5aqoz+j3j;<#k9c|kCG=%~GQ9vMRNPL2tMeY98-u37%a`a=A~(+%EY6&NjGEhv9sUtIg)0EmTyFlSqnbiL^bz3>pMAgOt&|@iCK|K zrb(ZcX66;ASWW2AWguT_>XANR6>quuEIvY}r%77|^0`h$#-^O$>Ep@?^9Y-=)?%q9 zm%3_VyV5>p8||e&T2n^kU6(Zn$_VQQDI*!t@=8`HaBAd(E?D#4K*ADcsN)1~7)enW zh-_Nw2h-QVj8Ul{doNr;{SY&}R0}>%J@R8wDm0sNgb~sn3BL!!@80lxN(u{Kxc!_& zy+jhS=8FjfIiy71?WhAyG;_F#8De-rS#)cRSd5$!#d1#TS4>K-HILfoa-pG)2SZ&P zlkgHUIGAw<%CZuRp@f_&g*?F*97ggdPFFgeTKdv#GQ%0fFgD1wOXmzZ)*&%QtY4#= zSh;RAk&U&30fAs(paFB819ROg1}1%g(P|_Bvv($7rgdO)h3h{sFioI|EB|Q;Oe_Ru zFjRa*#0`+FvY6YDCE0uKki_ottg7eqlPyBE=*K5*hgL;qNt*)qW8}u=frOpLUDW-V zzJ$0)JK3CSMolR+a42IULnPxGhqyDY=ok9En0kK7X#ZH#Q=FA3{S*!GKRdy`g~f~Z zj<(3({Sa!r=wHT;2Wzrri|FQ%9sBTpyA!}YR1)#!gDhN|Zu$W@&XJK4)uExVLEw@@ zTMCkEC8MW;steW%pocyf4MA7U9b^lpo&FDJYqD3{853POS~l$#AAW+xXE9;i z`-%Dt^Pj~W8Z16jV)+)#J6L?Chb=x+-=D?Dn6X)WXlr$g4*`ta;QVvuPi>7KeqeNeaf9GnD+(EW#m?XaZ>ZR%{6o==McV4k4tGUXME z0BFBO$fG;obWLY4_b{LGP_a8hS;}qJ{56UPE|La|3s8AUEKd9v35iHu`Go3$i}>1X z`ubjlgK2w3?;(nS4(Vsq<&21>(=pVf13gGSG|T+ap7YxbwlSqTxf zDP4a*`_tz`)2rH_riH?0Z#{$AN%6!0)vDH}xWs#g#Cm|BIcKys4Qx%9YjVQUaoE;` z)zcQg!kT3U7Kz)^)?^o>f@{ldO_!}?YciyAUg)TG4z{LOv}PQ&H63WaA-~LU&=+W0 zXB-G&E{%Pq$2CW8Yl31k6&6>L23{2uj4)j+2gPz*6O7H(IW)_aZA}(t*KJKU{UcVH znyqO{>Ro4`-l2XC*_s9en;rDkM4JmSLq8VFVOW;Pu0S=cWku1!IV1S}*qYd^H(L|> z6YDZK+sLRYB)@E#t*K+SCOMshAVuBQG?8b~e&|ZP9$pB4FAoRJ~ znykm_@4(g+dImcTeUPom?Oy-zwkGySSjS^FbAYUDYg#tD=U{7cyax%5ho6&;dUacq z*5gCArps2fHF?g6t?9DL*7S90Y}hO?U6jMF+nPGxr>$ujgqDfxC|Kjb6^xO~ZB1Vq zh$)xJ!`7IA6^3uoBYnD3hq76NP#yn%wkDUsB0~6X>M~fnQD%hD5^e;FVri0I96S2~ z+nLPqi+%j^lE6b}FfNU0Ce=$VWu7x2Rs>ZuFxmC<=Vo9!grxV|+h2)+sr8BsOhma` zBWE^Fz)5RDGes}&5M2R6@|8=2QE-Gp{-=v$lD%H0oF6gDorUB2Ed z3n0U`0t{PBO1M8dY zEg)JIlR-8+>ZO}zK~xnwfrZUaG0yv!7frjz!ITQno9Q;=ok304IQiM3R{EFfhs*UU zq@ry>tM&&UTe2-^o^_l{mZoAKEzznOw(UZr-~ei{bt{y49vb5mr}&sY;6p6)GP)Ye zJ7J@*FJ0u&@ij}ekntxiWc=j;uLk!R@V!83--q1~9Mw#D$5Bluw1D4z9F5U0z!(ch zN;Tu)DGpwn2VQ#|M*$~fxg?Z6pSFm~mSaqe%*&S`bBK>i!$z%_lWP~p4gk4m{ehKW zCys92lf_(nD~37sp~^(oV5KbYlm5IJ0_nygieZj2X%@+3}7s5_ERrJJV_jshmzpJHYf%~ zg(En%P$9#;vr9t&Fs2PqM_j-?W!3-xBH#gaybo>FtPmqWOlOuEp@Qc>QTp%pTAZPamA}vu%)=$xZ*jnj5b3iQn@&PwKa-lfv)Bnub4S z5tXMMOR)lWB&k~C79h7POt8(2sfyC?mKf3Y>Xb#j6plJ-+< z7My&MQ5Eal@f3$s6ySJk^uP6GQ*0eB8h7Sxj5S4qlPWhHacd5)l*RK=`lxE7JjQ=I zu|Y?T`J!!M1niMhz}N8&yK8RYvUz&zU>a_vI`5qgKiy~0v}<|Zf|*bw8{9C@Wpm-2 z8b%hf-ij^I51IQE3z;_q1K^zT19RC#K8Rtre(6x=P$SUhXdmTvb*3)P&Zoa^Lq2N{ z;R5H}L%8_{F$(YDpbh*Gfk9i5?c2pK{EnumQ+#SszwPulZ6dk85swfxrF+Bp*}zO; zld_}?)g#`5za1Pry;qQ>9_AzVInoJdzyz`|Ap+W4wELt4Lqgzo4HJbo>NXNJNg6cObNzak>G4Y6>*btE*Lqz-(ng?zuP5#@kK z8@qIzz?h7b&Rx@Q1Iw|n_sXDUWsEdXQ6Zr{oN3@Thf!bGyT!%9Q$W=?h=lWPq`*>@ z@+KQQKF_*-UPHzcQrB6>>Xi*A2xTRt9}n~(I12G@=O?uAkQj%zfIOitV^xnDJNb$} zLmE|uSMAR3K_9$3#xr*DC0+D7tNxG@I@A_+0hqTaU}-FP+*&+!l>QVy6#DVRZ|m)p z(yB{)6G2uRN#0csjXv_bDs_o#5=y^DZMv(t1g~a2c?$?rEPb=AeH(LSl|NvoVjmW% zYN+ash{S?KD3NC&;jP7=S}R*Zv);3plveiaaLf&@mjE+5^GvjmCqAD>%+wn>&q-jT zY6hkcS%j;R5-9_5`eikkC19>HVHWZ^-Kz?PV{?66wJB)X!frbu0!ney?9GK_Yz#VC z374xb<>@?wNaQK+9qN8QZg6KDch`l^W>sGWC~hee_hZc>xsVj-DC_*Er?#})V%a_M zv4osWU>=tU^R$7d&d`N;sIW$xW|0Pv$oa-jw)?dzgQS5b<^8xZr#bikDDgcxZ84U(_|Y8??>|hCc8s$Z zasm=<^hz#oOJFm_kLsYs*5XU>gW^Z8BNU5lCw9j|OB*aemx#IIc}cNf2%$^u5{Yij z&3%8nq!cDWY{F+{8*?#hv*P#OF_H6rtF{dx{aI9|2v1;Ub4vp0N0~0XC68j&>L^jj zg^De;ipOy|wDX6p;*<8sQLgfuZ2@M*Tef`dYbe<8cF;q-8%1w4 z{4ZOo^iL6*8-P&CDKTqwX|^o|xG&Ep3(m__ar7hu!1(7eAkfSl|M(5$#6m$?wjQ+0 zImr^Mz+kf_Stn>nvw&f9s8B=JRw`ijXaACJd>po$wMMkxw;@l}ZZv^LF`ma=gcz6e+Y_4!A?U)UqPi+JmV_;d^ zE~Vvs_rAQT`-GdT-X{#r>pv)`c}uQHJn#*TAZz-)!ra!1L;A!*agzIl@@{eeT{cDC z;-Ec*Yf4}ix|ga)UT2Hy}4&%F88nDc6(5gaS3v>s~wPGQ7GS2&J8#)B=l{aRB3}&2sJU)bXsHe#`RO1N599RKlH< z18U;}z@&C1Auc;sB{IvOgIH7Mkpj8!n=YW=wg;1h;lPO<6Dvr>nAXQ z>J&9Kcm_n`@D}6SJq5-G5c%48W8A2RacK8}1wvmQuWX|%|4)nyK28y=w^nq5U-QTV z&nNo53~ZavF}u7m_&ni=qazo(^g~fzvk5zZF`?@_D2#ubO1BrRXWIZrfc%IRkzM05cI`q@W;y~i~#tasPhvWp6(ohpgqHd|19QO4(G_tCjIL}!( zLwur@yQ#B~gKl;f3^JqL92@RtXQZ2BXX~b1u^>_XyD9H(%qMl1DF>1=o^>;5&Zut0 zy@hf&$wmf5Iz~CCh#@zNe>mr-)&8GH}EPxr9aMYdvEHpQ{ zK&6w^IC;}X2k{=}jvKcKNu}WdDT$O4f-PfXUzG9VE_^sKsmFC=ws(!mkwWMSSR1$D6N=MS5Glisk6*Js8y zuHgr^AzlJ=N4REP=OyU0;w9u|FcLI^O}xQV&`$?! zWTDI4=xtqpvM~p4!reBC2sKdsyJ;f{oJqogNVBVRwW6+str^k!MAncYCl+I0r@6-Z zYtyfF$Ao5g|MvJEnaC50_h+){KDMGzLu%-sb(LgQPKk4z2cooZjhA9#RH$aEK!u4|d$cwF3U z0Lmr+Vxze48}%0HZ}GxVA2eoNZOrJs8xagdGJ<^pklK1~_fd^~ z^5GBv>c<+p>A@H*UV%i?8#m_T2v3lnJGFcv(Gb~Ts-ugCDIe9CgMPVluYdGp0if zns6!hM`S3~Ey?`^>g^a?>tyk3<9D0In~%37*H$ZcYaPt;m8^^g4OCq^a@to{N%4}e zuJ+mrp+UF-GR@|%MM$|aZpeDZ0UG3xb%(Yw`&jtAewbzJ#8VzE5Kj+*yHm zvsOJkKYf1~rfuNeEUj1D3n-=ppMgHuKEr;lMYe7I6c4?$1VMa25(zF?8+~X4Ef#PN zK><6JL^0AD?jiGfq9#i;u; zL+mp4RQgUG#=1}w*p=Ltenec2-b-NSk(qMMX&T-KH5W(imWz)D1!5qFyO;72{EL-Y zOs!@ai_$NmAy9j3 zruo0%1dfqTD19L5H*6;92#GYT7C;JCYrZi?ZE8w&VU<%|`Yw!DIXVWy*wkwlhafPb zT4xX`j)yPg>kUwwSznrc-yc%fs1;?)#{@$n7@%lc)4LhO%cAr}i3Anftlrr7SNM0c zK1*nUkWkw?Ei;o&kHu0I*h}FAwVH3!<2F6c$Nbegms42EKxuMEW7Fhl9A?*|HTpyC z3Z}()wfkyJCEyE5`vtQ5JQR6AB85+ANUbRLzvsyT0T-ta)ZXaMD&u3|y8Oz}k22dz zwcy++Ytz{%>#@C$)29_E>qAM>+0!VM2rzqK7BTLDSI)T^Apr=d3e0#QB;m-1uvMF6 z3@x(*ZWaSaB3=fH5ogkYRT7m*1lppIXtdYrEQCi4ZUyjcZfpRj;Yz`GuYG-hBoVb>lWIIANhGK@jpSp5lrG#JBjM4 zo7<^OJ!6Ifk6IfQ@A)M2T>KE^6ZD>nCpJT&@hCm!I_|F}akFG;*r*cc+6>Kx!sfX3 z+5?u0$jE_g=W{q3%K-?;SLG<0qK0^9POhRx02T4Ojy-v_XFi2#@rm|t%*_izO|*7o zc?nERStW3kc~ZelB5Mtqya`Fm$2@I-uQ4~8$~fH%R`3GAp*eO-Byxde`j}*uks&3) z-&#p{EXacQ7@Dy@h>MdyFUl-wf?3c^15xH(ovZd!u1C1h2Qe&qz$hwd%fi<)BG+u_ zl8QCczm#a29=U7g`>5xomFn3bh-mVqg~*2`{WV)GbZ7?r2tTOi*5}Z zPV`FVCrq^=w@rttTzOf@-=WHtidW@gnPa72%cV1e+$Q$KxXSH*q#buk0vzZRG^oif z%s(UYn;8H>!oQ$r%$}p3amA>f!8S|r1JE-}c-Qm{c29zyfpvjyHnnpXI4h!nNb~2e zXRxI;tY?@8vXY)rDl)|}mX)jN8HQUOKNT)%Bt7HP<25~_nH~||3T5hghJD8+J|+^d ziX+*a0h*p6D=!*?MbfwCWm(pWfekhFM$W8S{J5SW9~@~GH9Z4`9j(IkjJD|+xR2r{ zJFI5_Lq*|~4zgU~An|_j-Gg;4gv3KiE* zTeCT!Cp^hTfb)6VLe|P*0&eT7wu04CLQ)tfAtp@wS8U-bUv$vNh{c1XUV<7xX_lk= z3F{uB(YnZ2!~jGhDh^bQ^+|Rn6orod^wM89G!_rX!4$M+jX|+a@j@KRQMUy6|7+!F z*}941D3>AQ66k|F>BmWzzTT6Q!HF}HZm^qj=n%Wesyv#y|BIk);}<4+6ll0gDIe)# z`Wx0HmTkJi-XNY~k9dm0o)h~yD&3__BQ5*V>|SQ-NwI$-eN^SOy1{NhSNtn=2{oa+ zAXG8gdmA30=n92E-9VnzQ^PBhyt0 zeF&WyZH3O+#MOq!7~nQD7gA}Vm0t0ng<`(fr>A&Y;hKxt;#56s^D*bmLiR|=hQkl& zj##ZQJu*yJN{Q*{=33%JE<+h35&DpRU@bo#ZMrd-1&y2N0T4HRVZ;Ku{kDA6t*d}ahlm&CdzR4?i6*!Y?0?ac!k80dCX z2f9lUc>3Uz@}*#*B!?x;M8&%V&WB|Ij*HjMKfL$qMh0wRM1|RcG?!IGysyTUqg@`+ zy{K3MFmpTd$mdCDqyLI%YqazrEfMa(jpOK*uk$26LPjipfZQ33jf)Zg79+k{k^@9x z8CJ1@6(19v?5r+iaD@%jVY`7&)A%dP_93`MPTo=RpnMEVQdXNog6{&Z+ncO zwdv7&=w9w@ce)#O*U(os=s<)h{W^S{xhblpdBh^LeUoXU2~wA3*`2obGz3HOqA*{_ zilNT;JXoN$Zug%Djl#Ysxs88S0-H3`SC-r<%b8FC^N=dBI@C_Rj{kEo^XWp~9Lz`t zYo;z~BUpQ7EMh^8wV}t?joU08OP}p_2_{sI?$Yxi$CTl%lfGX}Uc02#&e3NE$WXv; zqd|iu3Ow;EPYMN+7$h*!VEeg_iq)K9^U$23eSvB#O&b=C@{=%aXZQqs0nw~sq1lpp zFhkb^n)NZc386v(2>~mj-c<|leW2`~q_j;PN``?6Ny2 zUQRnC0)v3ZEnL^(jAej42tP1~G`eB<$Gj@&8*3YEjjAhztxV8r%~Hc>r1GM=HFshc zW|5I4mKNYbY+TZxz1APC%)ua>3HOHXpj%Kfe0s2%$6F(>0LM0~K*(%OpSJyI;wYs^ zT7EXjLk+;B-_`8%*qKki=a1V(wkda)(`#L*zF)t1R4?*td5aw>Q7kUt{8u+NtTLM7 zgvGBaw;h_rp=90>se8Wq#vC&HhUw{EwG6c>T|g)jqau&T#rOrUC=*qMFn?G=$}m~a ze|HIX5R6m*UTX3s8bDM)DnWAukN_=9UsN*^kiEPiWcu+8b_mC^b2b(LXfaH!nxh=V zBpvUReFv1>j_1*+4D6Iz)zecZpNgb#-x_j}3q%ayubF2p+Msg%Yy+%>ik{Wa_7{Ku zkN@ECeV_gHQ_()aY)OCh#rM4U-lvcM*7Hiz9Pmx1pY30M{^wu(^b4Oo`hs%Uek1AI zzyF8#eCDCg|JI92q5(;)be|X-`%vH6%Wog^8VY`xmn`o7WJ4RkW}VXfl;%jCKjOWK zw2l}r49tR@d4w-yo!ov+@6sg6+?7RzCP62!vPSncnmIz}5?2fOY9*y|NzvXpPmKwL zd-QutOk!~K7;K-r*J2UBt@|M=8p9ck($Yx?W{UeI8`A0|wjxL`Z!1z3)ypA)+N~E9 zCrFl`3Xn|+3STHtRa8`_HX~%zdp{sSWqfNG0U3^&M_Q{=Wo2ANmg`Dfb^zwkwAq=% zSr(-ty56^;8*^EbTP6q#T(U8F%W#yVnL4Z(tNH~c(E*iKRW~b?lAN}}8~fbyMpL+{ zn=SAh21&(SXr|vbI$?AH69cHlZ=i^$>I65SF^(_-5NAH!?o%~HgtxbxBu(#x8XD=h z)P#Y>2+_iqnzv zm7h-yCb*CK2xCJBfrS=W34cejBsZt%z9?H2fC#3D^oXnT4eNm!r!=2EW2O!|Z3Do<=}qU2iQJo6gc=dHZovbxPY!yRzyNv zyn6m7#XZF?EOi(*viRO77o*}e#UuJH1#7VsQcu=X_x)63Bji_Y`kZ7b-3k;41sVU6 zCU%2O4?MM{2T(4|1sv{n(kOVFvUKnYW~y^qyeY^Y-`?s4-s}b5s|yJ|o-^Kvw!sCuQcGEP1-tJBKO8WJ&ESYMAlCQ%;E`AYG8?yr2X3`^)1kSROV`OaOp3OM3EhVw z_3@Q6iI4BN90$jXlN>Y|+hO$AQ$E9+l&iJWf({S3aK!UhL{UDCxusZ)cNAlHW|0!O z7O_k)#%`r%h7%i>xMn!9X^Crx6I+(JZaA@RiRTR`j#=XR;lz$5o%tq0*3RsrJH(?Cya}^cjbyzlWDyW%(@55)kcGu`<4D%Vkky^fE*#0aFl6Cte!)oA z1tAMt(}t0(4IvA;dj3e(`5|j;K3hMMwLWB_GM+b*bzaEg+{L<)taTv^EoSXV*4mKO zm?tvIFbvm(tYki$8OfRnSy21*NY=Dx5n^_@w$##l(n2IdZ)bnJm}Z?MO-pOdDZI6r zO_2*k=H$a&>~Qve4|+0cnT8>trzWd4DvN{cV*F5{e)wK`5e=XTjrT7WRdWwT;9|JE z$_h=nwWQ+6uuI%!zJET%o~lQUWjxk9f|J3&pwH3PDCUqJ3?bpF7+r49V3OizwEUZ9 z9gxL$Sby-aS)-P*lVDNX8YO&mM?bd0w`)xpQ@pp+3pYjzH&mGXH9O&qCnG-85V5v# z1qw!p1-y*=>#V8|NH6D!uFvP|k%T<2&)CI_^aI=PT`T~v7fls&H%wmSZ+-Zz`reo; z?ui*U)(Xnvy@M&nh5k@^iq~WiGMRFAuJWYohhch2(|9^ z>|$((;BlS-!}K}cCWqb(P_(|d_ym1Qynap;5Pu*0=t22sFigwtCmIEPDB?bXAO-`< z@2ERT-xmrquJy8$>O+mu;lob_pJ)g^@^z!;80frR(22714LbTZfJWmBNXMZ=Vi3yV zyFv~EF9+n{VSpMl6>11@z^9HHGt{0Gh8j4p*HL4r@G{gGfQk-*BBCy;Vv(~N#B^*6 zh|z-qw2FK76mQ>C!VK?wVtVuiETs3YtheCy4HvaNr0++iVkvVmmI}UZREm8jDpW+KGn0{3g#cyM0asP)u zwrF>K$U1F_zk#8A)y&UX^Q(Nh^$Z`bKSLe|dsc0b6J4w3HO`QCra`QlHw2J8w&lBg z3IqB%Ul2;>pf5P9X-SMByLe3Q5T?X8_GdKr;q-(PZ$@o5@?G(MxB056gC*=q{+dR) z_(%h_<#tZ-p8R-pV;*7rWln81Q%x)z8C2bN4D?i84Q#366heU%=2s*X0|{9cDQU5Z zDH(L^i6zmQfOEF1A;mI7dJHgbcb@|chIr8YWHvod-18LuR(1PmPSW)XsuItwSb;za0QLVyS zuf;gy`WsonjN3U9?&@Fc$lfbDlDWT!t6{6}`G66O+ zefo=LDz(L2=)|YJGF7LJlr}HC`*~lAu|(>-(`8q$aWfNnuNQ(ApY!!23wgSMJyhLY zvSUYfS|zk@l)OzfUKz$UkqH-mD1E;qGriQ?*6OVqN^#1dHY}uw=$;b;Cw80)_9a7SWpQp0zKReUwdKKS{h#Z^vyj zE!gAyX4Q=Wh3;dJmC4|te+-j>kb&h2=yE~`$nOi77BN8j-5zASH_(|t-3*|{opFE+ zHNt?boeYGruStI)5XP*CQ&anAb-I|Xj4D?c*0cX_Ca9tNi0Nj=Wbv~($7exySEMf% z-Xdo5kcd}l7%g4Dc|yOuKhNY8+*zn>i8pJynDx!%p5!XH&@%rY<$I^p!iL;@w|Lsr z?=ittAY+R*H4ImaIDJ+FP%^d`lsI~%i{9X0TAu92q;0lm=3I(`buE$6$wRAIZHQC< zTo~BEqoRn)RS|q`gO=gOBNN{E(oi_+M}gZ5pYwDo8H-HG1SPjdQSp=W+?N-(O$V@q zA5x#Ogwz&qsTiK5D6t+B^2RwT-vto10bw8A;>-QHPCzWk(E@ifu*#=a`QvlQTC_LKcf#I$O8ZNBN_?xlXGID;-J*hp*#aST zu(v}Av&pS_s>~UpEzsQ=9FFoe>PxS{4ezHJa-x;PX_cpqNNT&F&TrV|pTXb+s;p%{ zo2)(a*H1RWSLFvO)+&8cxdlw=HD|9j>Sy#8jZ6vhNdpn7cs|SQsdx^ViHdG`ROpBq zzEBaT(bN%UfGV@tI4mvts)fq9|BXFZKF!=sk=crX7qRMC@Jlu$KQp`q-<-kn6j)6X zN)u|7Zz5C7nktEB`=)#iZP_`RewL?SG|U6vI(eV-+&uJhrbHGm1xNt+Hpde(h$)Hr z*g*SQo<)wp&UPUm4wOfRQ#08HF;0ExC;@s{XY!dUXdbG`BNlwO^^aKa-PS*1!FOB#hy~wm{Ua89 zxAl)$@ZHuwV!?M?f8Bx)j=HV?lA2kcM7Q;Ct|bm^{nOY8N?ZTt;+KYO{hQxb+4?UT z$+{$DVe7wWBKS=jnFj$~~NS=jn79Lc&cWMS*SU?l5; zkcF*(!${VKkcF-P{E@8lLl(CF^&?s9Ll(;Oc_Ufpg)D6SYe%xyhAddqsdK?!7(X{E-i&A}%h3Ggs;kYY9N)Qf2Jlg;B29bJaHsK;#R<`f= zhK;zrAtSDo@?ga6F_xNr-&C2h@G>K=4bF_We2$S$%&PdWvWvd|pJ5m6G1$LFhte++ z_W5^z+?6r2iz@M>NYzy{zl2?M)y!>Y$lGv+Ja#x&trNTGs(IK&SIs-qAXd#A0tibT zLQESs53|iQm~k^HzTMRxjj1RnN&w0vYIe=Vqwtr-14s9~k)SLVMv#5Q-fbZ9{oe?{ zW{!HrL3=~}$WiuDNZXmM4RrHWQ!V=R1o00sf40#^<;?__t#K{vmCOt z#}=>L#WFhAz{0m1{gM)kl-3p*8$eUpT9M3htLGGn1FNUr23AkK4Ou;vFlzOzSCG({ zl?YbPkqWe4E52j@-z$r8W%Z<7uzJ>Oi>1@zXS!mv0x<@kJDmm0Iz9-*RYkHv{87G zG=P`5)Y;%Qz&GM*Pnk_LKuj0jss)FbS1cfsIDnYm1`yNR5X6))3bA?xkeZhW5F4q$ zAVyk%Sp7@nf;Uz|Oz=WJ$l}egjk}iUrL99x3*fT7#5#G}uAlvcu92v#T3kqQEWl>}M;QV3St9}q!es;DEF z6XY<0osA%C+c;+;NLd)2Ahn58L$4JGQc6WRTy5pl2~uxE@KVAkyy_KbE5}L%^ctza z;6++MulkqTwz0-W(90B6sV-r&?QMiCsUchdpSBf-5YD32)e%lX>}?kebN6z2&|3-N zfTF2YbpWn_aEQSn0M-#sc_RQ^j&St~&bk+N?)xnm37}6Q9;V5~IKyvXU`8^Tw@a~u za+c8j{*1x2Z)-Ug(RRhjIs+=2U){0cT zmByv&VM@}fUgj(&2vZJpF`a%{OL(nFrjjVev6cm3N+pz#fI@9%&CcqYYFJ-mAL;8} zXYH*^)MPmqNJ|^-b9Dv3GR)nlchdKU^u>_An{Px7**Y$KSKc&-Gs5q#Fo<=uXc@a; za5~?CzqTm^W-8x}feoAAELHd8EOA>_78|7et^;*8*1&J=+xl3ok){R>s;aayc<)wY zRvE6`ZW9s21RMdgg^Rx?t0hafbzXMcUX=uaZG!ZyOTa~I1`2B? zOPteOR7O*5B)A6!*m56|kn4awk!Q*ky_v+RJ`=DTI%{JnG*!l-;=|s ze5j}`?tZxyx5~pPi&*^{o7K4}vp83N=%b;Z8`!Sl(8rv9h7Wz$2d2~S?p5nD&-L-Gwmmc*jo8IAmh_#J1ubvan_O>+FK`>r z6nLu_c#9Xfl{fO<%*hepPgHe7N56fUFGU)=DQV8)$JvJYQ)^W|@M=}v;W?F8vd(5x zi$_A*SK9pqpAB3yVeb^(T}Alwk*uqagg~xzhasg>?p7HIL(oVluzV!sooyu8A{vE8 z@`vR}6m6;;Nr!W&J`$NQ8BsW4WBEVx3LDgtffUP&4%Za& znvdNBU5T}cl-$&Ya`QUm6ff*0Fkq!{PM4?jQ^KiL^8TH;g@TH;yH6w>9~(IUr3RUH zg$LviQN-qejzNV(P&}5_Zxp4~k2j559pfe$hmSWA6PY;JoPdGhUGL`^Xl-!32{X&N z9&fV1SJm+*27mjT=H&jU>> z4iVOme;$Hh+5MpRs~-RK8!Hp8ccA`Q!=wdX&U*awKI@P68tD(+xi56bAYvvmaOnt< z@3%(UH^btN4xY?qww>h1KLuIZJHmmQ>L}a5mI`U%sXNRcG}fT7|x^dsgd+zuW> zC+VY2`V%6^P=t$Pepkf0{ABCj-K}I4C#Hi*ii&Wu)h+@BQUV5z7@iq;z}Xc~wx09J z%tJ8raM}>QdX|%!|NpT{9cBERGx43X9IG^<{sA7V{JQW6fhqEZoJ$uoBYpG+VJCmo zYb(j>M@$5X`hz-Fx$JORK;iX{rclFQm=+7p5(m5ct|Xq^gOJyjG&HfuS!!dR(5bi! z{e`)3dhsMwR-ERzU4suXM(aC)`*ElZs z0WeINqa~YqJM!K<5W<040Zi~z3+u^skD^o=@euRq= z5|=Qldz}=W0tavX*r`&;zB&j?XSF}i*==^quS$+cvLWz@{lY^7t$Imbzeh03H3bcW z2$%rC!)86x@r;fZD`@=H$x}^fUA%d!R-R9(fhrCc@GlMpR1;rPW%%9f>)NlTXpz4mgU2tagu5Ce*G@$Kr+jCo)rk< zpwbSE&liu`LFKOq0}%HXvdKD@enoYW@D%9YNS`p?sY9_KPeaI)j|qJYc+ER0`(Y7d=r|!u!Hq#*1H}zP0zAw@o=IW@|pm(=?G3lpeT!` zu5^1*wcx3$0Iz*vCKLL3OoLl>zK#(PQS&v8fsOG0L-TbUENbeWKL*Qfv-)LX9@KA` zm_s~kVvZ;c#3&BdCq~jKc|)#cw&n7d&Qzf=1mE;|O&DEZYXBn?4&~UPLS1FZ3>y^@ z|Chr-y*5|?uxaV7_*@WtOMF0kJMNUC2ZI%Q_RDd9t+3iL!f;`9@nsWySW~)IEL5@o znH2N>$kw72DY0d``(Ru%wJ~XpDNr~n;vGx*b1F%<)|ll5JQyFC6Og|~C%VSpn zEG`TbWoQ1AYzCTrkRBU+S)B>K^cNInL5lxwQtY)LSJqjrBaHm{JLs&^_Ktg3S?YPGeCpJ^ z*t6I)ckqU8>>rmsatbnRz&2?kFb6RAbrEh*RuWz&0lor;m?ctfeYTDf>xQ!!(2K-_ zQP3#J$pEnJ8ZYJVYP|e_LS)r>LlhH9%|7sN1CGnT`kXn${yK+nxp2Rhch;d{{^e{< zdq@t40cW!u#IqU}e`|n|DQq=Lzp80R6idt?|21KnFIDhDo1=jW11jlk;kvMDHgTKa zM^1%kOP}E`ckxpL5`R~N%rYrgLdH77ho3Q~D=8GEf2l5!#+xY3nP5$769DEI@heQY zj&Ki~BzW%$Aq%s4T)%<$9^+ZO_c^7BuO3+r*%65=@hR5i64w|zuRu>5uNCyfu_Txb zr0LUQ2|z^bMoq~?u5QTL|s)>J|*Xad8NtlYUC|m`SNH zfiHWSVMwnurBx>(a8n~YWoe5BDk6!!_`B}l;Do!)i^wwz%wff^rMCi#~Kok$Sg`2!GVJkJkIqw?BaHPQ)Qq8iybrV(2D+aO7#~E zu+S$ATuQp{f*4^L7m^-ggf(Udx@0r#YOZSvFz%lm<(I(&9oY6QkjyNF)n2%GP5_}I z==*iitNFpW_j=*0?uk-l##h7XZqSIz|3uwDPom$t3|&h_;JP3B$prvxbTo=9=hJ_u zvn2XRtmHsE)rg~pm7wbVQWl{$S)l{5VHg&zEt(pMTlcU6sv4zJ9EkJuFcCZOT2Jp+ z15|($IFVkSFw~`~MK+w&q&BYku{}10faLf`J|o?aV9DD(f3@Rnxgyt(c|_Sva1Pt>?fn zB6q@(ML{^n99cl5she`scK{Vzb+{3s5ux)+QN?FdODE2X(6(*GMftdNEuuJBEV(lS zeC!nfd`Pj>i0+R$3Z}8AKQBn{GQ zTA^vtz3e;`JYf)*-v#xHmeQbpMbgEDt1vJiG$|-aYT2~egiSiP0mprzIM5|c4HV>p zE(w`Qmt+7}Y9S@g5ikbOA%I3|;^rwjas^PQ53ZJ*9*$|xb__I6F``PrlyA}5__b%;2?ZG)7$3F~(S_zR?F zaL<7Prbvjeww}x`0I9%pDoEe!gfN@jl3%F$At6oGwftOQd|Ck}b9USyY@E3@tFy^Z zU@ry?wrx^Yii$N{DHuu7H+!`Zf>pac)-V;p^7TxRsY-A0I&Tei;%(n&^=&HES!df- zDMuc7C?QUXSHh&Lt4xP*N_arlBr7F)bRi&0kQM6u2_Q1eZM@5G6&R&Th3TiV+kE<^ z>p-XH#Dj)3DeGIq2YMUm?1zDETESSdvX;h++IOpOXGG;np6vMSob3( zER}7@d4Fl+kE|Z?Q^TvjHft-OQ!h0iWv_h{V#&kISf@136}H2=6qWA`FxA=*9A}*A zx6}a)IlydEt`gJ@e-+v)!0aFC@2fmvAwQ1;PpKQVqg;ib& z=p%(;sGKM&@og3GqRK9{QZ*pXrwW|sYT-lpyD}miqD(BEw0cCi@EMI+@uG?dca9Or z-(?zogJZa(n*-%76u;C?r1b@Oo#a4FBLK`Wa2*^sqWrm4HY$OHtr|gq>nPQEc7t>lSziLwUNot0va#bM!~tgva9|X?~5Q`3)qk z#&lR4ezi#btO?|T6;#EJ^}??&R>V8YuOkKH+5kM0xni@B&OUdTZMKaQQf#Cw`i?#Z zJkoD#MB-s~Nbk7PinZm9$Wjnh34Kc6Wd#8qU44+4Tlzf|vytZ6ej{|Fc)e-1$VX59 zYkd`-{bFb!bgf7Co72aF*z0xq7SrXS$P=VG-q8fBhgEK7R+;SypKaG^XjqE7!Q5=x zGA9^g-9$^Qp*mJ?4ZwlReSt){ zZlWS7*5meK>K06w|Db#ET>kXZ7w=&X`r-s7x67jL4?FNRRxKX%YhNvZWjbE9%aTGc zKwtQE-`xyX+$VP5XHp7>D9c`D7!)574$B&%@f_u z(>#om1Y5%w?9azWLok~619$JBa!v3L>5$Ad=Cqq*Q5bvNB$;zR{XRZ_`EUR0-+c2+ zAAR|*-S=!?ETcH?xkv5uMN?52(UG&JM`6UU+1io5pq^+sQ@`R~s-4;aUol2FGX>&0 zb=OY6r!Ne^Xhi}xahl{K2-D!W#c1fp?a}|JoC!dkPbe0x{6SD2bY-I?-8j*tu84%e z9Ltd+JppIU+Kn_nwZ*9{&F zi)bqnN_^2%YzHi!4A|B-Y{7{KtGeO3S^!(MNe(co*{FL}#NxJKN>rR*AZ&_*3=b;P zQY&<>spy|HA)_jK4b9^wqAqDTDDTUAVVGlEnk)7s^do+S{Rk~G?&UpVRO-@VqCQEa zheyHvoCe!uu$qTzj=wCQrokKs)?*{XW#mR0gc@zd&-l603H*>+=rLE4IZ*~TBTBU) ze@^f-nz@QBaa(;LByBEm*_VMGdkV+W|$dg@Gs`*Fj?0 zzuHu(lx>ik;@HE~WfTVv65lm6{y&mhVD~!z4(={?lQyWT0KCG%k&wCopcZkL>J@OC z1{7qwc3s&!z<>0@I@63@P%vN!6X|9+39*$M;yCIlNwI!m-&mO<7+jqxNENheB`Fsh zcf(B>Mgz?sLSWhE`3m1?2*D#6ev`F~w>aY;gByt4$6Nq7(SvBzhU<=)W9y#a0VvYt z`VH+l6UO6LT`?BGdkgyO#8}=CPy@uLyJHFzo`ioDcIgh^RtXVcf*iJpz1Tj9RxXhO zMf={rq=XZ9 zjD74bvF1(A`Hc#WO$wZ_0%+5n;yHgr?c?Qnd&#-=>qiMsW<{u5O^S1vt(lFho^nv4J=A6K0oP z#Mj1uLJX}0aH2gwHRd--i<-3rd2Remhw>A~+`Gj=-KA1q1YI1dzX4M|F^w!!_b_n? z4#XHYuLbqTs1N{QTh1mRODM;Z!B4lBARsVFv@-le0J^3_E<~EMU=w~ z@gLCy6#p7z(lE5)+(5k$i?@LA@GnznhTNKnh)BhlhGLgE)P~JC^`)5YTU@DSjy+wY zy9(zVz}M=5)tJ`P;%YPDydzRh?h33Z(yvK>QEsn4V}S110ZGXN~+ozAF>76Dbtwf2F>d@ zq_v3?GBBA_#10QHUo5eLtM(x20h4Dvu%08q?F0jRLlhDZBFgxLB}L1am3oQkcQeCh zk@pk$b4!r*T$;tO$bS_gD1WNaR9V~oms|BKSZZ054 z9`&1prCX(VS6JJ9sBJel;8%-0^RMmz>57!B-z>X2$ z^eIn@H(}$U)(*sj8e%+|beMF37|;-ER3QQ&KP0^Z?sAZ+qjgK!jvT&yB~-Lv$-^67%ML_ zY3NMLHacn-nea1wriJYbl7`N-$au^*nSO@Pw7?li8amU$tTDTOmZecUmHyN%Zl zu?66GcXHxcUm|0f6tOT0Oz9YOyZAfeoO8DgmGMIC0iZYbuMigh@=mc?sk<;8E_fq% z?8c2MLo830gbB<0LMzi>)OufdzZw0IpG-`ME@|O{huD^6)~>K^vD|T%-(f=}n|O({ zRN&V*dze286XppAH+Le$0bm^oNJNC+_WMNd?6RQw>4=YHEjA!l;B*!9o*^yz+A_sc zJ_vMS3RFga(~S`gKvin4GBcjhx%#}UC4@N0dX**mf@-oy-VQ-+W|9CbdjbnKU;4Az zHD}QjyXlJJ99^&NeS>bteGD@~>=+htd`P$hOo*-M-8UIx`k;nEjtibJM>biZbe|qFP8EKj;z(a!sJqYA(tm$zm}!Yt+VF_!e$4=)qR7+ zDzdYKAQ0W{u+^bG^8#>UK5Bp+>DOfc!;V@eWbtd`sq}a0qCHflut>pdNNf* zMd{Zinq-OP`Oj%AoG#Mq-zuxSKOR_obSyEL1B$z2(^63z=Zm|L2E-xf*weNtLl5xMS9`u zpDXRri%B0C!^`p{{ds+5oQ%ztIO9LbZ2B)hbXx<@;qA1i?`7b^SSJ!BvP#J^Cz6vl z0vv*;^uRv#lgeTBKn86oTt$rFLv+1I*;&h#rS3)QwsnBt_lEAjz^;TncQ2&PaA6io z9}sTu49jsJ@GqZjz;fG9?+vA%px&<<5-jub|Ewhs-GR7YUjbOqE~6HWRyK4{TzzsY zPL%$Nm%77&V*pf9hF`P3^JdDgL7d!RlZe>U@Ury_k|V#wn6k5`Fn>T;OoLEnOyag+ z_vmh;Nn@RAub|N4u5#BPO7By*LS1_m}h{W0oMdo!@Z#fX12 zwPx7}x;Irli*kzNz&8M@X03KFHj`3Ekoz?`l-Pi{_0FZd(reC-rQZ}2?jDV~5xU6w z>~8cE{xuL^NwHD?xPZ0Ua5V7Kn0%U)-b{GmLCmOU{G%XJ5$-jH7e30Otz*Z~uGnFc z;wauzkY9Jm&HV!lpz^&VWfD*R> zwOCI#-8B&b0uG z5d>&RYFQ_2YCPX2fX}S}yRY zg_^A+<@m3i)S0+KL%R8-8-=wT76^)t$xPH9#uH~ZJSz&;PiLGqkJWPH$0!< z`KX@3va^uUe)c3w!ml`cFEEG=ke?hIjP3A9%kNRO-M7byUqng+@xxi;;k(!xRy=7m zs>8VmdC$DgsVV9oOc|tJP`q8)$Lxl0kF(WqAw=d)M(B8bAke}UrE<$~=?-eI@X-BM z6R~rNQC5MqAa?vMf(+8g#g>f7SjKA62(!^L+uk_guQx5S#6e|_+{80KJteRYRm6QW zw~&;PLwDG1vR?5i%aK+xr~exKv!DYZFG1zh=|n9AP%!zSazqmkicDeRkuOlm>8kC? zhP@vw#78+hZEGWeO1fO879_EP;aar;DyvIcYNJ8h;-uQsigSDiIw3R<}1;GQ8#prAA0O#Vv&h``ganAu)!oJ!)lbi->{KlnAf&W4#Vmm;NzDz#aYSE?l|dVnC9=4AqQ_+{bkH z+<0hyQa(56Es9J#DJr0LOiMeQGti3NJU2xmcdvCB&6r;<)>RDJsXI_UCJWex9Wj|w z8lNcW+a3KWD}1|5YB)_>Tv`=wgu?Rjr?K*zhQ6U#b^RQj^6S{>ag$c>UTmg+DwUaP zn_Htv{o#%o7ZhcbmKg`Gr11*kFAl z!$xJBPFZ&$tGB_%0~G<37>E|QZ{h1oqn_rmB+Ik{&Y+TtzUgUL%)!Or7(b}QCb?dN z<6!OcK6{3aFLLUdSjfBdL-A&~jKXl1uZKa+IRQbPlQ9B!hqpS&t;6Gv^5paN5a6@1 za!~klg?Z8qh@PB{>!g`x0`GD}kIzJH9>DnoVyi=zQQNG?HZzCnGgD(Xm?Zya_zm4r z4bV*U$7og0d)_u*JRjW_)3jk4fqOI3c1WK#=O^03OMHjAAEAr z*6YpUV)uqDPI0HL)XSt$XoSw4|B@RN*yq_nJ@L+k;zu$6p?aW$_G2nfnbhRZEfgp80lr#CF_q_7VezUvi3&vr(!6#3Mx}0~VR$V~>B@O?|E)tuLqBLk zNBS@@f(4vzXoOuh!-!9DXCB|lo^8V-O`&kfvk%(_ERf4mIH`PC<3+|6w1O?e*uRX>+1gtYcUPqiCbeOWT-(En-QgpAy$SAcgYS5kc|^8>EP0?VHLc z0t(OCz<^|#L?mX}G$M~z)!r^EMwB5Tg@{_9o1+qQLMxjFgUY4Bx`7f_OV?zmKy*vd z0UQeJU~kL?oSU0ZtbdUOVU&o_Y6K8aDMH)nnvTm&oGLa1bRdIwjc8rS&>-bi5`(@zg3XjqT?42d=x;t8l1v_mfq{h1KB0o^m?2iigt4cOkh0hRI06D!bh zd51nvoWiC%K5(93j|_Y;Y>b&mzjD|>q&{q%f#|x1-SlCzC0Gp`x58@Jl4=ZFNlaoG zHZZCU8{C8fG;GM$Nr>@^lGuuNN)lY;zg+emP7yg8I_B6^1CbIqjKqK=BE(SviK6g5 zhFk4BjPjW?542YDAip}oK^dB?m?o12t}3ELoh|XwWB6^crM5E; zV_(iaS_8I3L4r?~FlA|Hz~mEfe1t8vM%fZ^J4?3IDk~naB}OQtMnEohQHNO*C|GAr z7SOWBnh-WM)+Dbr^$}PJ(hNGVCMrevIBTNwus9WiHIcDS83K?Q8$H7azD^11)i5Q1 z??wlhGy^X2BwQ-#fG4K|Mu@@O=7F^IXZJ6fy>gfea1W=Hs9?;g0Kr*NfzEMNWFXDd zsGl@kMJn=d)#v~$VML+>8;=OU$lnI(Q%8Xs9e|I+bO2}!-8o_GQ&V==h(W>?9l$Z| zK@-&=73n9GR?)}VJhy&jX7I;%5=y2NqPXP zF!*G~9%2|050XpZ%Ap;#?@ec<8$SjH(JoN5bg{;lFD5dU3Xu*&*?ZU@DPrwW!e#@8 z={Y_l#6b$LA=l&$g%2tDs^p>J!j?+z5zl=z5$PI-pm0bFJU&wjJ3my-z;X6yEV8}fda_yIU6^CD+3$OOe zRfof`C&H`!UX`30emxmp?f0tWrQz4p;njYbsv|a4MR;8dulCD`hF|xGSNkQr*zoIt z@M^zIb$C6fU#5CE*HVGTA>viOC|TD=e`tjLt#rL_q6BkbuugqTPnGWN@hYsZYXT2zPh>y1S6qZn)| zuo=}uGHlEec$F2t z+=50u81X9|B1%ZBxOHCI*)A=$V92NeEYISC0RfLK&Jr=r=_H5V_lqLsR;=v?PGXGn z4AfWTqta5XydV}h4&QCU?hi<~wAo}lEImh}q2Nf|p&Cdq0G4KPmpq~9BdH6BAXiLd z&2R*Wm@JdV%u0~P9=Af19 zRv3hwq%r;RRRulK;sT86XcrxsFJ+zrB-)!P zt=H+EBWX5@*Vn+_XWAX}h2Gpse_LC0s0G+AsY2&kRnqlJR#*l&#AARNPs@}T(pCW* zYahO>fsOhru#G4&ehf>XL{&Iou(B}?z-DlS|xyc*|Jb2yf!f;4<-#L;rCHd&*H0UWJ z&%ru$B_1XFkHA{8ys9!rRsvKI_iD^sq~aA&g2_aHptu95Dy|P5Z%mVxFHPvHCI-#Q zA@qGtoyZMdDJ8i3@q{zqVbg}Kgk-aA7dZFSU*iYv@&adI166i(ws2WKv7OhL(;AnF zlMAudcNiS7?!%sprx=ID$3RHIHY;>u$FS^cXTS2cWi0&r7aL{J@>nXo+YSM%F?lFF z`gTapRvBoU#j}quVeYmY$#I}AQ0G;jnW4lYEP7g#ACOmQ+2CRmHhXx&vZYUb z4yjth`k{&9f$yjTj|ntlj|paE4W9xGUYnm_N;p$&(+wtaAkRj7j)y$b@2kHD|3Qvj zGR-BzffTX!*>x&QM=#lV_&4$e;4cqImkZselSE{7i3u*nOKO)y zW%7%iCHWZ1+=86Y#;1O;dbP~u9TV5W4qTQDcd-Uz>c&YS>u^9w`;z?Qse?n4j_DkR z7CCv*&|+nA7hfXQtP#YW)!LSdZRRBoY{-R(g~x63Ym|zJNt`WyX^Q4R5ZRY9j6veb zoEa8%aTBH3kf7=G+vc)KiIh5#l$fF|{V_oxG$Xk+RJNlAnzZS}uJwT^9nGf6rZ(Ni zjY$Fn9%4GOvfD*Prt8!dF&(5z;!-y1%F`^s7abvX_?keWB8eo<5^(yUk{1kjBFCntZgMqaZ>Q@cbA9N7uK(CAZ{&vJ}oPJEQ4(zFD=4w8p14(*P08vNq(@N&D zl8piv8N{lK*2ODfk6ku%n#>n&)v&t8P6ooZ{v8if(USm(Ek}HDwdH8XKnN315Fkkh zD*BD>uwf>;rK!_@;;GuZ42w$)v5rV=`OCgcr$4?=U27|D2n}#14ab9r@6DQf;pYjA z8z%0`j%z3c1_hXXElzMIXU@S-!pzfVIKO`S)Y9Vr$KJa~`E^}&p7-&q=dVh-r7Asa z+1CC2B*!JNVui^#YCAKdqZJz)hsI`Q&0=Qd4|)x2!IV6`BHN>7)}(R8ksXnUL;Lt1T@X0`|p-N2gs)&`Y%R< zK5!yBi3q`{qau_B_%I;juB{4jHQ|7RpR56i%NoY=gasl+R>Me4iq8(wYi^@cg~t)i_Z4k3y@A8qikh1U=~|Ska{R(VChe598o&vLar0wZepyU_tt}tY$JiB79IPHD2ifu}@MnxFZWqwkw zm=mzBSR)=|poL*5;zOb_$|f1hkYaZV6+f(pEOGFR&I^=ZP#e6?i{I3piw$!!XLHn^ z-u3-q)7@e0&hR<57Z2|xhCNQseEUA{Us$NOSt2;*x|;z;aZ2y{+%2sFa_s(C8UwU7 zxEQ!Dnv?^2%vanh;-{bVdj8(KgS;dPV;@dD>kQ-wM2fd-P?FT(*hfeunH~V7;)B)O z;>nA4Nnw|nj;^4%^xC3^4P2Cx25FXdysj=oFvZM>nKKbt0H;3W$76 zdE4#4)q>Fb!%g|&Sl*YbKs@zPuGDq8a*7;KxFcn3GYr>jt?Wos=SZv*gGD|B(4t*` z&>fO$DMp}V-$Mg?p{6lAu+6{wTOE^6ok}K)9dc&}HC{X}$l4luPG_~OD=_?up7Ne> zqB6!XNwJ*mvriLj^^-^cXJBMo!zH?ysnDs*p%3r&BzInjfF!f;drX8Si2*v{h>4xc zTp)S)g0&hOteZnx#O9D^PC_VAY`uMcwc53BhIWm-wfI%_Q>l8&N#rbLMtC32OOl0| zx*1%A6zPeJs>(i$=cGRdoGjU;WhY1xAD?h&C}uPXe>tyeCd2RBwNrdtFSS4qAGXU1 z?p4{ZSlQLFEK8(XS?&~{tm?%otIIxbWxKH~ofMq1@P{ZXDh_<0<~$&Ww{STm*eUie zR}AE}Jkbozlh+DZVFxnTLm;Kr@8Ai`g#uLZYbs{Hi(ilR%7GHsNNrIje8Lb@)DXkm z2lZQ2CT~fGr2Ez(Bd0nq%*#hkDWSszUN}{aSflc(l!7?eg7oX-V?lKN29Jo;UAB2JH` zz8DT8TNT%(#|iN+*)oRGb@9|Kt&sQ}v6p!$ihN|5wz|<3b7wJlWuK694Wq;!BN7Pp zAk_o&pO!D&6`o3ok;RWoUtTYZd)KhIBhD^7Mh~SPw)Oq)KXe+W-&rP)O5-+FDG#Pl z-P|_-;CHKj4-=)!_yzD`LytBVA$?o}$fW@&`APGLk=(M8-U?OKfB56<=*-TL6jIfAqNq3EJp^u&0EanGnxG z9)i#v}lsdl^Hlj2Gz9RfY&qp@h8*K$!uRO(v4C;VQL*-9x50lzF+ zR$yj8(R>4s(Dm$C*4OD?+9vZKuTx%c*B9}1>90vfK&Bjp!f|7sNH%PlmKhY_3dyl< zWho6rnZoW+R6=cXN4s%nx>+Y9)b2{C6iFxu(a`nI}()HT_<(?(p%lUP9#^a@oW zEuqfhi}!01Pn6}50tPi(SLChuA}c5=^=G96(O zpk-3v5gqQZ|0MFjLz2vILATU)y*f2f-O=6 z2r(3gD2lfY4swr!bG)K_svtC8SRxpdbuhxS>dS8TR!D}zUQV*ovs1}JvX(}RFA1D( zLRqPfHV^j^4?9K4#TmUuYF(6vzm@Dv` zZ}jDBZHiNdM7koml63!%OumA~I}&B7`l|X&$cv>R8xo2iFt7bNfsnO_d=!S|u&C+? zNS~=-N-gu6$fn1Q&5i2+jXa$Y9Dr8Rhmhui$3?M8*(bA@PnTJwv}2TzL%ziW+8bdP zHviaMJuWV$8Zyo)SW|h50IBLE^?XWL9AU`{5$a8pihr$kVNX)YWKW^_sQx4<3G+w< zj`WjYAc-Eg zhUT#^Pz&j+S>7Z@las}oVyTH6Epc%0#AttlTBD6JYSB{Vu!Z%Lf(GZ;D7_`yvW-oH zxw$V9os?Z#R4RA~JvYH-Uun02iPDKaZa}mSW!V8hyLI%2#IV>|I5C0R!;qn`4m0sA z%keZcg&TA};om&Xh`j|vxXV)uN@7B?es3c0-7_`j5e>Blc9M(68#V1_zAEh3+0F3&MkisF@@h zgpm!gpGu1(In%xH!oM*{1uqDsq90`{`T;-VzO&^!JRdck9hYojp+r#S6U($O|JIvv z#E+{kLu~UINYxm%2Z;G(#E^j^w3k@~+*2*(6(WycP0557Sl8eWQ4$F3J)%vh)(!_z zoF|%(JXa+74d+K_PY*7I1+#N>t(%$giy8@|-NB7VLnZ9;})|HRdHJ3ZpKCT))!>wJ(zB1prlb}Bvc&SuoIyr*@ z;x}fb&k-{LkJfM;SMww|u<>wGc|9IfVYO}Bokqx$>1(ks)F87RFpO%qu&n8i3;!J1 zDak2Y_~w8Rp?VxOfs#^3qv{GN(LUa8;abB-QW%X2?Zh41!mgoRag?+<8opQCA;z_@ ztA1qrxW=`6%c3o9I9T2cY_}ScepOKnJ;cst2h#2yCe)aSe4;;YRhDDBO$dtipkeDq zuI&{09-T9lW+;urzDrGMz!dAj*-jD&xFwQ3pbN&5<6fd%USGyE&O0_!jM;^V;Gp3~ zGKNUC!M9i+X8osMmwd z^aQ-_o<{H5-u9qcbVwSWdSXG&2PCm?1x$ zYN@s{zAtO?7d7DpfWbdTzoO6KS#8V=W_K;_H&RzQAtw2R9NQ>o7W2bHT6*3-!$JrL zC2Nb{moN=)`=lz9*M();cQ-GrSbZO*G+4*5;G?3r7$NG_o-e6@wL`_lx?f>9eAc72k&6JNN|PBYoI%-(!N7 zofdU_c0BL2+NaZMB7E8UMd@WKQOJgc*JyEPC>&Q&ZE8U%#$XJ)8-0y7)KXwIM5Z`w zobZY}hlh$UYhjGGhN|AyPh@{;^{mxLm4he4Q`RReCp6MU%z-o3l)9mk`QrwRO0=iB zEt)q$Liao28kYbf5YQVlgT1tjUfZb+afD2C+A{JJH)v{SnO!qG5Hgz`zD&9w%Nnjd zLWotzJ7&Tx1Lii}heNjL)p1SpATa@l;<6XDa;mS3UK#s)21~2^-ccf9J!vE_j?$UAaXX!*5P6uNC=Zt6hRJ47>oD7JN z;&0p<1Jz<~Gj%$V3_-`q3qr$(#0p9G4~l#>7FI1S50VCNxZ!~c%i<&MA?uw={v<^Z zze}kL(K8Yz$arPLo-a{3=1Z1y$ygrGaO{LHU_YTnK;7t-SYVz{ZZh8~KBL}|ZOI*e zB!w(Q?RQ436u-ZZF2WZh_Pw^P^Zjalq z_yhZ)V#PE1s`xYgRWL^4R8s_w6-e4lR7M@!B`tR9t# zgV&U6i$AxHX#oDRT%(rOsIE8!`EGZ}sG)DxxLF?g&f+ihY13l6HOU&){5%*k?%Dbm225|p`Z zBj^COW6HD{(XH{0_10;Wc{zhOQ5vrB4qcb!x*GMJ($ERf| z?Up#y!YEUCOBrvW(A4$Jk>z>_M-wMlREC{DG9=N~4hAm=n-X)98B6^kZ@F@0agHU4 z9u+7+c{>rT#2_mE*uUf*hr$pc=(}STFBeby0zw-w3UVMD*;N?G z{kt)7=4y0#o#d(`THs!(YU$m*%gPM`#U$S{0_B#(mK4bK$vT6&?#5M>F)V9qx0I6@ z1dxgD5q_(j4tr~8d=mS_XmJu+oNnl>TqQ^ADnBcIO*tJ&O??LaGkh{#b-cXnE}Vw~ zB1EgC9P35L{h1PVCg~$VM022(Zi^q;1h16jJ|BzzTBHZI?}GZ=E38AwsUe7#NT@hK zd!;BtG25_%Q6pZTN<&{D~1=B?5>kls_7V6B_bJs!OP$T&G8}zGE@+U(udZHEE9sji{eN6ba4}Q;jrFThiP}qLSw8 zBLJ144N4u5=F>p)tPjEl8>P95T|}BQxkx0alu7fP$x7HRDpVQQsP308&EpVWK$_Pr zLbi2lmmtjrLV7GmLX_sBer5OPT0qZSCXD^%qwrZC3}Z)%Fr-1~XjsIK%b@s-xd^SI z+yMD%0^&sCEvD3?Vm#SK^2xMOu^VFyD?nN~5jZAZD+vXoh-J;rz?2iS19~)T)S5rm zltve_oiZ}(lBV2Sp}~^c6YZxe4xF4&V(ch)QO>CXnWL8W+NUbU&uCek*;p&x_ovI* z+tzh6BJ)zFM$9Hie4J$y{IYu8M=6mn7?4_2mu7iZO2T_&1*sLYyamoIrV^w=e59&D z$$*#~V=6&pc3Q+tc8&A8`{pL>3>c@52@4U!fB;mY;IzzcNJa!b2eRcaBSqUTRKfWL z$#m5Aqwje_J(9ziBe%ctiwpBo<}pi-=|%D9Vju9Gwr#jiX2X604l5=^U=tay!-DLL zn|MBetpF*7e9mz#Bzju*d;nXeVSP;kSDV@@j|~!e$c_ns%pT}Z=(L-O6;HdF5IRh9 zO_1N8!Qx~qCp-%^`NTVWc9Xlu4+$tGy{U>g^oh6jCZH_Muq-zU52JWGDF$wkMCOOU z^}fsr0<<8bm5NYQi~F_Sp(EgAh?(k*h>o>Ru-GwVBDv2AG9BO`ak+1hrt*k6$wV`Y zftegmApJRk-gp5BCVC2i;B3K4MV`YTnuT|((@N|SW+kIKI?W&pvz2+sN7a#O&B9mN zVPF})?!fDF+DrtwYQJ=u6#KOw+=9y3;$w{}aLwWsHFA~%RctkTMHaGr2uq@lWrYmu z!RP=4pwXPBSDZJ^R)npQqF7LyMx@%HR~BghLc;}=DltO|ot6M$lFQ8C5~0FNGzdF$ zr}(-7h*}iR59_dR%y^SqI6$U6DyFuSAv|z9BG7dt4B=RoSAR?_5`oQbw0QKKJ*7tt z-0TOd8#UM^$?nlczIy?+cv*{%K7e4#B@$aj-zH%(3u>o|5ZAn3ZT@yDo!= zVU+f0;4losxF^U<1`or~2`I$Hd-K|_yP=VR1|ys+ZLAo2o%c zrX#eBGOYP%RDG@I?q2v5Xkhth0e^%T5^P*@tL$4Vk+upBD>%4|vGi;dEr z0#OkK9otF&@~s&jMS3dO^UV5|Ymzg^fTp}W9F|g`f#kbbf(3;X4iB5A@D)vi1MJZK z?7v{g#7_vi0P$ZP6O~o+-;86zQUA?5CV~t%d<87GpvD|E_+U3ORUK8%-> z7xzBM@qhwddmq*blm8XG4;ye3U5fVsMN~+>RPV#8mAnt>zj_~BH<7a;P7aOt0kFA; zXc_N=82%-AAJ&^1eG%`&b7`{_+=|AfvGjj;JvI}qvXG~L+L&0pLo!*tLFu=!q~h_n z^2GAvAv3T|_M6nsBuzLlImOzO>gRf{NC|b1r5BlmxospGx>K4GO23NSf--(u{ummh zWmU*;v(RYN&c8^l=6OE1W6UTS6+pL5t?n$Gt)f1qfrn?MNrh{|;csxQSJ@i3Ks!J{ z?P3zm`A}`3JqCdW*+Vhc2oKl@(-H~l8SAZ|uGM#ce9&gi{ILmaGZj$IU!)%-9yvfC zD*O9V{2(|>;=uv}V(i>My_4plm*%8-URrZIHsNJ}TRe^8#ltP0InjdKPoz0Ppv{TS zEjN-;#04!FnrlZ((l~b@6*JWT&a4B`a`6qn6&+dd$wY5RbVi0NIiXPg=eg=g=?kJP z%(e5n!Wt7?e6bRP(TH57#UYh_+Ng8J%KMazuS<_Y@x4=fcX8J3is1eV2Cjss9P183Q zcW0D$MLpz+2|NuHked6XpY@gkU0p( zvv-3(Qi*?ljv+V+o{6WRKqZ45qy721wf9Kjz0O2OExe&$7tb%Fjl8o+*C%i+O=dl3L3M- z5=OqJUSeYjkOH0TP4sNyHg~Jd;%gOW;-B**019We4&YnV0*h^GqXN8(mNCTBNc-VciEuodot9S@ zO5|L?2LMkuoY4c$<5KQ#0G&=Th$!O}z!c`KmQQ$)2;D~DFQ8Gc6GkXKVDVf2+APE#CDuXDN?r>sM_|I z;mfYGP$@Rjh*w+ga!oA^)6i;f&|r-pU`^bfM7k4FUmdEWV`5?h)tNPP2MQN68ZV$1 zNbFE6{kbmvxGip_Y+S{A+bP~FHCv_IXWrO%v+>+WCQYLDRfX-8%RPWO?y#`t6&X!% zk5-V>8=a>mZJgClFsIaqVra3MvzRt9FrWFEH>}W;f<+1dGQ}L6k@DzL$-uPz@RH8B zSv(XIKh+p?s9^D9HdGz;7pa&YZLcKz|GF|kSLCmAL8~)Dk8)pA_mK>OemFC>sp$I% zkynvd-tLn|RuQ(G^_gqVaA8VBJFw%gpklf>3Y0*_rlMWafC^!sJsh@TBRuFoK#uJI zC!`eKuKioK9bON&OZwuO*M1z%p(l=|%&UFrJXGnt*_Y0;?)FJZ5S}gFmu|#Nkyk~J zcW!%!p+foEQQk_N*|Kc1;sHU|J?`WnwvgqK@IJf7O}@}}N4X6ZRGzWE6rQd&kt3|d z(-wEn0JK$Ah7n$Ljh~}2QG4~IaFZ;GbveDuw0O|aYSy3w75NT#*%rlWLpnPYwIQ9I zkJX0s*9$&@L&X%9q1z4I-NF^V5QyTAbp4Jy(mT=l|7y)G>Yn~gIJHWg(yQ&^KYwar zJ_%c`ig4l+`th6|Rf*NUFlGwGcts0?>mS)p;*^Nwtjc+S3jJZF_tZKb6%2(_mNw;MXq-tXts3^jjPLz^EuoE+{JWJf>w5^e z(Hb4%x<}J$Vbll>983Qm19M~SRpG=Jh)xn}JcAMy*i%B%U9Db@+M2E&`+sE&q9)pg zMZl7zrw9WZh4x#Cx_CEM#EX_bqu-%Kqed6yQP~BXIyKPI2hHq z;FU2w>pw!OhV_hfDoe~x{Fk<4RJG1OAz1vJ%ssP-OuuJ0SLtfKwL{M{pdu1+ z%$K`4CFwTy#c}j|OcVsk?CuSf!aqbc;akRRv`!h}DUoJ8SV_o%nzS8;?NWSa1!+(w z)EHyJf}1FQS7?TCVR0!($VDl6k_O>~AaCPf02w0cpg%xV$@bF@Dl-M2d)E_S>YV<{ zc-o_Vfn4vRiDWIjS`chZXafqexacOBNYwSuL2^S0#x&f zgs2UBQ3j2{wQ$5{u3d~8sP#woc_n?q%3<18&H(<=_CXvIVrfO3Th7Eud%`)4MqD@nxL%_++_xPjkKI zxGEn@deM2$4lbm~tB9Tn`CeLT%~yzKQvwTzym}4YRCu1D3p({{-+ z=cL|2XhsT%1H9HXgic0;Wo5xS+%q9N{6V}0b=Ze7`w-=bNMXZ-z9J-s3UNz))@aT) z!ZI}$zonll`ET99qBJ?i3lKNFU|Owhrf%2qoBBvYlFsSe?;k>TJBNB$* z-VckA7)?@qI6kb*J#sA_OdE%EGkPBgPs%n`pdr`U)`a9v@#jvk zwJ~Y?sSSiJbMo~25iE<%$uoLbZ6kZ&y6hFrF6$Oon8c$K*n6)0Xa!3|QN}~e5pzPi zhtmN!J0{FJ@Oo$qh)G|39i_M6LlR2xtd&EC8|M6zoW9i`Dc)hw8U>Bg{3x`gKjwC{ zByFcSBFGc^0=gHc<*E!yzQYX%OVKlAj^eAh7R+s3062Hehy5u! zT@*iOohAZ)`A(B2kFT(9`)h>C)lg}FT7aIZ_vo|s=6TCZ7eKA0ks0-zYtbhznVw$| z1lJbtw5~te?jzOHkzJR!pH}-q@-Sq*1ZVu1hDnd8y|VkXRVyR@FZNV zFHofxeW5OIuheHHA6V@IQxN?fafs>^An1ZYp;g=XGPOxUX(LEE(=W=k!p}EEmoepQF@lzLEv1vHfnUYN@Im#t8GMiZQHzs9~gMbXRs zE5vPoqrPfJP>#kn)^uqB!Xeq6F&1-TB$~PYmC*~$vnz_vi*~eO*VyTb;=M*o7hwQ; zGR?oMtT(Jl^*4EMp5nG#p*L6gh@YlN&r5TValgMp>1p~to4u(&s>?n7&Bd40r`{G? zy;`m6vsbt^P@oYrP(S7VAB0^E=7#UqLARA+|x2hJ6fR<-*q|R~F$vi%3J7Q>;*je=>BAtUn(?LiwQtiuA zWG`<1xxO}UaYy$})KAwXGHjP=PJ{;$4~T%&7phST~A?tP|L$B=9l!h3rS8PO1Zof-ZMp zQSfMG6Q)ZcW)n8;#(10fKU=C+*|y@SsRmWjGV=Cjx{Is@={<{ik_B(%lC`eN=xn3d zR5O=RSe)6anM+_QDzM47!f&!|eA8evanid=uyeruXPdME97u_P16{~B`CA46C#l;4 z99XIWXHzlzGkp#KMk?Eu^=rcL&9?iQ0M^ zJ8G0EL%=50fdDbkCs==i*%st5$qo%t*RG^@4OD43F@iB-USY<0w0BLnye4+ZOj$lP z*})8{)-O71(g@CF&5g{0mThZLLh=GWlJHW8UArm1uyiyg-l)ch8&lHdz>8;XM!<_b zs5UG!RNpuD2d`7`U%WJ|z_AukV-rQ~fn7asrB1x%Q0 z!#Q1x%FE*$3yr`WhwchVw`Q+nXCWj%ZIWnO2Y(S}z}9c`b{Mnsr{fMfz|W5j{k&E$ z0Rm61i2gfnx9In5^LzV}!mn;#zLuBhG4uCoSC-YZ(_d5A>Or8GtVjCTiA80^`gv^<8qzg2|&pP1HzY{dyuCnDzJqF;q{O z*Gf|*j1xk};c%6@aFx^ZPA=@()%3}w)QALaD8(OWb2|nZNq`_Y%`vtP@80EWk^59? zUli6mI~lGJz?l)2i7cRQYFCx+%T#S@YBsx;x~B&t3oQOikp(R_T%*W>h|zqR$bu~~ zvOrz)9;-!&nf^491x%}wD@|y@>(m__&16vV4{wR)C=(BMto2&mi5my7cUi)lv-W4G z+$F33wBGe8EO*e1q{fVA^FkldK-ed}MUy$faM0w+p#?h)%@++V5cO;zk3}bJh-931 z>5a*sWyZ%O&L)L8u&WWq9&cc_uRNdRvY@IhUX>n*K)E=9;?be*t3{k?xxT#Gh+X=b zw2g|`rHGJhJSf?V&gBZ@IdCC> zmuu7wHoVLFlG<&iYFz8q?6raH4Au?quaN!4SC`%mK2jEjrW>BJqw3-Y1m|cdV;8R$ zja+T-fR-a9LJ<C5Md9o2prQ*UF}~ zu;t(Pa=PEick(x(To%61#A}8XEJ>U;CoH5 z@9XO1(T7Uut{fQe7C^LI$6o$qnPIW;AzK8)O~E{fEMhdepG(<||AM0eY%7dvnX@aX zH$%HoKUuC&Pi=5?jIOmz>?K1X`zjr8d@OtchL#&jx^yUM_a}$)aoamRI2^#Un~AG^ z2uDXHd~W8tVQx8-p!~qN9T&)l8&QL6-isj15S@1w5?M8iQCO6-*we&($z0+d8Vg5s4-Nf-P6()5c3v^?)&f|Pvf6XQKKe&> z!bI#6!LnJsrteY73Zj!vqA)t>M=(&b{%bu{ZB{R(ELs0b{gSVTco*gWCzeh5|0zJO za}9u4L=Vmu4=EZ{BeOw8e~(Qx#2BX*E7nUGz2NN$ndBTY&wI;h!G`+EX(32ugr}kc z%0jKDFrKM71Un1D3D9$I&kGZ#X$u)=}is#{Ri*(S2 z;mR^c?g4UDhuDVIdxW}Lihl)e(@h4Fjg{WMYQa%_3vh5NL-8fkV9oo zhD0Hc&^3L61P|ACf#jw1m|4&rcN~vWhirf)kiRi~ zC)2oShR;x-6GM>Dao-&9RxbT_X5>}sDwhQH8Y287e#l=0$5__x{otM*6d4{eDKcnp zna^Y9V^Hcn>pGb5E^6N<1FtXmf`XC z)3>EIwh}o=PdlMYNO)cH8GTWGPyfVbvG=r0G_*!8@B=XZxKw6H^*CCCdx0V8;LNth z8?h&#kWUU`f+p{C?4>l=QMNc7x!;iHkwIO^kZ5c1Rw(5V4h~ERp1lksWS0nZ|G^^* z^Y^{ga>dEw$ZH&z9lS9B65B)P77S^wwd`#VE#I#k1^`cRh@W7d?et!dk=vFEqiC~^ z74s5cdO_C(vI+WV-W%o@10UmwXytjnmt$_ZviU(kZu+>oTO4^SHIWNoJG7-0ZmDj* z(oK!KjiQw3OAM>f|Nl^OtLCVl_ZT8 zB^msXN4f`7337DanAaf3wn^nZ2xH;z>>AJKD^o8|Q~t@PC72xx%_sAJuTO{kNI!iN zlJ3XTCQKI_D~*Ga^-%nxNq3u>vZ}ixD&{YGW(~nCeyA_==<88s6Zeid9;K0J3QEEw zpH|E3ilG znGS;Hv$9u^womEGkrz7SM++A-SnxT6Yk+muU`4Ds?O4Gkbp9lQ@a9Rj{mE&1I_u<>}(#(rshO1b&coTk<&u7&|bkIC@M`kyw(NmKb9O=yKdW zhCKxLgi;4@$<(ukjFDp5?@?N11IeTu^|b@4CTXXEV?B_;UD>|Ad&vvU#w z+(b`|hCwszG;;H#Z|~K_Eyud)K+3kjuPa=|XFeEl9YZ^bfY<2eDW!u5&>`V7;bYS>lKQDu$tvU zOIx^L#g_i?q)36MC%B4$=5-YX&C|gPu=XzYPo3A5m*;fVJ7pt18(*G@FHcu5_2o%^ z(CZVTuv1hSPRmHFD6F}n?*O(_xfC?ppyYbAtux`lhG&V;?Z?v;Kz0uh6 z^wycsHd6zu(V5d8PfkHwKdoj>Gh_E5#VntLVfbVw@(ko*qFcs)Lt#hE+gTBYdI`LR zOF(){QyjcX#MyV%S4M59S$rhCyTR*IG0W7iCmAE(Z?Bs}io}AfZEyF6y*|_pS!Dhx z7pTvGwc|CgzrHaZmc&446+h#E4QI_$$2&T9U20i!D9m^v)~9lSXRz-N*>If(#eDmr z*8~XCx4%hl(#Bo2U?loQ>iV0QH)22h8c&5VH4k;e(%2T-m~MD|3Ms2;$A(2`ybrXi zDw9AUvejGH2!YK1^b_+;d_nJ(l+=C~k3x?L-J!t4Dj=o0_<23N-XBO)KvTvMWuZH$ zrnbqBiH|x|7c&3`4K8zJcyCM<9k!Onq0VvHEaT<)Z1!3!|Ju5p@US+l7Js0YBnwog z)P|&UIhz`A3U(WjDuSmWoxGIfSt|XLdIv;V0OUvX zWi>_94BeOw1{x``X)_xd*gM5Xtiv6Z+s=kQNN0N=ZOkXJ4q6N4tgn3p3Hx0czox?U zu2QGkQPk==m*3paRG1dgO7w}1yyMfJ_O)~P86rp%Y5Wk1I+t@;fNlCf9F*f?DTh+r z6;^i5dnf;zsV|tTnu?kDWVvQsQSS_N^%QIlroI+Ar`31T9)nM)1ZdXAp- z9qb?;g~20cWj4Q75ZTci*SdBpMNhcUjBrAD7BvFwlZNxB1wu{jK4=*@1Gw~)tp{)! z#r!i8w`nj0Mmc|4$b@EWn)POCNiUB3b;p;wHRiyrt}b@WAE3^NchQTpow%)BY&hq` zoji#WgIp6Su(ZN`Qq>6lf;~f#_6U@V51WJ-!l4qiXcq;@j-xI+l=a%{IFqhW$46ov zXWMl!-(|(mR&|_0UqbEY)@dMJ%^jpVlUA#^Gw7(OWu zX9mk`^Bp3g_;Gz2g#l>wi1iebq}XYkT)L7#Nanqfd_#vKzGXt4LfXs36#RqZY~<1^ zqyrdvF=}ZZn*NQPsU|LOYXs-3WHm|=ZEb@SZjc4uRaI$#Yi$}-Pkgufz^=hR>0}*W zfMN_C^Ne1wL)cfvL)fWF2r>DAg1y?+X$rGmjn&jb&fu&*K6&y-pkPBb4+(*#QWwR)h6$26Y{9*IVMrnk)g#M@tW~usQmB> z4I`-ZdL+JX8di%mFiZmDj)tRBTDh^YKPhvekeYC z6XnRKjQ3IBWskJVWDmzO&-lyytgg)du&fn6p{qBNJs!)H_KHn49nL94(pbx+o~jGN z|DAAN+MZY|jhZx>tU)8ar{(oInGMC8o1!e z3rCEjFRM6uf~YEQYe_R}@W;evUXwGs4OI#|kF9q@je*$NmYh!~wONmcOAW--n?Zjb zl3iF*{OjPV_~pvIkbo;qd*{r1#fQ8`>H9Su?DJ=NLof|h#55sFV(wG{9wSY# za^y5Ij&vn%OBs3&GmGm0100!lZrDDq7)hUla2m?D=UT5MhUWv8b9vz;OgJNC0rNDY_4-MI2L>st8 znc_qrA~_W@I@i0L>jDjKy2g2N0nxm*MQUhgSbV^RaJMCV zu({%cC4lV>@`2H~VwzWr=~m!`{>q%Ng@#1_9^B&$(cdIlHkG7BXZ5PdiEgJeJ~k?; zCK*b~+~@&;Io3kUf4}cD^CWQz_nX5crrVj=0xO6bc7$Q^$5e4#{6Pm1Ys#F@6EXvTX@(u9JCh(af+uRQhI7BI0Tv_N;L`m~TMmd2`s2pf%*JsZn zro-uX!Iak{i(Q;Qr)NKgxaZj;NJf4k1+UL8@SOVvEjS#lB<2}?+p_=R;&XYh>_A4S z7bsY1D(CePOw7ldyi%c0tlr}{Ps}hqi5fuo-A#T}s^QSZ@ENxv8OElYD)CiZkS1=b zpS~{s))e#9)L(v6r)E?CQmUZZ)$_Vt;3RtRUd#ITUJDeO@d(QWEc!~cloy|@dOHi~ zAPSqpdYh|0ZALbINRU%oyt|P0ws?!NXPt5jK!;(4n)AADzCOb&w+Z@OF05@TFKZe? zHLbg6p+y((-ls0Ld!mjP)jj<7bPq+@yB8ahod|ejz;6ma^&^t)W8vrg8W&79L5pRZ z+M*kJfZE>T)OG?Q02D@H6}1gPZ3}t|4e}Q>ME`_6iT;Un0ydQXHE|XBg%;~cCbTMI ze|ehUXz+Y0?_Djpp3Qrkb$vSTZHdJE3L`B>#uy;LNRlA8`A=Wm(!a)NTN@B&@r_&) zPK{_e?V9V-FOK%NvP##(f7C-rxQwFKt-Vbm8=1|$J{Q*AdSC+-y>@gkM-u>b)1$9}8zRtlA%FE$S7R$>`crSIG#jeoxn(MQ}T+Mt#UpzEaUR`dE zq;A2%v+DM&oH(=PH8jr>^K5ozw08|Oql&5Um@1G=i4|HyG-wUM7S<5#poYq;y%BGa zw0d;jVBo9F9oIl?Y5;1-hWc3`i<|`YCak`GnE|sb{9jf6W@EQQD3gNmBZ#1fbkCp; zO2HIsqh5~874LVwgPyAb%088uXu_d!4B@3wm_&q_OadMzY__6IbVBaIaB;|M35U=f z6*6Els6Oq0K5Wg;%SNL{ow_Nz2W`}4+5)w#<(I4fyni|aom|gmy}t0Ag&zd7VYt^9 zSo?tKMGQ-os4}nAkfLGr;;^XgpKDkSSO>HQHKr_kF6UX5nB~@?*Wb(Y+SL$63=8*!o+bJn83vt^!=eRK_IOnI}i&5~BRM2xdpQl>2iHsBh8IFTD`+WyO1i z#Xj>YS(hMZ~40M(m3ttsCa z?RN8r+>U~^Lafo|-ZkarJ@f%hL*gBq`F#Er+w1rYldd(3Eo#NQ638Rbfz%DtDNWN+ zPFUEl%)=kligwgHHfWZsHR>0qWS|dRl8&=M+LzS)Kdf5^cMJnU+z}L|hw1#dGhSd7 z0%KVS+QjBv`{q~ls-J11$->*Bwsy#_F&3d@+MB@IHC1ZuGO84*o?h-z_8Cz)s~$M~ z`HKVPY}=VOgF^(9UKw==b@(L>tNDl*#G&v(?M#c`mWWd$o|y?OY{%nQ8c4DsHbqfw zIV@C7<*@ks0f&XO&5~p58t2C9R@tvw*{K-bah1vvi`A4})hhc(R9!Rk$w8nY=o z*(&>cRyGDNO`z4X>f0_m(JK3;fwHKlI@P^ZFAwUm{ic@m7HG%?FVbUJ3J=Go*T<$$rLILnEq)_@Ih_^@s=_-ZuXSbbof1n0l;+yY zR1soyB{;*m=Q7cRSy}n+xW1S>AvJdbL&lXbxx|&A#SBwMm-sAl#B20f4EnRBGfAi| z@n=h6UctSjX55Wnov$2BF;TYoVijJK2eh2okEY#6ACzfVgiqTw0=SfEheTW~lRzoV zi%q`N5|i2aG7CvfW^SCfN&F{_GAfpogyZydmRDo}N$Vq^tfr#DV6L^1ijFo^M5@Hk zMB3r|dF-JD(GRCaAJ-M?K335ap-TDD7fnzvNK&O+f9BO}c`hfGkQC@yfuta)Hc7eS z=LAtLmQ-}Vgt)ZfaRuViG}yR^TLflqV&YG5K1GNwG!Sk0^27jNvT9uMWxl|wbek(z z9NBm^u0{NahGH&H9Nca2$-yU%Bs4^kdn>Rj6Cz*r;7q6KqR76P-ijP&K&q7>y-q>S2 z{HXJJ6rd9-%aO`WSwL8q{g9QdW(})Xno&zP!A<*ebNkp*8Dk`mE^4mLmq!t^utx-q z$+@9rq%L}S^IVK_o$R)binbrQxTuGH@5o3gDLe8hS!6dD9 zyo09WjmApbH8fK1L2Gy{mQj?mbzYJjI(pd=2}@*ULM9nw+D0&ANQW=KL$l9hc;5kC ztLa@K90-JFjT~_$mEwC{1bIpPwMmMLYc*fJ$@8H3K{4zYZaGkklmfBUB9+aK^J zJN#1eiJ0Bj4SaFiWE^m>6@p4m#Pt+g;7-_)V~dpY_~ismHh#+$qs`*BSa~3>8M58n zRw>zGuM8w6uyA+~sAy}TEFg~YYk{}K2eyQET*bQBGND`p*83&ug*+m7s3g=BbCLZX&7 zEB&)7JE+aZKp*fkAEz4`cCP6EnkI21s2D&Br2A>zPU{owHVTJA9B%_f8 zKqcK2U7^lec>Py5Gupm-Mk`lac&xcA_ECE|DavCoH%&_Atp@O<4Z0E&tKw{VF$G#X zJ=*HHbmFSEk6Uf4B3}blrj`K(dZbl&P=TI^O<&9sz*re*T8dQF^q}r;Te*PTj>;0a z#d&Hc7g%qHkcv4oP@CqasnwP`?eQ&o1*`Z3z-x64YZRAmRPDlX#D?M z+O}owxXaqn(cuvzajAf{sFnCUW^!V9QDlbV9S23NBvn;*P}IIuNmbQ$Ns_86{7)mP zs@nc(Bvn=0rAVr(?4?Mms_Z37s{M;4)&91my4EGt7D=jZ@w1hr@{f>Iv*34sBdOxI zwWPY%ZKQuj{rZw5)rrND>aA~+=1}>{CS^J{nj+ec&GQFkz7y1T3?r_Z_>WcwU0cWV z8DBfkaJVcErAbT6E`vuaJjP-z-aXUW6f|(0-y7R{nkkD>SKH)<=ceW2Y)s>p=Cwpu zL;b6|KvW4)z?HRLsVgtZT1TI=u2s$;Lb~!K)#f)TpGR4gYKaTY@gK06U?O~e0M1y9 zOv745jP<4AhpLU5&GdFo-!30-VrDi|PaSPyktfpB0Y*5l5367k;pDNAe!oft$yvOO z;|i^AC~7ZOMu=8|b}CA4Al}o4jt1N4N5RJeP;!{XF*vxnHKnBUWSS z6_tE{r1kyr*7wJj|9)Zd_Y1A>&$PZjz5MrQ7k_`Y_5Fp`_vcaXAZ(cXT-s{6#o z$Zl2@Skib1gL=cQK!3za=92jod!Fl?d$2}$nzH1arp0F^TLUr8Jc~ge;S8zR zNVr?^ zXANyj5{s0kH;i5oBv^WDc4n-%pGE%Wg+(+jP>%xHcokw%F^aRDgiV`4o{6mrypc8&)y6u(p&fablQY^4x5L4O?K55 zFP*-~E+>1;=DsrD%{|%rgH0C+)%yV!><`D}lWu8;c zJf|Y_oUEDWWWzj1jd?&l@SF;DCxJu&7vJHR~E>ir_- zxx~h6=2_hQB{5H&<_WBh%%f|IdF*jyp0g`3k8eheRn*K=Z$vHOFxce^coWr`yul>O zkdDs`FG;VfSiBVLN=F2uPxCuGGh|83wBO_u;+CjSIW%<3`KVX8`t5%k`-4iRObLc* zI3Nb)fhNq(xy+SYy<`ht;oR0ieDijqf2F^bCht2C5b(uci#OEc9rA4TY_ffIkY@!K z0d{1ca0K#sn=$G8ap)UysM;xWkdsptvd1K(N7|*M8HZUK9f@RJY?k394s{%#@G8x@ zt9onYBP*y*8*@0=36(~2POw2GneUV%K$58$={s)(q|!){d6Nv)Ta1Dz-l#hdua9T7 z(fNsx!g!fKOxrIF8O4~^TdC3`cQ9UAXr6wPLb$2~iYmsTR| zs5GpzqZ|B8jjC6IwyFA5!VQDb)CTZ1Ma$^WNrPS!H+KAq3NV>&rTN$77wFnn(+;> zzL9kv>zJJlX-KC*?;0S~$xreuwE9b@P8r-!*OR`~fHq-@KmW=N%{An=H~ULbwuod+ z=rC-RXx$jEIAOfJw&%wpknDaBBTx>6@Q^6Dc-vb<*NJ?$iA>J?xa(Vdkd0GqG zisyyz)(2S_(Dc47d22`X&Q_(jgyklkN|2U%$$`4 zM|49=x7y#`;VEnSy&{wfnBU|mL#lI9Oo)&vQ3NA)?93(#^5+bd3f|K!zr9CD~F=%Z{hVcq}$b^plmn zf$^-p*m%}1GoHZgfz)C=c^GFrdDuG_p8KaC57}@eeycI{S95xMxQ|)nzVFzGy=M9I z3}wA_l+mGjX|K;nH!FCT-Uj9)7s#Wm;G??T~8oFK7Bt3 zX4l$uZ?o`zhVMFI-xoPHx?*4LC9vRDT96X(mM_}0oq=O+pCkJj0`Skiz{>d+ZqDlF zYHsE~%MAfZN6L6m=wIjdd{&chjiuW z_J?$(#4~r?kQ{`56-jXB4!(=`PjbIM-Y;-(COu`I;(p#wYOsmXvGdLREjdisb_iXj z^`ps+6PI7s9p5lEx_;f-nKe^v^DU-veP+90d+n7wbqDw488~qVI65eK;c|0-XHM&V zaHuZxoscutfZq;b5&StYPWlwb|F(qL^GbLCsrxFmOmeV0Nc&Jspc-lq_u}HD#Iii# z+me67v||$k8zJrpSMnb^Ge+4R$l}gkiN}J(9Ga@8?h2dU#2irWx$7>m$Lc-IFB@rt z;8r78z8JkraTq8Wov4WBA!mn?1RvAm4arv-s&4m&oVns{R97jiG}naIdft zF4O!D{ZFmxq{&sAndhLf`{(hJKP@N`0K&ehgef^Z^N%N>^p94Iz=^5qzz@V@n8=Bj z(%`5dn#(emV!}M`b2hAG6;aK67M>fd>HEBgj;Zm*yVzrcCoT=eriEPNqBCji9w=7zlbmd?UXdy$A3 zu{b7pfq0>7TXL(*B;DQuJ!l;xP1XT)9oaLbOTl*Y72!xCkw|HT*tAIv1XATrO+vD7 z!RvFhXmsq`wS#D{%uNp}$|=u^?A@B7&Q;xTm20hs$NNTD`_U0@h15YYx;=+j=?=Yq z`nJg{jbQkBpwp+Wx&BzmQz@kR?sDo@qXURw_jjT-h3UXaIU-&H&85In2-o2+I2Dy+ zk}irBM_gEaQ{j0C7NK*X6X+PAp3iQcydw1M{sZLOVCOUn>V0!z6Xhh*pFMyCR*JMJ zf%5RAJ=vBd{ag|%tO~1@y{w=ifkd+Ft9RTqiAAbb_emSNcaJO$GC|_UR;&45z`)>c z-H0Za-?O_v?Tlj_rz8vu%*wpq&8C^vVd0&c1ioOqd#Q74zL1e$PGS(8f{cVB)sP%Y{V&S$_|Yqf&%P4t&fY4O?)Nag`UpD9}wPa9y;h zzy8_5aS0O10ILIEq}9?NV?ZI}xfRUADitZh{eV)Dzy7hoV0HXhGDT*9OTJ%n0tPK2 zAL^VZpM*e!My-Vek_VH~5}ZC3aY~VcnH&K&9~j*{l=Ww75CXQDy+SdN5D2C~@t(<& zX?E3jvVb|=o3ZW5+rV6Jn;JaOk#NOW>-1i!E4rl_!1OcEcQT(&F}{_A zIG^>qHVy}K-P)J{X|0*Ty<%&m=(1fH5s0?=+1iAUMQ*}<*KvD9yE!vw?76v`rFIx<=AZ-D?mK< zBT>rqUKtswBil{&L^i5HV?!{|NQYcXVKaB>#5G_dTAC4PWS-ZcdF5R1l`f8r+7ZZO zw@nV0_5%StqaTb6EevYsakr7CfVJ-;)!0`=Q$(WSi^JnuWs-n_#VmKZ!wBXtLW}vQ zZA1VBnPk!gMw>1R&$F+=^BKqBh37~1Kq7OmOcP|J@@s`I(9N}96TU>&Yl7R0IwE;} zHx8`u>CSEJJ^dDbx4&Z+6|v##0;1ZOA(4P2)AOk?wwF7f8PVmY!UAzora$~_)VWbo zpoNy2rcrH6&ZX*niQJx#vmpWkv8#7~YKFb!8RR6o9J!VGSvG2)Valt)3feLPu&4!~ z1}72XT(Jlmxf>TocvkNiC>C9#205Q|Z1~4qT(W7?Yz@}T;8BXmDQ%JyP_i0i+8SMS zZH@Ur6R8oG#KKbI%Ha^nWE`e`EH&aeGz*2(F zoL6QH9Oz6a`H7XOCL?G`Or_3mizdPLIEYt9Qy@;CGAHIc<#m&nVRclqS0pLxG`a+v ze*YNl^}6)wJ2(tpT1XNuhp_MkfBU4uR!#!LDHJ!fw)5i3;^P%4wIM^99?`z+dvSi4 zx5@dJt&BajF)mPvwFK>N+CwITG90vmUKzljbGgCzO(l_DL*)zh_k^0j&mbIyW93yS`ZODZuM3}7aB31{|6ti+5^J?%1=dD=SDKo+y(E4~SKTm@6T zBzDBpwk$gyW5`R`@z@e}Way|d0>>}BFgr>rNJ<fabB?kfaEWP%v{aTVwPJt&#|+rgY_4(V8W$p z_U4U{niMz&V_m~*#e^0*XeFHIDl>vfcIM_u>aB%Gu368LPA_4>lO&mNyKp^&p*y4o zlndh29r8K=x6hgDnh8YIzpRg8R0O2~-rG15MxE&EipVrAzQiXW8Q;c}pfh$QTHqD=?uj6$!h51`E|R8!UtfDn}q1k4=VXJb0YYNtoni zw~y(j?CzFG(Ji(KH`5qc?)n{&(A-Lo_jek-410!NI*O%vTFqf9kT7*4f6RJz5)ex^cB&K?>{KRj z`MDtu>{P%mciuAGD;fa;(57cUJwAvSVdFSxG1t~7mZHTVjR1yvn{9jm(a3xnN~zQ%aHXH-zhVNhDhVS_Zi zzOS4zy927--wEsJkv2KDT_pw2lq#rZq~%m%s`Ggm;$3zgz82zqscP}JzSMbmCAIkI zASgAmZOKu9X{`5=8ko!Lz?=yYWo%1@ULA3{Sn*ss?>Z=#z+=U!_6VK5c5teF7|(~` zd<3o3u+^X#KavYr*>icg^0rBZ=3=qj6KX@v$u!8a!wpKFUo<_qYl=o#d@#q*x}dK+ zx5_u91+q&c(L;l96;uyZB>Mgusy0zw8i^uD21t|%UVyf6<<F?UEJJ9&myP<}E00?1;gHf;U=36@Rd`PoJpIv2aZ2le4u&HKfqNv8M4c5kYfcRP@Hjae9_(QYX>~y3E9kg)kZY!nZLE z93AS7j~Rcmd11?fX-!T&yFy1QiQi)^oHa9`rXua!qHgU4D#H1eIcCGr`jsMo)GH7( zZxmQLYpI-h!)9^?ClUs8U|hRDEA49J@#hXm$7yyI?%Gr<1# z$uW0gvxCy+b2_=NBZ!Z+XBs0MoQtUs;vd z*00r0Xa~pL&m?WlRzIZ(kog$3s&HrikrsWdf$-@hmCq_7jDRts!PeX)pE3z+HkWI5 z%SE;se_GaM$Y?XPvRT|B)tqUDIS1c(808KLdQ0@eZ`KryG}Q8#8JaA&&BGs(0T9we z?^Ub;DfeblDI^yAA*|vV9_knNeW}H1YHb#$%ie@w{VH1IO>rCsADE_9{4^y-jxSm#k3ZFdHCIbT$oS7x5Qu@ zg6l31Z+l*kUKt;u=UyH@W{<9mkMLz*9-g*GuZoYL)XT$r=Rd=TuZWK@;VuuKvPb`2 ze8i5KOzZWbQ~b8ah#25E2L!b5%-I;8d5+d&tkGtU8L_Y>Mc6z^Sf3zzdJL!t*5$X| znEcKdxhcLa6nmXtyzi1s6^BMY8I8D@M+tOKg!RY1TylCX4!&=IzV$ua|mRL zlP#>0wa)Alo_YS)k?=&{Au3nb5C1kkyql+!_0zYV@S=J;Q9pfG>uI-s`o7lF@%rfp zTTjR8ryp)T9j%{!wDokPe)`L;r^EHrPq&`hk)bxU|I~VFQBwBw^Yv3=SoboNT-Drd z@4nP}SD-~ojuLfMzyJN#`_bxs+Pwdh)_eQ7{_-!XHv~%IOLk6yVT@CZm)6R*S7NLg z)mLUrEUEOd@Jxjbh<0@E-BdtyAHm6Do@^)bvAjFnC;!6b;ec_`)eV2F6-kp#T(*|n zPHV&PJy^Zx6k-P%JoRTH@{d`-ok=K3fxwYc6vSOV1LYvfvhW|NB?&*SzjWdP{TjV_ zV>oQT^Nmycb;Cd2!Wa+51v~kq^;_zzlWd8+up(IQ(sY<3+;67Gg!(4s)E*+5Z95Ae z%Ko(v=#!2m!sk*@$RG#+3Y%!K(dx<$c7TV^uc~a?D^nkyV=B}2!jSqTSxkPztC~G6 z?9yTO(Y_rutG}4+Pe?YzJuQ6E#F3tJx64^v!sEpQQf^~dK2O*S zOb+RJl4$)rhkt@TqEdN2=B(YsJv6~)K>*lP2l1;ewW$Hfo~YV10##p#1XpXsQ44ya zAN}3esYI}_Xk;OZE}j+fD(Zbre1Bzi;Gn*GYK%6xK0OAf$t@w8Qxwy-#A#3!tB3-H zKs-tvlmp!`PinQt+8@U0LLC<@siAi>Eq+GCL4~3%&{JkE`5ivlh%eSDs90FLa!nppUY1^Db+okGKN_Up;NcVE!u#bYYMJ!y z%gXg}+|}9Fmxo_4DR8Yzffi`2x92(ax&*%05rTMQI?656ai>fKn1t+i@wCQv%^v*i zA@;0kk=gul^@d(9aQ~@ze}wx_#`}5hKVkQ2*b+~s(p+XQrjjkQ6C#aalLJ-3~xdd^PN_H&5I%QyLs!%@q6O=lss zy7003VXKDY^}|+kC+*?F;OD37hpo@g)DN4_C1mFFYFF6S{0R3)pawl(;C_L-aKS!2 zJ2=eq^}|*Z=jw;eSX9+nsyfTa&7WQY&$k|E+voOpJ{WBs8=u=^g1h@3wo}|!vj6v9 z{dsdFDsK0sdays{l7~eur_Y0ut2q@&9`UW!&zT48bnxy6@la={KmV!QW_ zuMVaX0X!={T~+jOtmw!}75$f35wR0ZMV$JgiZ0X@>425uOjYS)*0Sydjq5I(eF1_zXf1FG$hd?Ow7+` z&8pnVDhE#zo@_V%eR}x=RVB08oAmph=J%WR8<4ps$~|^4e-E^tJY-J}E$RQ+s{eiK zzWu4>DOF6Up2D}7cMBghaZ7Q`H$}o2?OE+4nqC&sCnA0@_8a^l>YanT-*IWRSe6%L z0#1;DT0p|VY7igH(vfN>@-v(x8f$BF*%6rv>R zDd-yvIq7r!K(<6|aa=p|vF+z1AV9ig&)mjBQpEJ`WX%Dt$90W^TSwM0U?itRi zsp~m+IO80uYzt5JsUe-CSl_;y?=uKPr_JiF;Eod|xl?|D?TCOO?l(Xzvf@zGlkg^; zL8xu^Y$cbdXH$`~$?}KYXRWcwU7BfHa4?j4_wjx<&UWq1#@Ptz`cUX>XZyc0!O+io zY+2~#*a~uH4e@W7eIuREJ z*IRb;vWXrT>e!+oE<&pkLVbX%lDFtMrGeaG-ai5TS)8mIzm_qrrsmw`Bcvz186PlB zkgYuJcXhxZsP9(kC#vtd&BwLI!1iGVLV;FzS9-t1e2UzG^7p zlr*x$I+49T*#2uf8nJjVPIef)_@!cUuj{jW00};(JGlUq?`r|*jT8cCujf3`;&D++ zKjB=pi;R~1Yd%(oO+O)(8`E~#ZJkwSP=mz@_Jh7l`j^?TR)*o4K^Qg=6tV>QB@onM zMm$`4Hd&&x$^4*5gPB8bhC_$>uC|i^ZGEq2eQY!ZkG~<~pbWpX9Tm8&i z7U1L!;qCY9T4gmLLo%D+7ndJzmH)DRSLKIN{WYXTg2;R&&;YJ0gNHO4OMpH^y8?Qi z@2m)=LP=>GjnJqt*pBJB?Z);Q%fv1tUY*B;wE0(p=@ltd`5KwPwc!rn$U)vL|Hg7zW}d|hu^kqm18-BQsi?G#)vNP(OIV& zgh57!(mwB`TC(;{JK!!d;B5+BL z4{&(0u7~4!yeRodLLm;@(GaWMl~tuR_qFRhncbL@d!sq*XM)-zF_#)=HAyp0(9PvsbTo4A0h=$}+I3h_C!Jt4d z)!0CRrG~GoAiF!x>q_pQXLLowJE!Xi8)S3^TB~kM|B@S{rk{GNQ5L=ddRff|epTNI zQO;W8#=rqte-9PWvo~;|W4F7iM;x8V|EYki1 zv6VQAKWnW5DK6^I)qkFSvQQy%#)p2SeTD8j9;hy*AbO5 ztxz%t_l>vX9~S>5G8$7juW>M0S$xIqmQf+|^VZ~ZSwBb9?kFm$mW8}~O?G>%@ZCNn zgY}mUEJ?9n&qNV|)dTwBa{~MrN)4CpaZ^>P;3zo`{5hsLf*Hxk8Rk5nU<_43$xD$5HqDU9SP6uHj8g5 zBQ)C{ZH*Q1kZ`A-fR174RR5{gBWZuc2~CGg<<2>hQzV{Ai!TZ-h;CEqskS)d547)b zOx2W3q?~I1{bAGHVeHQEIa(fOmnLynNnFJ$WAzC1U6<3JW5D7G$F_na`-lrQ)la;! zM+b@|VGG;GV9qiAW%*912Exu4RRkH(Uh$A>9r)e{YW7+)#w_jbLV0ab+z81i*`e3K}J;_@h-K#P)C+@{EE7~aRBhF2P((&K*wb@l9Du&ak*#& zMtx2ui$`I2c08a%E&1*d5lwMw2@>n3PdJRJhC>Xt-|50fLOvHEq9KS*;OIrvkrcN2 zu(&rux*(L-NbWQvMtmb&TBwo^Qo&STI80k#NT4nAg+o!SzHkFY8?^X)AiKHe|2F#3D<^mx)zU^&>3;7 zM(aq9!grW_G{&cri+yX8CvXo37F1t#)YIYiFDgBbt6PT_X;iiP%4BjZN0^vch621? zU4nu~Gz^65F$CYRl4e$AeYJCz@K3v@A$9FiGc&HBwu|FyT!sjxcE^e*2-G*U+W zv^uMjsw6h66YE-nR?xG4gaePOs;%CKaN-mCbzILXAN!Z5@-E(!ymB!eS;>D=NnlZx zBuOuWcJXxbzk3J0DR@{$Mc4crcQL#Glxok49FjftkddE6K|n!HOCq=AMUm(~%apWB z)ylP^`APQ5{?>f(v_12A$_lEml8HWJlbPHu*JrOY=N3&o6g{-(4O6^pJS(0z9&sjQ zaWMLXGSg2~ZWYb;`e zE7f6=TjufFXwqX0GaXj6FCU=2XF2-)76I-Q)(O4NPG>zlL)w7vxqmnn4RfFMx6IQ5 zJ)OBSdoC(IWWyTL`}3KUUJ#J=B+l_nvGUv3QA+Qy6=JgxQ2>)4&dv8VF71qxtR&^8 zI%!Y$T)CKmf-iU1;Z3_SeVy800QLVbb?*bO*HzZ} z?mzGQo?4#G>R6;YoqPG*>1QsVOYTWvk`Q9m z(45i~Q!GP(s?l3DKp}N(vEvjiSmdBBQ?zPz`jM(d!;sOU6)F^|Snd7(o@ed-?)RM2 zq~*`d9YW9E@7{Z@^{i(-|JSqD@))o1@iYlKGF4TJnJsM2#{*MK&VOW=?WPLOktEC{ zgK`HK?AJGNL4aJtg}^28CP@#Oo?WxA#0^*QYupY>({K~iBwnS(2ZuF6KWPoIA;AFN z^Ws-r65XViP(fZBziiSSK>)%bmO!l=sEbEWLC;&brMYP?K)b2hONBKqHoaN7P%=%9 zU%#-xFxmnEdLIvuHE!~e*cpiW6mb-L95MMOdk&OitFCCdnh7W3l{J9n%9x8S_$!;O zvRq+HUiTSRb#aO1DldI89`w|VwZg2(@oLnC$VJ@LO`&2l7?sEA6lDggYHk>)qBJv5 z^}J!A>Ny5#vuY0LbPu>{<`^gtPAwLzj#LJ!umLrWGM{%-Kq=L$TOj#w5e`B!AL%bo zg6~kCEa(*b%2T?TX7$OicUU{HaA#7O4KZ}FA|(!g(F-0p3P zwYw$47VsJubtCa;7~?Oc^FxC^xXoY5Ugwzpxa&)--*zl5B(i>jp(?kyNiATREVGKW z4bB#GpBT#Vnp}Ncp9Tp2r}z-1ZHK1V-HHdPB5cwGSr@(W0NO_aXbe{{{ezFfd!g3- zmbx8^nS>^Cb|gg~XdFb#lC8zOTTXO`C%OR?y4^zO4JA{gnil2+@%@?9AOKQg^U|!Sa+S66`-o1jh^l zalU+NW2lvk7VGz~2#Z38w}{X%V`Z?3umyi)4OPLPV`|7im33tIno4m_FsUiR`K^l7 z8HwN4JuOto7{X%UP#;2kfPtS`528LfYP4@gU2Xns(k`{3w-Q9Hk-Na zehM+yRK^$k4{TyWFE_)n0Mpj&P9%qqnY*H`o6&v;X4*BM|D?6ocJ|ksO(hU6Tw87I z9Vy@7p+f>n$@7GJSMqClSJDCM);Zqj1m@KGe$B1P7B1_2ezO|0w?%lnvGMj@_O?I5 zR>(5w$sfnY!@9E0wanOR6{A|`l8VD0rgQ16zBu&3(0$@!cFV{5mnu3P@29J5b}?3J zFLoXz%>RKD-msro!Q>n!f=VGI;{BJh*ba6pT5l;|{+hiyB#G3RM19kDR(rpLo7lRv z-y+4t9;h<8Sv4TQ_b|ltn>NUuUJC_F)`;By3?TaB3JF^mG(G^yY$`LX)-O&9%0HoU zK10b1O&>}lStkvU>-GrhSM8-I0Rlzny-hE>Xqi4|AFXNRJELVo&HGbS1a_GQ9N?n{rcJl)NYlhR36f8e%m{!seU7t3*U=7 z$k8a`4LwLjEipJ&lRi1D!n(OxH*kom2QAD}U%p|d;YN7TI#-^hbyr{ma0*`|B?2cX zG&Em9-G?xYKSW~nC%{5oHq!&cX#KwgMQS{ukGz9piq;$i(h5Fl$pB$rvVAnUq zuHa;>na~jnu=s($#mHVw?{|t(+pvaWDri6fWy_hlt$Zv86pxsBM$118=#hMKgfPt3 z5P8jpr*U41So;_V$Tx;Z1PQYRf|SIpa3x~07tru9K~N02&mjORhVtPr-|(!mS%7yG zv~r0w!GiOzQATGWFu{-HBn#_t2qGF$HDk2vbyc+#)sT-CVtAz&q;q%}C*i6&_}M(o zN5x{dzZVK%L(IT#!$#YRV z?}?vVTa`LfN0|b~FOh)mHk@q6l_NA|PzftjjW?R{VH6Fd`1G3aACtHZGiVZqg1`67 zj~4`>^dEV((N66tqTez5yM~OoNaQj`iLj_Z%8f5M^j{-@E%opjrts#6tAl_K=bD5-5%x&*ac=@ z8^b_cbhrshs>3MwKzqle0X$rDW~>I_L7v_+Y=Ez#4Gfd)vmKDnZ{dN9?Ty<{)Eu%o z6+ZQ^NTg2aj)`zANud(B3CZ!2mO+?g#O=Ry|AH|bJ9x5b+#YS|VCW^hrB}dhZ?0p> zEQ3IUj=5CUnZ>n8hWePW0}8BReGWXPPf|5;hw9NvGW%r>Udjr4;^l@TFD;OOj@Z1{ zPfPRG1ZW$8*@TRknyOd|SY1#!TVNX{MhI#0F4DQ-W$6Xgw^H z&MT#9*ypJz+>{@$Mm41`8PBt4I1-dUlBrgK&U2Kau-+6lz#JZi9~rFx$3-`5vefGA*r=ItdflzV?f zP=hKG z(=tW!#OfCHN~t58_lCQjIErR{ zi}g$fpzG!}ab5k>_maRPHLYlwjQt9%$9WrK(Y9C=PegqiqY?~-SeuTGurA0LTGCO{ ziS0jPVlHw1adks?l00enS<5Jka?`kqtO5`A8-#`LtmjPX86VKvh+0d$w{;=aKH>!+ z?v(#5Ft)t$FnD||k;$+N(Iz=f9t2(tB%t?!1i+f47{*S}3WNRxDLi7>sfi;9V0A_a zbvAbJUo(Nv&F)>5N@M64%mXDyoEq0bDXdxry#`{zI#pl!0qqs3&vZxmuNzKdF^s5L z|5e|C!RkA!kOc>jo>3txgMGf73R9I7`HhJDSZWn2MOOgx!cW6i;jfmg{6$$dgSrC~S%a2^ZOV8a*C`5{V8gkrhcW>Y`&EnnQ8IWP=RuqNPwc8i~hB zQ5Bi2>*FG{_^I0Cs$#57Om1m&A=GKpz}l!4OA8+cgHS#|$dO0Q0hqj5WZ8veuO@6Z zApb+Zphgx8n@6ahq}5Ku{9>f&ml?XM0stIzYzY%vqA-(*<#jN9y8lN|y7n8gH7cK? z2ENJNg0emXzp9yH+^t`t1bd-G8@*VCG=agZq8#>>a#rW=0y!48GUArTcMwjCq5&ZrNo$8R+k&%PFX)yn@q_Ibz+@RUNxBr3r(m;A|5(hmvS4Z0?x4?ER6wd zbwQ15O~lAavo)5gz@&_AO!xIuSAjrPtH8La0%NEf6==^O+n@p&%!UdCAq<(PZkz4{ z7|{}ls8k@2m)Rnc+?6fT=*V!tSF%JzLTYcBQuP{xbghT2M$M`$`ySjE5JXm#2}<*b z6IAO=ghk+{y3wo}@I9onAGRVC(g@p2@r(&Mo-lsGWmp>Qxbn(nAR8jI0!Ro?u+C)n zC$AJ&f8W57D(W~PFw-xbA?4;_LzMS^YH@)l=o~pqa$}fUsn5iFG)97*i=zZ5QAz6l zU^1(J42fCr(!afw+)iGUU{zKBLcdt|zI z^gilEsfMV^tuTWPM1ckU88I)13L8rh=o}pn#~e!R3skqCS054q@Sh!A6278td^B*& zg>h~pdMpN^lLs|hy8=|B%;q|9jQ_|GfaW6)0JOoy3|I+3#eHp&!?!?*b-d->^$RsLF{Dh70QtP#OS<6cd1JfR(<-`AI?ZF{DVZ#27-{yCIQUW5%wm~J- zFKW|?P*P4zD5G4cwk&94eE%C|J7Yq#i|Ym%n=+%j*$#*yz}*y>G(e$SpH>sUVLW86SBo^95u~;0;L~YFt*^jQ&5WLq4)Zx2+DD28Z=)c!yq_{|V(AyYH z|85OPzwLWK(0Z+Sh_idkrg#Q?m~DzGaM6qTCuAaM!Uy6Ve4m}kfZk7U%XWEG*UFC( zVkvHjmGLTd1a2Xm7S@{VzWnzE7UN{rHoyNszq22wVA;(-ryn@>h8*;!u%c*sy0g%s zQP7{fX$_k);*v}8LHL9g(TUV3c4&Kz9Krf0bxY)_$P%N+8fp&|miOHU;FQi@DQW;? z(pdm4#-~PnCaeHHH!Fm?y|81UF1#wsA5=3@c82#anAo%A_4fhyUmx>)783o?NL%Lh&;;M^tVdc6Tp?KVVG-ZZx}{$ z>`3No!HdHnDP`6ZLc$iSR5lNvMsvFXCKBtQbte1wo3Sa75Crb(U|1@KA%Y|JzR2 zvfqJE(SlZ4V89qTvz;Zw+YtPPRWVG_C=AP$qrT0_I#h=#NYQ#aqzN!#MuA0^jA;Vn zak!)6aZskzHG^WB3m|R+awA+JtR&Om0i$B>w+3q|Yk{WhdxyxnB0E!e=oD7~2~;jB zBu;KNXGKvIe~ZEgsLLSvs6O`jdlP>q`^24}QR5XvQm{g<;aILzz%k8O$AsG|lvat% zFfjv`qin6lTcc!*@3MdRnTxaGXD%QF5hZP~FU`WQz~L8w3h%1ssD~7fwY0L&QSSRX~d<|vs`$tlf-90;09We63nnTD0iqhCen z2wOkK2vxmOO(A2&dQ=U|Uu$@3yT{tqu!KRHlhh*#Oao|NQr%JtthG$A2pF#@+pbO+ z!ijB}1QYy-KMMkC0D}o7+EW|uwJPP6jaG}d%fJ+^5sq#nhf^Ze&z zehIjWZL>EI>GPz*tcuv6h}YfYNrYO)R)Yq(e`mvlpN1@`^7n-bo^+S&(v}RG`{N`@ z2W$Kp%TCpzYZ2K7SS21`QRIg%%cp41%ekf(JNMiv5X3bj3b$yANE_E-rj~pLMQ^B| zjox`(e{8Ned>q~$JI*H}UV``RhZkFO%)(H=@@6(_2?J&Tf>n{3k)pk;lKUgPp7N<# zN%qJ}c8IXTv0$0IDGXwHtAw5*reG-ZJU1&^mDzLO>CSxZj&M2j?Pw2oTYLHtAd;H0 zt&NB=psO6aM2Mv5;Ov_2Is+Oz8K*ILt-YWHv|VE`+54fN9(UEOq#*$w^t9QIcF#8I zifA#u=Iz_F2O=Kw$iG(7&}ex*Zb3Q=1Cda`fy69u*hA=R>t3+`S>|-xIxx?iM}`d1 zL8Sh2a0z?whe{RyQWJ6cSD*RRLR1pURXq&u^)#|G|EkD}?+AwwBEl9}h=Yzo!`#|V zM%2q$z*mL{!>#C#L^y`TOi$ATXEDv z@uh*L)i&&3%fUHZeqbUgSr;eF8iY`tC~!CwLkhbYOCpDfoGUv3aOK z)%VA(F{bdea|p2o{ECQ!Ab{RFVlDPMpeMl4S=xNL)wcau(mZCDwJ{E#HET|k3ka70 zaWnkI6eGvU?ex|(OmS)>@BP*ef;QcH`fjInyK05W(VSAVsrB$^x{8kx4*9k?89 zRYtA4BtsJ7LWC+?8iO4C>JLlpUF8>vtrwBVun3W+I-j))q*7wFu2c()LR5=Rq)>zk z*&~5OW#>ilC6S^~tZhmwz)yEo$6~CO1M}rtdcmH{+T?I@-P+LAk3 zZC0{XS$9oVcDQg}!U&f35WWS0GJId zW*rP`5lmZtPZ4*|v{PNbwP=Pb+=R1dUK@`UhD1@c@E6TcYn6XthdXuH{jq3Q%83%@ z{UQ8$OtFMwd(9PMV_?gSkV5b7YZTBfh|(5bAXNcAS)<)bpNQm~iqL{vfr5>)56A!$e*7wVbhC2Bh;|`vX91q2Y^nEyk#If ztk0c?^ue!Vb-pc;hI ziX{69NvFUh1nYuPY@(`XjzkL^)isGrJtrb0pRtg!0(LxvBhN-ofy%P71G81MjQW}1 zY1O&ohn#T}-C{!xdApH*p;B#<93UaJfhk|P#~>OIeV0zt$F?`zX=atOvqZLLw8&(! za`~g?=`Ei(d0OFh!0>sKHzMY|oy5BRJjqid_hB)Sx9?5Q4=A>t_4>JZrAc)L3Tr^~ z;LthPWA0l>(H{F4PTH&9!Wo(g7tN%zJZa@OXp|G~=_`JR6p=&~>tiKGMGY9q?ff4_ zAZDVA$~rZBcT5+g1L?6&%oU^aeo__ZH|uZq0`b(5^xyZR@|?GFv!`Gs*1PQ0tDT6k z9{A+?h28V)g>j8&IWFJxw)yhCZ}a=sK3-4k+Y4;J=8Nu^ehIvbT;6L#e}x(vg^4tcDMEysYN1vJ5_rD$hH5M=p(6y^C%n4=btP!~hzMn6;G&^z zDPGr&ENo$~2^*+ED@gpl}j!lIpcl5{SqSxVCjvv7P7<-)zi~74} z)#JsudVa8<83Nwc@6NsaCs>7^QEu)xzVgcp*B+RYd9eE`Y8aRQfBV;7qGUxjn*7R3 z<~1Lc!U*t3QvRo}jlPKwxa>j%LG?_V@qkb@gV1^`*3wh$t|d!StNB49&v*0;>$`g_lHZ(O%U1DR7UCXhIh?DYbL_aXlM}n9 zD4@c?lLRM9ko|%+zBw(}SD4FmZ@n&CIRb&pw%(b#%=XCp*_>Y1J4=@P8$OCkD*73jkcXxT5Pz87Ww@Gt=r~$)7QjEOD^ly-C+A1d`dGnug$s%|+0{eT6p>-{9mAtIPqux(RV#vx= zv%8>NZXSD>k7Q=Ey`9CGAZo8uC2~4z30K^&3v`Z)Zv?LQIK)QhT}0e4ok!&zYQFrg z@?itxm!)tqCIxmc5fi8(cRFDw_B zhio6$;O&J)k8FJNo%zqK{B5h;{$^<5&iv@|_u59yYIMmBcwk-&oaKRJAZrkmmJ9VNtD8ocRa|bICBd;r z2rw-A0~<5^`cvr7ru_ePs&JCmIHe^hj%W;*up7Kyv)C%@j^j~Obc_CM)t~eAXPf?P zud0)chgx~Is?aLdAmgWD^WBr4{ZC?AJ3?QT~f+)mj3vC_E)Z$7T!n>rTiht~=pB0u*iezBZgzrAWWC ztV#oh+7%o@gP4ClL^^qgRhc2GeulM&2;hx;=u%( z-@%FsH}?8%xzy$Pds*&m!#Glo?1K9Ve72c^R&n8iWmBZ=YY7ZdRjsv@L~C0{Z@PbH ze}pBOd^N)IG%-AR4_>=_4V2(1@#4D8P;h=H)nM4#qyrti*@t%p8WFFL$(TGqBTx~JH+8M4 z6u&{MB-0@hksi4spplzD<&khOqU0K2yF2mm)w(n?0y|_7s+qMsXhy8AY!RuSX zbn+2)iep}<+&-8X!t~pYjw&x<{kqwCN};95QLDaH)o^Q!9aGtXh6;K=G69R%Xix$u zU~&{f^Uh*6_RJzS)(Ki*;?R~b1O8}vi6kc>@b_V6qTCFvF7fCTgaoyBSpwMsH%FCO03=c9qFn=heTr&SyU#%i)uDd~-F>{JQt+tSgDs76nk8 zN|ZGswRu5(2dLWTE--5n@efwPHL9-s(u1oie> z|8QY|l|w^V;g@~K2LUS$R4<;opU$s_>YIlu<(E~;2cgn;#)H@m0m*ce#U|kbHZ2{R z0084QHIU-uE2Ywmwj}>K4Ypwl)64uWvp77FQl?Xv7O6;OxZ(`T1`#EhA1T|S4hnuw zc$Yv3!k_d>9|a!?1xmC;7TUvReG~M+rSg1GVcJKMiIFsh;M4Mnm&`TL72= z>pGTSxb?h$Xhu%B!sI;q30b)MfO`yCRT#_wb`Px|Bd@#VIYG4(!Zfa6Y-KeO+xTEx z{+Gvz6qvTafI6MMJ&k=LbkHfkqdB)Zp#^O>;i^#~@ss3aVUZ{gaTI!K-7$AEx~@N> z3G!m(ICA)$x5|R*@Ik@%nuu>-_Lf(=(+u(<0g0`aEoD=+lI|K6{CKklncs*NATt0= zPkH02^9zwyP9i&KeT2)&=*_*@hN6NO1HovUrg2DPzSy<%RXECc`x@W?fNV>!px!VV zjTa6XAIdJ}^3baC@kze>baeR!Is=r3yig4i*M5hnsuj}v(4G4Cl4gg%$o?)yWjZ-Y zY|}Tk9JffJcd(yYC#2$I*U!REew(N~bRM223&#vsS-R#WR3l0y2VX6&pXzt^V!G(F zQc(~?BF?4W;lsXa55vlB;nUbA6!@j@K@jU8G{?^SO$d)YzOI8`mksLU*A)fZZwQ^( zuVwreUH35tD3o*dT)MYMLW`|m%f6nl#Df>|aYE0rlQ1YDc_cF-UG<%8A zC`aB5HKj7W$T!>*tghFv!V#N^fUPIA=(Q=A)D5WcneZ2$NO0yKF#NRx@r=uzZ0*}y zj!SV?BM{RLB!=!684=d9uj$ovjcptOb-Bhiy3DXmylJmTc7$!ss6(>&`&UK7i(Rg}Fc=p})Zf6y?1b>NjZHIa=O*taTUjBWoGR#U@Q*1f+fa2pS=4g-$8 zPm88A9~meu3MS(d+n-%zQ)m=~n2U%&U=jb2zEBuY#usCvV=STIe~onD@;b+l zV#dMR;%0>7%0_l)7c?GX6e`j(Ph&=|_LDDpz(3pOr9+`NeJYznWvZo;&@88NL&KkI zdj$D&?ee5zwRMC8ES85J<;naE;S8d-lv=fCZ}z3Bq%)WaTwn^Jv&l7YN5>waDYbq) zovldyf7X?8XU!Dh0A;X|(n~KUM>->(FQ5@>OmTH*CFFg=FuzIkuIvz&kO3z{nd40R<5bT?@#OkTNKzP@xnH;bj59Csw>U30 z*^lQ}P4=Jlf(;vA_` z+^qI(S^0j)Qul(a{6M%pKPx{txV6JG9L}U~PO=Ex_GGx=Vj4urZDn5^^r-FQBJd*=+)#@jEbE4%?j@sst_yaIaF9I zsStJ~)YzC_lXV&V6{P>g#AB@YMT2Q9Qpkfr6CYpF z#F^hr6Xz^#f(NO>7V8xqjLU+Q4;O-Va3a6^W98><)&Ce((R!qbZ2*Rc5&Sdm@Xyrkn#HYuG*18V*AUVyI{2Zw4hqsuPHgxuiwAEUQ@0s@BZ_(UC&c1B1`bxWQ&okpYJ&KMNL&0XE;0xp7j-?Gn)Ph*dWx!^ z=h4+>WT64s7O!!-s`0sZb!!dc`Arbd_X@XG6`r=M7Mnl~cub;3*>g$f@yb%h>_5`s+m9;;L-!9Set!+_ms2m}4$|$J|re5S{-;Hyl3hOgxwzwuusn2n0l_q)*~5 zGV9AxcHRlqk}NW)Tl{8%;5pUt`kH6UW%PnXI1Yf_*RlzQIGHXmxC_C4G+`PaCP)-S9auAo)=1m(uSL|cdId9`2Q5#bk*xO3Y^VeVI z5wqy_`tNmLj`JGxV*nI6Y4%z*by!x>w%iGO^d%m#=^KwQL^@p1g#4dU>7c*Q#tpCm;Zyg*D^_4KW!Rhd z?uJeW24D){T{;E16_1Xp-A?(J5GNfq&%UB7r!l7sQ4HH@(iSvkqfE5pF$&2D6psi; z;t#O{2_JmO=sP*+k+$5_>yx^Np3Efk*CHT-p!WKUx`)0j&8})jtf)}Ano(Md>JdD6 zQ+x;^`Xjv-AzDzvABk~dm{*e}iu%Pyh>MM3VeBErn)JNh?u85fW3s)_JwInpWi5i}jTg9UzP7j{Sav=%5ZGx?8J<710qrt9l{=ds0MM zu~dV=%%lvIg>IMSD(Y8vY6AO{p;gC=QFCe<<6fvDPuk6Q^~xch_C3&NqAe?MmRuLi z{=PPTD94o9DD(-#TUi>=AyHEJ-&daaUG-Hd#{M*axArMk10=BOPu|zZuNC?#k#A-^ z{UQ%Q$E`zlI&0l7(_C0zutmRY3mZ&B9m58a2~zsN*D*=})FvHOjI!;yCrNqfeEC*H z0S(j~+A%Fvu#ZGYlxJFwIaA})#b|z$`3GqHq#Q3FRF|qriJu!uwF;@;GVdXf+xE{5 zj|6`hM*XQ2(l;PfO>X1<#fJEk8u>7?ZDsHxEY9`mgOQ+)c}Ia-3KOfvh6OyIpcfnxKXr?TF{p z=~2~UHtEtu7hQ}ENiC#ZENFltI`1nsq-vP-1S1+h=kr_s6Z(FzoMR*Z7}R|j~q z!Cv$0p;deduZW8C57*<6EE$Jj(?SM54()Gl9O&n_KqiB{`)3~rJN;xim0qTizoNW2 zzwfz|54u;X9)eg(^G1EI_|`fzSS0Z5onfk~$#I%u0-SC7vwgTPJCxuK!`blQLlVDk zg13xc+>0_=XXK3u2a8k;pr$l94HwD@dNx+C$dipaB!#7#0ES(}%n8D_Yz@?vYI$Q|(x3c0-Rjmci07p(0o%BW{&+ zVyqHvJ)%{M=99)0H4{RM?^-e!lw3}`!6X8Y(`}ZedJ7E0Y9Ll-lHE+`XB*p6shoTO!K8))Z;uiPEt9=}yRbY(wS3+Z+UxP~JDtybPvM@A+Q+R^>DC z#l!l788i2YaWZ)JS$mcYJ^K@T#tK_;xfwb7@=u$kVUu$4-3JW_`mOh$Yrcn;_3Xs( zv+(5?hM(G(e>wateEG%Ur}pLJ!_UH(Uv54XbPKPsJhsR{4IP3@lruJo_IangT8GugbV(mVvh!X}F#vw4DQypA zGhT2Dk^?OADRzuOWRGGU8*KZ$j1@XG_(|b^!0%nBJoYcbAd*Ad^~gzv>8JQrWz6`* zE}+0NgQsRVsrtc;&4RY}Ki90na4Mr4xL}^%DaQdN);8)HDdL z8m^XjxkS`w(+WcYhvnP}$(T`v%(^z`TKn}$T@g!7sctjVX4=8zZZ0|uo(2^F6qBow zjpf4c7?}E-2QW_yq?3D)YFVqOb|^|;~>E5w^Y_D^d(G!CI>F$7gVSTT4y<^Ccc4eep@&`ascf;_PV$5LMzfR2uzLy&&sm+kt5}T04g_@w;EvK?3j9} z-JNc5Ylu`UbmIum_IUCtWQZvx1Q#((q^TjP3XP{O^j|=t4nma!ea)DEs~<_<<_DaDs7j*xJSmbpD0IL63NyiPYQ z;VW$2ON%~OE*^|dEO^HFAr)xN!dMj#c;+S&y?$VRJecx;S^-@Rhq~*~{@aF{4?L*$ zsx}lQ102AX(5xFUu3M=^6Doj-^ZW21ZUqG5Ibyw%UUlpD5f+1a>Ls^FhBkE|6!w_7 zg~+`K(OvM60V2Cbs=0?3a`(0`(=k;uJH>?;q^{Sow+;`N@z{Hem@Ydi{W}t zxZV}6w}$lJ52j6+GuKW}rWHLF#Sny;p|8%dfB4`KNLw;15t*T_c% ztOYE*5s;wG+qj7Ln6g0qLI$POuC7wUir(0KL+UArm?Y)VMtH{i3!;cR%fDNp#}yuj zdpvN94H%hQj#eUlOVL`P;}mFJxsidp`W}&ej-eWD#LMAUt9OgY8nwikR7GWj$uV8v zt-vRK3;i&d|Octg_C*$T9!S zFxGz87QG8~xj5m;F@C`y~T=73>yh~2sdl2O2Kdc#1*xGU3+^?d~8m%73o z5VO=3PFQkNu&g>M@B0Az>mI&;97kNf3RI%5o}hz@kZ^kv=j+okDDB&q9FBBG79ZI=l%hb1Ps>3621=)#D`XfC=jxiI<%8qvJu zQ@YaFlWJ5l#doMk@}E`pRkZ+7P&vBnXmlzKtHh_bmfXaUn8cTkMV?FKM#K z%ln2wdOCKH*r$XL4m$*~qGwNkHOUD~=owy_U!gPH-wnD2NB*>C1bJ`J7#zt`I8u91 za#q0*97V1!LpR{D(Gjm7vDd+UXXm5jHjD_- zkTzI2CQMOi$3@A}L=vM_!Xza2KBaL|0QY8c2`(jX-eV}pF_wyDKEQ{;ra+i1j}C*t zHc^6sa7TE5`wB3an=Q|$f?YN%6$Jbe}kCU%}=)C z7JEh;X_n_Aqv5W#Lg#q-7lf5Du6t`i`YI{osJBW#)lWT*dpKwCv8c4c&&D52+MSY* z*Et`rU&_aNP&WCPG<~&`u&dj`QX6Acds{5H4EkvBrqCoD) zW@g4`>P>y^^^6t>PM!6F^)svlJo}uPqSrrn(~G|A#nYR&%=l0|!y@-c@&cpHjc(+A zQz*e%E|KO0G-s>blV}m6NfnJp`4~tesBHt`TU^4Dk0G?DlgHz-3N71inG+oHA!V{+ z{j;j{#z|{nyWO*=JB?i;{mvmPiXeSl4IWCu^X4-v2f5_sNex|3>Tz6)8$&t$^9!six!wPZ8XR@Q>N8mGb6BAN z@h%G(m$bXd%6`wYlzlXmRd-qDxTNeFE4y{s^E2!@3m}(#zs{b&RBhdZLOSx-65b~1 z4A5KkJK&aelEZ}!x7{t)-;Gw!ciVl}?l;^0sNGLj^*{O8aQzEk^!l+bw0d&5{zHz$ z7ppLW*=fO_v5<1fx8G$AEr0GbwQ1S+4)D1{b^Cy0;WXOw!A8h0Y0v3)@chDLQMCHs zbz;DU$-TM`Hw*E5G*fP*8U6X;x8ELwxQWb;D~AoByiTU@>6wr)?hjOcP|;^Mg0yHoLR$I zA;5{qr16bgYz*0+Jm5O?<1twl>c;?i;FV=f;MIp+@>cR_B{>tI!q{fMHtffObhb0O zQtCn1V9J$?>LycX*UMzbMb)$4wr5Fr)<$aT={KT!q0X{K8y4N^+GWFa@8<2Ugs^WP zPbGMzja8h^mdTARo1nnt9ma5CicSBj);TPGeGbmC!k;-PP? z6A#s$xJPtTp>vw;D&01JXwV5<;eZ=$+6AVbyJk1bo3!%pL>C^ZyYRqp7ak2=cx2Fp z(QmN}ToKHdJR?LG3a{@hyOFLv*7XNza|xrH`Mp(7mhXl*7ZEa(AG$y9S&x z@+~sEf)MhnPA_twR~PQ7yKr>43-^XD+%xC`TJ&2IxgziHKo=J4F5Euch5JGm76)CZ zGJ(Dc)o1d`sQ(ml;XpWgzX0OWeT~#VO!Dx>N4iZAa{bIoTQSYpVEwEH%l2Yoe&;lMF)iyF>WTPW+I^Ww&AZp!WC^%* z;vSa>@YtW|{SIE24Te4tZrNbygM-`K!YwZEhr;c-)fPx;7eCCmK(gE0!w0yrzZ7oI zskT5$@kqlKNU8kJJHiJWsx6SxF7D(<{3(PxNjLH?Z+#1-w2QNNS12=8ZGn`sX-(Jy zDeaz7ZGog<;ABa-T#tMUB!6^}N9Gz<5pB)rfDKKySZt7)9*by$IDDX!RZv^m%|mhI zD;S|Frj3jUG40Ec7HM-Iq!?GKr$Lz9$k%Y$WvAfR|K)$0`i=Eo(g!S6Z1b+PvsY5diY9@#qXOpkz38XdQLfz=*p8#U$Gu zscFRLfg;j0A17uiTL9#i!y6U>{&wddZHCB3C;FtGC|^x6r9|lA@J200MbEyu#XPvlXz8((JYKff4v3 zXv~^X)|6TDSX#m4BY_c#xpI`Fa?UYiY8@U0B zVm}xvc|ea31Mwm!#4qaMpIz^fa3`iJrG*#w#`F27B@ZnztX@IIY)@1~S{EH_+|~2Z zc|QgOJzNsjbBtu~UxKN2z!Yo$3{$o!bO+I1`gV<9uJ+57eqouY-p%@Dn_ss2W!f)= zUr1opmte}SV9KsX!u4>tE`;l~{(84xz*O5YMIpmfdyZ|w@8hvnOM@g*{RwM3f)t!C zAQH+BBM7@!N^>|H#1Nz+P}I8*flKA#4p8Jr$q(p_-E*^7hpbfkjRuKGb7hS{qKYt` z`>`(F{C8ECZZWa)2d!eHyZzQ@x!IZ7)+m`xXO&iC#RObSk#&Dv)G4&gi$`7Z1F9p$ z*;F`02MT%H5LrtD|C(_i|i%pj;@n6U~$@7b~MdJj^B4=xZUb zMuB|~-Y2PK;J30TiL|MGdQfe5GliK=N-GqrXZ#n;w~eZ%=(qCEh^U0?R(_MJLJ|TU zFQ@f~lh~GjVHpDIBOh$7>!)*D=kteDosyyG3J$go4ogUAdSiLcTgtIr+{Z3SfAfJkM&FB9?e*Jv*_SiQ83cWT$ph(vt z)8bO4G^>(OukACz?>v8}^>_&+(EyTY7!ozUVrR17iET@RW}bL!lQ16%98e{6FybZT zq~qvgj+t+VW9B)FOU{svnNN0dE)1}W0wHT7cM6NFDF{_+5W{O%#t12@&cCM9w)qkj z$uW}}PFq!=r3Nr0^P5%`zYuVGC}vgtw55ASe~|J0K%8>=1j{APwGNbQuabCQM3#sE zqs~5RnMH5bImwBBJQ2+~n>qjKyahIEGY-f<^mgDAn^VEgJLigb6v+k2=pjg)C%(Ia zHo4gZIB;FT1wf~`AeeJqU>t`WxgqlLBeUCc2XCBPxc=>j4j(zTxNz_WHcW!t>fH7J z@|}}vgjk=ORQokQ`q$WXsZb-L{LKk9wZ4K-v&YX}s7XAMAT=!5?A+s}a&5Eesv;H_ zL0Td*k*ZN81I*~;Ulg%t+2@J$FDD$Ql9}j5P^M&%Xjeod*{-uZ;{1;#HYz9v(>6Bl^0d*VTUT7NmT_x*lN_W7JI-lBt7V} zriUk-AfdCEctUD0&4@9=4lz*T5LLY=iN}|B5j1e*D7+zP*e!{3za{vounU!|UI}tw zCySGdvN*}+xm?-5`j^hq7xu6yaA>sW1E=cWhkWOdIO}Xqm+_RV5y*lLT2hn>6=}b zF|G3PTkc2UzC%N71+=IT=xRp0HRj-yfw*XhK9tr7&C@w>Ap_GCah%d+!0=pQ0s*FU zlyt7nb{I3@ux+IVziM3RztO@a1HK|v%=79d{& z(uO3(A~1O-JD@tzOT%MJyRVjDW_c#k2+(!4SNxG}$dHpXf+cFinpcCM;Up)o;NDGqVyO3y)ArLC5ZbHCDNd#tIXJt2Nag|Us zDa(IUZ|9wJl7<^9fsdR-A zZ=65kjGfF{JD$o-Q&J-$AW09Is$4BPp7Z&F1~I)L!K=|-!kRT(E~n2`#=itz>UiX; z6AG3aU$6Olpv^ue4JCF=ha_(@h4!7M0z1z%J`ELE-=%c(&tGp0ZHsI4`%ipqeA{ZC z=6|XOjJ389)Obtcfx&CMHHy}L<=R&YNo0fW zq60w)imw*3eeIy%LE`N2q{`u^tFSU4hAX-7>k1ySOM#EaHeDG(7YTYSn*_kQZuT+6 z2Hc!%NQ5Wlg9=mOLeWcCHhOoZ z7#}+x8(y6_tSu9<>5WjRog2FA&kQO(11x`LOHJ&gd7Uvhh4eP+SCdiziI&e``Wtyl z_|VBfo~;Wsvt!0fE-;GxuMOj{iW&o44wr4v?DT+Uc?kwFzlBd(I7o?g%L$nj2&dNU z^Le_8r33YDSw{wEb%>G^umoViX)}iJbAAtyxFx0OD!NNkU|uU$7YY!xP>?z=4FrW) z0^A>Rl`DmRLc%tNdx==#la)QoEMY%31b;HOB1_%-hSw_{Bf1a|5krhjlKSF_IjNn z0Dz{g!`6rH32Ey>OJoc1RCRwr-}eCv7_E$*|FBIXwx(F0f5`w-2mEERMlG#TU7-sH z)dlS#2yE6k>Q!CLzpSEz7Mra}WUKa*YFt0OdLz%!lF&#iXcy3CFRZ=G`fE5lf~~h0 zpV4B?&i-oho=0D^fg_H`i#78ce>_pFk=hcj`GRklA;VQ)SB&-6)Wfa*j`hw^FFB@| z<%m*Z8h5sWE{Zeip>Gv7oak{ad!VV+&>|BH@m|D%Fj%sA6*qQ^hBVD<;}+B3TuSpy zG*@w^vll??aF+aML|1BS8=aRqvKIZzfC(NVry9DBEiXW!>HEJkRCZYu@Rv5cdPt4_ zkykD81chu)c3+fE3D!00^_pTGlt<@R!{?xcWOr~Rboz+V>qY~EYE8NYp=Y%s-KLC- zwQ2U$U_Fro;$JGx4e^`V%LlmQ8WVHL3Y4<54{TkIcCe6;EQb%tJ$^n(! z&)OCXW0oOjss`?}LrB)?VjS-ofpIZ~;h2Kiz^)?A)XTB)8|=!O7)b&4^%nO^?!|B6 z@N0I>MgUM2R7w_bo(pp*0$N-=uaIFq{nFQh5B5y0VC!byQza_4Qe@w}VF)iZQai!Y|& z3yHHMl}&+r|8R4Vhc`HgH^~BnwvyQrSO4J;-M;{H;A>*JKnHquLeIo)aT#q+9?>uC z9!V><6wt5yU6SDP$zq4Au;rD($oG=9_jq>1Tj+0=3j>Mc;bD5r1VMf@8Tc2(qB;Zc z*vXMmXFAwJzBWX!P|#7EPHL8|mB^C7YUFU^2-_9V7g&@^ArZ82W8qYUZd}Aga%n$i zN5lpEL|dZDSImCAmR}Y$W_|#Pk^ixDO`nc29~kG4i&*=d5iL91Os+1N1~(Fy9~B!zwOZY&J&sBIS(7g7GFdM*rNmF9n|S~16*)m*8S1dB9^721kc_Qt^! z6^Kb4F?5NpV6>Q>mhLmCk*#r57I(9bpV+4_i11B4g~#D39mnZ3^TvVkTv zx`H`C47F)vlsNyoKy~mbCZmKV^RK9MKVkpYLw%d8CR-1ITW@hMO zRpAo`su5kNP(%AwRai!^%8sUkFcNO_h@(`Dt`HHI76v*OEl>e-Pe7qs=i1M}GN85c z`vvgvez!bpzYKM(dZ}?1g%;gCV0*L}cg4L5%IQ=Z*C;|~ix*?W??4Cp$w)qp6zoN_ zkqcp;DK6tWa6{%iBQQc_E0|cKTj1f+1v)M{8#0pbP5uRI>kwkZOPGd5D_U%Xy*{Bn zw%64F!de;`+t@eta0z?TX-2$?wt+fk5B7MfvH*-sw#uPoz5@$@TX~YB7p2$u z-4s4FcQ}x{C_1h3)ZefL?6v(9er~lAjAuk*YobtAzMJzd4 zg(stm&yY}!s9{J^ikp)v^wV07$gGS(RZwIo%fm-)|&lCtdIpy@y(k1!!4c$$ENH5yZ&o>I7vDnVY+ zl8ZJO1Q~vT-WLHAn!Te4mSp~OUh~VXW@@xaVtS?|O)jH7l!Yj+3mpqGE!TFq7{8}9 ztz_QNo6rDpr2z&rLf$MqP%(uj1M>3bzh7LaqfjRNdDzOIl0NLGK4XjsGdD>A5I8ZZ z)eXrPFRq3lGW=H)b|7E+5!B}IH2dNee77v@4CR4(vWr};D5g=K_^JCBFq;_taH~nP zPpfxP_RndmqfqMrw)T1tEbqLAJQBVn#1d*?Mv88Ot>D?}hrVD6jA0?b#Hp+!ySN>_ zd;_E%?^h5cK}nU;YSPY1N+s#y8^#FWUZ+@b*{b;=8;os=E$PvA?c2^&wo*ozhrW;* zG7H2NMKX6pW*Q69VMFLT2d;Gj18W3oTrx3+MoFM>whe_i&1*mm`_1TF#IYj+S98n~ z`72joo&$}Gv1yG_82z#1^p&7J`+feS`jS6wJ+fFb#@kyoZQh3!018JZ{2t%z+0*O+ z8r&cME%F1$qjLuj6r(qwR%8v+Gm$3aX=o@U6{l%>EdM7*o>bF6>(-1IgRJ~Dky)AN z&**tpq9zxvwJB-k7H$&OAca*~eoV6nQPz(PLVyHsy2ZMN!~lM$bM|YqlSy0Tum5T? z8JMC#nI7LmP&J)9>+l@M;LKGslJRvtP1y?Tf=Q2ZTM0%xdM<;Sl9p5CKCwux6MSC^1%FLs%vkphGA!OepwfHv|zhejvBM-t5A`Dc%8mFsTAgojjYX_2^ zoZk{fc9b^G{DcMr1ras-W56pLo?w9cC2W6zIqz+5mu9D#cGnw>K~FYxsp%|%!pWkKOMJiH2I$XIC^TA1i5zR zq`m`1azmKyC1SP|K@1SS2Jk}UR*=17u}awb-grqhGM5E4KeD8MyZ=Y^Z%OY&*$mEW zpHJr?BT7A6&yL1+IuYhWWnLrZB^X*77VYe~gs2?bE;eDcYv(!oJ${yNe^gS2gReD< zY5b|VI&`aSNAv;WNY20Hm*3epT~}t=yA#{KPD)s;z0LB{w>b^y!FH)G$jEl`iIE@p zMn%fzJTNu?UA`gzI$WmNOUe^pU|3V_Te)q|o$ol*$3@wiKuLv7xY`$_sNF~`uwd1` z3W_Xr3WdG%DtK+u3Vos}2Xpya__c~yrAC>sx*aDFJPB-TL(Oo#Kq7&q+B|OGGKmQ$ z40IN!Fd^yC1EtswMY}!wRFbp~wku7JRTpnE2?7GKyj+t|t!foY8Af@F^%HC{^y&r4 zwz+NZFob{vzAzKkK#2`1D;_X_1uHj0@<1|D7bfs^8Ka46#*TpBDBDrqctBh{H*u4k zX;68f4@x2zOg^bC%jxw;Tw54VlSze;(Dn{ zRdP%($-beaUaC^cbyUt?RpnGEU)@-h;)5FjDf>}(CE5N-bOb%Lv|1S&nuAV)9-6-( z;)l#Krh@lsJQ;i?GqvXbMWg{s_ucu9qJ0R$!c*dGVf(cD4$7OKf7$SNNR;gVj@#E! z4dXt=k!f_ruucDBY*xPs&wp3$gpzGwj>V=>a^0n?~LY zyS{Nhd~LJhHkZl7nk*>(YMUn*U6xal|Gbp8yh@hgSf%D*l6(YU+~Pit3PjT|Vm)WI z%^13j7L|>WbtlhE$p}UJ=?v48;ZO==RZ7+)PY3ajfvNCPT0t%F|GuzXv4&ap0<^Li za`XL3@oXIgXu!tWyj6gc7t&Wkhum(A1{~8qNwR@wO$eCtp##57F+2T1WN}zEExp&++zrm)xQqrg<>8HOAx_$k8&>PAp+tW10=Z zo2SPyyFf6CQEAIhbJY|@!d`e>(mg%J?n20-=)n78FvI~jmy_!04WnO`ErQNr@=4f4 z-yVDm+qE;1VKbNDU4@d>mgQ&2B08eZ0K1N;X%~~n`>U9g+UYf}){`(!Yl?j5Ti6Cx zC!7(-_TUDw@0^v33>s??ofMdFxRxB9J?7-w)73!2+!Bx|JI-dVf}7VOA}*W(xX0W5 z*J=Ql0%YXFV{CKs!U)W#!jxt_y8ImXu`e@f24J( zCO?FW)io|;@`RogdTO;%4Xo0ilt(ghcwideP)yD}^UuNdjSexRtFR7E2m>Qmz)Ys8 zi0+wp&tT@C5dA9T7iAxgaY&O&jWv0|go4NZz_rpP6-|b@LkwF|5(voYW_~7G)7)}QF`K+?q5LSD>nIv-|Dc&hjiOup;ZW% zueftrbL)4`xs=EM?Q<@*67l~x&gBg^GBEC3CbK0wmWo{Qj?Kr!Qg=Qw50hWzbR}QD zT@K~1gNvw~ey$GX%Um=_Ixf=^5RIZ>KzX$s43D2>*VRUfq3P;V-T3I&}};FRDq4H z_VN6Csz(U2dA39&QAV%cko_3u+zOI}AX7bip*~>0vRr|xEWpd9o~!`Hs7*bnr!;u2 z@U4lS>B3}2!6lJr(9p4cKoS{0@O64w(kbLFB6c1#t$XN{goTd?rhnO#M-}AGY;IH8GIOSxG1-t2LG(RFHawS=k#kKQ4sUI_2Wq z%xqbqtY}9So2sm0UnC<#ogQ8@VbLEW(2a4_68#E~V}#9tu`gQyFvm&@S9s#rxL%CW z7RD9vCW{tpK836tO2#-^nR0~{BIO6I8?)hkEz6~^`cs9z@z+G67KOHY$58i;yaFg2 z?-^^PGBpK?*_UQ&+HK2CO+=4n!7#`ibIvf;WzbCsV=JU0`o3dr4joqnX-t81jxVBi~-95FMv6l4CQjCR5D{#`_>2_7s+- zI97021>sW~eA~RDTRK#ksWB~tq9JiTB*7U~smxK0-T;K}$5;%|h_fJaBicsM!CTd6 z9~*oUQvx11?UZG`V=YX)pL9g;o?Q9ww_V=66yyp6xD6;|J!t5#Y4(_&$}nY%H9Zvc ziH2w$mw_Hh26`kJ=n*N!VLehlu7ufgeR;@I+-Boo%?F6ncgvuWyn+3Z_xTNal_?J*|0(|}|LO3x zGI#7g>sblaV0lYnaUb=u`obh?wtpxKe^^x*F(An-Uw)aI2wLNP2A9cd#U%!^TAOt% z9#4O3F<%STHG*l8SLMjQ;7GnF9+(3d=K&OvBvm9KhXNtpP)Gy=s?!KJ7qlhXQ?`_!_&q*?`|r5{kGkbpZHvXlAN)~_HhJxqxOqFD0?CyUIR zJwgpIs7=lif;KNoj*p}P^RNNqvBGv6X^Z;S72cdR2p<04A%)(^P!R2FonHYwDBJRw zWrBZFWCS93G)2vEt(=Shl@n_|RuKg@jfmnoC)ONwvN&C0&24F%HT27f8zn2}W@xhd zi7GF{Q#86LU+A@!|E%%T$V}8Clag!+*8MZ$o~W^kM2*RzL=7qE5cLqJFy`O>L=6%% z=KHI$$}Mh^63_THCTgrcO`?XSXgG3Cm8VgIrwGO=@-$9AOZ*-i?!NJaBuAAs(@(w^ ziGn0j1db?M_Jc^yf>e|uF}{XdswK#>On4sq5@cB+EQivnW)gscDT6MQfP!yrWn+5h zA=H7%d3oeN_sa0FyX!0fd;@Y&0QM#(4wk7LS0v!O)cWTYjv#eHu6a~9U z66sp7Ls$jei0|siZmu&^uhW50ZA?Tz~*yp zT3Z%A&qzZ#COmnURSyULmN2@nUY1)dV_uIhV@$9`4i@bVTnLqwEoRh}c(CH~7qgTo zrsOj&>-tEI&B@iuKCp=7i0;ffyb2Hj_(})(ie$Fjq+mm0d({iXa+AdtVB*Af>bExz zx2hhjUPD@HN*zDj!R{i@m z9A;lhm7$1ay~gTTxn5QzPx24gH;T>^{l*Id#~_F3wv5Y|f9?7FgZhS^0I3D_P?R(h zk=LK!rdRkWAZp4MH*wmvAsY(IKV;>2wF}pW%Z{EB0tAQh3Ya}U1WAap7IcO7n5Wg{ zTIw)u=sn?_0i;cDA|wK6(ikb6JnKJ*qSmG2f{7mf7#ri`NyJ50Y0k7lXN1NBk_U=~ z!zk>UpAiu?inRlrlm^s*1b1s^*&-7S1lb7*Dr4oj25T;r0RZC-MwQglQKGe66h2lW zDtNLsdTZ1NsH|%lt-Y{v$_{RG{{T7{%`*C0bVyMjcNs+r6A4}E=7b>TbjXqrmUrcg zR8KV7sm98Oq7%wSn}#fDRww-ggQJa@RzdyQoj(BbuxU`q+Os9mf|wmec3%8zahYjW z({AzT`9U&0U5)22;icWOvx0mc`DWx0O6suNzsx~>C;x=)`wz$3Q-`J&7zi@m^FNb` z<3na(1o+2T*}!pmqDE+!b39Pgf}#)Z+l&*4Ud4b4<1ARA@;=DDH|}@*BSZ=g3wH5N z&gw}m-(otsC+?5i>$R`JSs5qEdtzrlGw?gwnxAfTL=USqE@gLD-?B_mi`>tHrLIRN zCumMN-C6*rJPp20_6aTy_+YM~3jAxsRJ8TFLLPO0z16X{SdTePRTF}HBL7u&9xP2L z>vFy7W!-_*J5klU*6W=p&X_OO1^}qi9WU$>V>kx8zaDhtYK76^Y?!qK4P@Nf*inTF6%Nr79DRMfOS)QBM5tn)n7-L>RLsGts7 z0V7(%PT-}S(%K~p{4<*(zce<;>65Lk;=B>MqpyoK_5!Ak8S1#ya&Du3q$v9KZh>Mc zql$zXYte!U%q*Fe7$`EJ>SreAfpL`Fq7PSsXSA>Lb)d3te~g3|#N0STfi!Y@=I|pq zMRx8;7YGytii1pp%c*DW`4vD%KR1sAam~V zay2`E94errsa9|!*x`A;A0NoiEiON&S=!AWR8K##2)}GC@Ax~e)xj|3gZ8v2?`=Mn zfFNhx@gYuSWWk0`TjoNDOip24^l?=&s8AJ^)8&)R3d^UzqNgt|Z~JQV+xJ-GXO(v} zYhZ?|?P5`gXAeLf7?>!Mg``oQ@D4p^Ihc~30q1}Nx5I%uL}rqpGu&gVtg`_|0wVRK ze)id{q(&AhLl<)99h4}mCeM#+{N?PTAt6%*6OBeKm6ZMLqDm86Iw=9A>H%Pu$Ep-s zR$1Kpw0aqqcnt?STfwKm^YciYk`wcVL$pl840BGa_R4zwUyGk4y82hM%urcdw7w>HNCG!+u>Si~&MxlwcZ{7Pbf%OHFCy+>e_>m;a$u z+i}Dn`Q%-s&4^{(YmHn+>JDD$m}lHWlQM%udj1dNhm8AC&$uVDqA-jt7TL%JN}l2Z zU9)yiVp2+^&bYVr07SxX(S~-ib^R;x6h4I;R(kNThgkq|A^iLcoV%ZzYg0L=lNBj$bS zo+38%Hmu6Fm}m^Zux)NOy_kfh)~ZzHxVi}~-Uo3qVCb8;w(4B=1M&nFnM0azY4zEK zPKv@4r_}_AZU*4O?piXcTpp?zFt}ZkeKH;d<&F$`YMqPAW50Dao#8L3!sc%g6jkyW zp6Y9OQeLmKWF0HCii2~v$<1ngKfI#sUywxlw&=(1M>X$<_$Z_jTGaok=<@tmT&DqL zu5?O|6*>!UO{}bavpm=wnTRSbW})rkd1jfC(nUiibF(!9U$PN5 zDr`p2{7zS5vZnr9Y^>#!+yYn$SjE}UsCeV3iSJ?-Dw)NgsImYiB58m!GS$pg-$&W^ z@tJ5rMxad)B@9m`p!L!J$KKmOTXt1-zWd{xd(J)Q+*`Z8l7gz%eGaAENa|6(jv->~ zSNlFpDiGU%X*=!X+3C@~@fcNfkHpkq#P>>aNs16OLWHP6QKFu$in!Kv*A4Pzd?7drHQ_9IH2BTO_d- zRT)rC%MvCQ|JkNn0Z|^>TcXJdOH7RxgJ7X)N(13I3@sRUGYHhjpog>-v0xLS46o}< zjbe`^ZKJMv&2pPSlxT@lIjUTcf;%+5YZA(c7WJSkZu35R$6zug6RaM~W`O0jI*RXB z);03)jWg6M76t5RP}Jafkb_0&YD|RE#@QU4k?j)F7`4lPptza5I#Z;7SX{zXmdGlU zI$%(|!+TIEKe5bn*oRE^tD%d=t~+qM-}QLzF0|NY`m#{`vc4UY%+x};)Q%mHJ1Q57 zcUrXyQ>D*6>78t-5`P%b7jn31-Giv$^}H!iw>Q;<=72*Z14fKLEYKkOy>`!kV|@cI$KKUfp&2>~6ZedL%jE;&OP&EZcr@hbn+|fwdrj zVItOg23h>*wY;%e9;c<<&~r^0MJ2Io#pM%O+K+~?y$!dXkv-f{$FGZh=pU(|=aMwb z^IZH}B|zET0syz#ZRnyzIFEU#8dcXDkOSv5EJGeC5Rt{5q;X1=VUL+jjh&9&gKO^G z3^EWO#km&bj=ZLzOF$;jP_InLl)=WL0b#j`T$AgnusnSb%huZ?!?qf1mus0E zr(Y$)q&ri%+;67F4FNJto32~@nkWtSVQYA%j)|SH;?;Nj(5euf&G`1arN2P4HUps( zDkGx}%&_M6z)M&Xe#=0lB@MF(`e4)3*;A4L>JzH;DXd!FDBXh28*?CPaR`H$y+|oh z_!)=|L$?}+8Q%~;48u}?wueFY55^Sc`Yf7=(w7ApQpPB^#bOE(xUQR&$fs!!MP{&~fYf5^v zY_?r6_{pU?^J{^-#f;T=sROG3^^z8?)Lh-5ppN2Kj31$kFZp4bQp?XotyOd>pJHL* zW9&Bkt#mi z?Ftm(32P)@1+eyfA+0JIAE!;KTJpGTv|5F+uC2Bhli_=-4V!VY=U=dbA8nO>T|M$w zg4MeOD}bUz8-R)Q5%R5XJ2>91pCwQu;RT^IOvEO}yS?1v9fK)hLsXIs&8b3LLwqrJ zDoX>5QfaD{>}Fear?vLWGC6@>jQx<}3bfmYTC^9R>Cyieu)Xb&?*i(Ia}`yo|Iu-` zSg#+L_F5fe%LMuXOf8pxz4%p|dGdI8?JRW|4KXWsl*2nFJ(6I&8bp~F#@$ic&)5F& zt)!+$nhhD{gd{hol}sQBDMWHJPm(s_h>8JNGmen=K8@)eLcAvc*atGf31PIQ>Q&0cqsvwq@p1?kd)NFAnL>t~ zQkC1V2rCy4pKA}6O)e6d{3U?lTAA(n}bX+nMzKPC?mjm1DCq2!spvB z0U0zkRjNEK^kW_bo@`~XTna9}P8s<+g%A9<;+L4L>AD3Trap{zPBw);sGBrf_d%6u z%CKBgP3QyRE1S>504r_*R{-9KOcvRVDT1>#X8{|2e!#geONDKsm-%u#CM$)r$Z(D! z_@IM)USM^*t;&hyyeP{STenmxOxAPkBA}ra{%AkdAy6z;vfWo%hG;FH#A=bj=(%?Z zg`i@Dgzc&{Il-5VO~PX-VcIDvq~(*T9MS6EDnO}I1y63tM>sI+ZIKFap7VhrIRQt{ zDR4@QG%^;y!Skr9jqX!lpDGDFE0hZs$YI1yaX|Q}A)!YTf&CJ;LMkf)S`+=843w>i zO?QYFb++n-jHJb{83%zM_?^rps3~DWO)u0UA^e{1L-j=`jKtqmh%AL#(ML8uK+wlX zI&3YDsyQ&g8%5d~h-j@G6~C^Q35*hX)8`PXGZ}H`uD1mi0*3#f%pg2loZ_ET0F%EU zW@e0};Lg9@+%9(o@DtY~fs>9tFIT@t2unIcoBVV2YgpWEpuyVX0OZX8xm&HErrWEk z&!E9U1*D-w_W^}@D%^>vQ^J)Bg1oNSY_fRX_Gl-g`NTm_+kYvV^ewr#sSz0NXu#Xbc2lo$o57C~kJ zn=DFDqhC#R&0|CYW%f)~hs`XOU>bk`cQRcO-S7AYgfvM) zUwRe;nimIYReX^u-fdtRqKurlV)eAdY3CISBg%-rYkIto?bsAJD^z{XNvfV-)91ah z>QL_tcLtDdWRx}lbXq&0fA*S5mMp77(W`v%2m$8rrpc+pXMu*>m5i^~R zw}WGJ^$(>?fP*UDfkaXc%^=RFp+m>qZch2ne3Q zk02Q}Ij|uh)3ENrght|wxP~q5maP3}^6NGUV6D&Q6ey~MgLp%4HUS#~MIkL<TsPw1}z2ea_z~_0+UNg{$ajRfVTWJvgNtbloA@|RW^fDI5E zriNjHNzT@;m|h@V(ez#<$hIF>$8H)trOqT`V3!5=nUGqHHlXz744QNA< zjMgO76^w;V8i%;cq=fGW#r^_+LMU7duomI(oDW&)4*?iCJk?K&1$=psBi_V{9LsF;MejBd#dsU}; z7Aa;0qE>DaOx2uF)Solsxf66IwMmNrcU7ngbdUjOLmg;K#C-} zUbOU8_L*1|Xi>IH8nIb*m0=R;saL+28n9ANn;rgHn$Fo~FyCXVna!n|Vi`Te&SLwC zk~$%a1wqoLib9(F;~gEHJ{7t!0hs8TPczBisIoF=4hxxd9Xg7r2+!tLgV#i0n1TuM zT%p{2yWADV+1Qi<6rejoEn0S!D|U`S?qv8a8d(<9ctusqmdM}mgvv2MIVtQnQy=}7 zaZG=biPl~)9c5OiJc==MicgYzEeP%%Y;zKF}c48svg z12cwra0-8R(_~Hv0ycHZF<`Y)heiuF+->RC{=h1xIf7T50Yyg}V}X+hso{5`9Qwh? z)ca&O25`(}s_J2+pS0eb9(n`YOou-@v^-7StJ1?QwCKcsRdB%R*pWr|MmRv^2tfg0 z(k*Tg-q8$*w-h$UDKE@kJV_XYJCo#&?-LsocaR&HZIZGA;Z4=@Z|>HEC|ascPlowE zDzdK#4a8{02*iE{9NApmY(scf1gMr~){W4Ex_whgtbL}!@MLaT4#G|9hJ7)&+`FcD zpXR0P>`9guIyA~N-dD#Im{D!38^Pv8ku#X&Ipr@JYoK$>=Ui-zok_tJ>nH1)(-ura z2sw66|AKLx3O9$Blf#Zhp}sKfRJSNgsurdl@(!T9cB-FJr$lMes&*K)a3^{*5Z$g1 zB0Hof5|keQA|kcWf+fHe!18`oMq(s*vivV#XK^%ODQ!p}pn!$W$h4;8YPyAegV42{ zH{JiZ}`yf{kbT37!;!-gCSap!sY66)@>`q8)Nnq&JEfY?q)=}d)vL-m#?$EpWg zL-nTN0}6Et7iis5R@govrhs9b$uMj*3k=^vZ5Ke&xATd$!mGWk52igK+E6^7@zNpB zSe91SruwDO0)}0;;EOKn7f4xD_(nQmdhZ9tXofIDrdD$`3^*{7>u{efXK`Syv3e<^}v=&Vg`*HEae zHI-ep4wW@c9aX%6-~cv<=G+-%UA^kXhFc^H!t4>dX|trv5_ZglF?2ruJ)wjq3f!f1 zyNkPp)MsQPtc|g=D(9S9T`&}vWwP;|f3Q@KrC%Uc|vFoJyX8Oy}A=VsoU1uAi!)G zA5w8D1*ep@gb7$917yc_9M)i`VlEl}e&aBzX~;xzzmu5goM-gH9Lfd@P! zFiD!Mv%N~(mFkI>wsi`A!@e_1W=}K4I@1rC13b)&l##s$b~6n~?|*GO!Dtk)F@~}u zkg3lWzea2lME|Ss0Voc*;HN15(E4Q&3l(nBoiwo$bwpLm>JAlMPp4Mx5b55Jwx2Sw-uGP}tT!5rq}jOK!Xt3WLx> z97sabKLdpcYX~T8rzoM&;=GP1EPI&5m18li(XiMGLK~RH0v6L9>$el6nWqUj%J%Vr zS)wr~A1pbRR9cw!$WJVrbhjcT&V~Z;t9!ZcyM5fK&cM@|%mvh;wQ#JLV~`?@gh-D0 zP9=eO0;R{v)gbH<-U|nn(ULuCNZ_NQi+Z{!&Klb69TecqAti^Q z@D;t(uH68(@DrGboPA1C#>D@mP_XI_VI~o%eaWvkay2MeP<^x@B@3Y>a4JOlu`s1? zW|{YeC!IV=z|~hUVreFU4@v&!32&ccT#?x&D%m+?w(o@My>LgsX{q<@Y(~#C1V{Gn z0CH;;d{^oF`9962uWNL0VNm2bbGf@lk;gf!Z4c1fEvQaRAqHo)gdQOoPdhq;t5)LS zR4Uz8;tO*Ct)s-Nfj!CQv3K1RdF($5ZC(R~j;}Eh_V%iSL;>A;#fOci3tbe@@I$zwN{(%#_+*3YmQ2|7q;h7Mwi7DTVTCPC>z#nA$URfnnUC_QHi z^eoD3<-wh@%dkulGz9!w@1bu|?{VE`n@oRg-R9p;%|EPqQ%674WMmOT2FXa$##wL0 z`y`HQ;7vDg8XQN}6S6!Mbs_|M;WZ6k=zuPhJ&D)?D(YK*=EEy?nj;W9Qr`>Y$l=XQ z#Y$rIFx;c|&>c@2Pr>k$;al@*T^tdxl@_lesPA6cS z#~kjvQnP%+?bH(oETvn6RSsggnL3sUjFj8K0x;wjpCQ)6!1P@{YCp3+faHi z!$+yWKj*{V>JMK{q4tM%EH%BYp1l22otI#&>%x68eh!C5)ZJ=cfg(JVrW(4<+O?Ez zQ4zM*%cR}xT)SESN-1n|*-4xI_C_o!UfD8Y3pAjzpuwT)8!m6v2E{0YUzOCI8I<|K z;%~(!=(!Z%fKEw_&OCN9CjC=0HSWI@R#{FSI=g}+LcF#ne70-Tk!%2Cb(8vk!@=}H+Dg0o`@Nw~l23M?aseD92y zkJM?6Tvy*r8Go{+Bk&Ghw-mh)22>3-yVf$i$HGaG^27q8{RRntrV-$}1*;pI>cF>K z;W9TqNp;rMB8hQ9{Em9!IAG=Z+ng9Z4QH`($@AG z`XW)r&Kg}MH8E8w8mc+pHA0DCAfwr3^m!8HzTY{Dk$x=Z~)HKkR z5UEYG18p}Q)Fj!Av;%Fofm7=T+CC}}4VlJvpshagK--#1SoOgxP^I$1KXMo}0&SUP z0s{!NCAySQsSY!}MV$hJwv{6fv@Lkxhh!?ywus9VJYv!gM{wcHcAgRqrU+Zk2=zc) z#hiw?bti<8D2fmg^WPZ(PPXn0sHd=X0Ye`_5LeibaQtJ8qTLEAD>@eItf8o4glWPX zDFUThBlG^lDHdO2-N?MvOT4UrXN?f^2yuw0saUy`*AJ5oFl>j(lB8E*vYTU=ESs;W z{@}z?0uYxRase@<&I@sz#!kdRp?O_w) zGCwC73VNaE;ysrJj6cGO5C~0L}SpB zsw{*IRY!>+9Rj`=c5Ld-pO}V09jwb)h@5UR-ze*oBFAclAdudxhR@S;g~iB*n{!gb z4cGBj13=XyJJTeuk{1KjWdFfah<}DKq_V$hQ(6mZ?5cA?Thswh8;xi-J>ldL%>w*1 zbon#JljTK+5zK1|Ie(zAheSRPRz)(`r4mXx-Bs+3Yy>S2t8<81mcMLRbs>2r8$lro zG0RXUkw<@~CfTiFG0Sd3pPA5NUs{Cyv({Gq?eSqxv;!sr=nKD@K!|uvT7pPLJa98 z;evuiN}k|wYKQbLg0$jT3h9O4x|W1nTn-w|fsN2YL<82_)W?Yr(=Kq@?8RxXAt_jx zOcLL^077v}P~dxwhye+=L?qN1-A@e(A2V7#H6-+Jo&pm3hffI!{nL#jp=nO5k#I|l zoo$0Y;2+{$xEwGhMctlaBLT23O%`InIoQZ)SEXj9go_Zz=?p-w!G&^)h43UeonxU= zX2IdKP_Yu#uJ8vSZCF%5miyrQ$g;Nv0mwWEMCNDlr!h@r32^%VLlg^Y_l!p+dvsrQ&< zDaD@}!w;dZ@+lgJnbL(MIEQPhlmJ<~=9C|oSEfz0B3+F{hqq=H*{X%8^74qi$+{LL znG>ePwC<>I4{G0`4vGxru@*ak)9KxNdHW;Fc#RGQKbG;-HHQH`4yEV)r3~Ogg=w&8R3bnN?NAfUT{og3w!~)q3BNJvU_5se+Nx^k z3lFRU79+#Evb5*Xp7|j$^jw++Gt`Rq)O2!CZE*cCDh!?&MnTjbKW0RG*3+TZdfy{_ zkck%@*4^sZ&i>5UPEB@1lo`0xk24-3H&s09JC?yB#BK)o*2^(v>5kpOTI_2+I*Dk9 zav^@K!o&h=w2gpFe#srph&>>4vtTZx`*mrszbU-Dx|=_w=c{T_I={5j1OU z=RcFS?E2B+tHFY3%(%Y4_aF8B!i?|P>#jnIL&{`wh1ZD5=}T4NQEe8gd5`S%#a|19 zs|T&V8>UZ+V}{w_FhV|P_o+q8QwgZ@H!>9u@6BfAYO#jKL^7D&K4GK2971}E`*jbY zkgQO!ckx=BKB+z~8+`Ri{Vh8?tE_^S2s>W`8n~=7QDlvS9oah(cUMpA`;^s(d3Z(* zNI9C#l@kC{NOiZ0$`XyoIjqd>uCH@LiOlB|1$mXYQ@{!15p{0JZgOcXlvlWXA-_74-R`Etq%I0R1bedlfg*Wd^AyUY}#~U4I=zl zMhIp}CoDaKxs2cqo{$9S+3p52XKNh(FJ>s|bj7dsB=nO%=P8i%dAKE0{`@=rFo7l8 z5yLS5-l%x9@Kofvx_V#Ov&@%Q?zS2h264i$yB zrqUIk3q^GPuxH61e!uM*@KP|d;F-?$RT}gEtwRuj-b;2aGUb{%@I-$aalplKRqE5w zeCJ`oy3uA)*;`7$)|2+(`IZzAjObkC@vc(r-wfgao^$!NVLQI)8s5ej4Z{jyH zl4#t7Yb?*blu1vDf3ilybI`WxX?>OyU-akOb^jWiH(Vt)?Uv+c3SG8(IA5?!O`*5x ztt>_G`=`s<_tk)DcVyaMdS(3`a!j4FYQ+)L3rt0G_>!u$#6tHw$>kUW+3zibIjBMs zjxf*laM!}V{bV@&Hkz>M8K*W?W4fG!lVThpHCq@o0~KD|+AH{x`qhpg$DYk+4!_Lc zCpZUCt<4R7UvD+KKhO`htLnP>>LCQwiP8N%i}{dE^Y!kAqnt9|aUm4JrE8P2M8${( zhOb~~U08u5`Uz4(-S#-50kKcxVRej zD)Y2QY*T5bh4yY~2r|uB)S$+>U*9uMYU7-O9Eku@Xrnyiq|;;Y&@hn?Zx<8ufL)7T z>dPg9l0oBUy1$2W>0tiy`mo@2;uo*1$mPy*$T9IB&V~1&)z z8M^%W&2{J{Yl}0;p8JCQBI@P#CT{ns;Tuz?i|g$vlgRbX)Ms~8{V*%fYHn<^L>ky5 z3`D>?$$*;m_((J$V6OoY0{vwhP!qBoLM?8ks8hfQY%r^J0{A9Us0>4thLy(-J>_l= zh(k=wMK8$dxU%8Ck_!*^y&%7gvdk#z4fNv`bKB>Z6INXPFp|NrL+XDKj+ZAE&`aA! zNpIQfscQ*Aa&cnA^Ez14dT{9r^6P|Kfc8#Zf$ikZLptxY-CHYmps z7s?6jC?G5;%sNWzy}^1wSgS{fuhcvGnql3eUeFtIBioos-qobUoO3;2XI)VMvZk-* zG&-}g{$a!x>8>YzIg%ZAQBcx|zlo}=X^};gp&Kl0V7k8$Mw?3I9h`}vfdifu&7 z_Lm;o?ejG&wA<(FsfKpn(+KTWnD@-k?jJSK6TGdw^ca>rn43A)9$9}!XDL<4KJZ@%|xg=xv|E1CWE;hlj>k@K=5S<=2pl!20S=>dP>3E zPfwg(Kv1zq+fZS@hfh5s?x{s33esJJ!7~+=_|3NTWA(o_;^GZh>cpPlQp2B|889IW z!W*z40HT>el{Y-)?q45*Ds34knvp<)FRC{=^bw@Yrpf_ae?M$$hg6~f7 zok2B1tDjP@o~GRi01lMZ|E=8#H?EJ+He3t&Si3plp*+(fG%)Dt<}nEgc7OT|6h9`8 zvzkA@fn64CA)`OysE*4d+9KAT6)65C+h!9Kp3GAtqjV8fz$+ySh_9SYlSB-^swob; zsSr-3906>*Xzm|Vk`1B@35jU#rg@jlLwo}@=^JTw9%<^E&eWinw^eD}X=W^%&a+-* zR?U+PLtvh0LQZ4beM1fgttr`2LWHzzNyJ?@STsN{doe!C#1|)~7uQGrtVJe^U}wzm zXPwvpf3|Iy7Ofuu*XGZ_a>}2}1v#jvk{Vvz+t|jM?>GCLW>MZLM_%dtQOe>r30v3< zb$uIKy{XOS)nrSbP8;$>#i29&XqJv*PKGJU6mOBWe5U#(RAFO{p^EP|^vMNTwB>LO zvY6BYQ*1IPtT))!xfWvx9rG}dTo7XR*@(A3#+Xvo!T@oBd=0v&wU!3v=w%uh1F#vi zg9U5`B?hAXQg1eltU1y{^5tYt8%Lsla3lTS;z$p>k8GBReE=pQq+&#C zS<=5Yp+R+-(6trH{t$U>>>LO!%=vMEW;4HuM+5OIxbgjY&c!Hnct@V~r>t%TYg^`w zzUFlw#ei4*q+nH3Nzg?<$_fWw$4E2egK3-EVc4%8>`nPtzhXYb!Qf@mN7X@DpF)>1 zTk6E_P*LDosP%im6+}*XD|!w)$aU$#?+Z(K}zs1S&a)bOU z5X5!!Cn1OzCfPoarQSzSS9>#5WZ!(_Em@>(o82MGA ziTslRl5-aNi~r6>f8*9+w`@bcYpbSrqrLxx#WVR<Qj_t`tJ8Q@q6Y4-`M^EOCh=@$heRP$1u1|K1A6kgf10 zU8&?ou6E{RZU$8MfERD|&y|5Kudc5x^Ek@Sz*ebU6erJ3x~)log)jz~cdr z%fSZw*QmrhW*x*ZY#n6IzJv}=$8H_HA;rYP(VPkp;LH;y4x-M{M4D5`UNCl}?&tXt zunl(S|8lODX2D3>FdVlIoK`qJD1Ocu37Bd|SX)&R9|pb_KOZNe&qP3Fa!&&rExRa8 zY#qgP#srgLr%|*RE1p_nxg1P2&^eOS4i-DUQBrWB29p}E!ad>Or6-*;l|E^6(CIxPB?wV$CQLHj?pTa30>zKnMgr%w(t9MG4IeZqw5~n&Li!yUK@!>XX9e^o} zLU?lwY2*A_lOZ)mH}F_td7=Wt;)uG`6%K3RJWchpw!lS{wFYG2gSvt>T>ZyG=<-Ht zjeG-DS+!1GyseQAhLm|h%k?8EXD-h;57pDbZ~q%6y#kVh;{6}7PSI}H(H5j zi9Hs#{3HtrP7J!%K1*zU(-&upt*e5rIqYl0`-$Owwx9SUrmtVmv%yrynXd5#L^BJi zcY)nUV1=a;$mAJW44Ia|Y%>Tk_cKumx(ldeXPb(>3zdS2eikZiB=^b;Y-%p39Gh&V zL@!w>(P^^)nbOg+0L{q1q623&C~(w{IGSmscY$X{dZ8jnBF+Ft3Q62ZAJ8^qT^KPp z;`QhS;MCMQuyG6g44WlmTPK@}paeR|Lf}YUTL^?;S@yJ92mrE(mj7E80-fo$juGDQ z)JSTrf#AIL4Fs6!YWsj#h?X6?Huk#fr>0?zxjlY+@^#}yYeL^HSse@X@)-HXY4DHG zixY^n-S^g(w7lgx4@@PHzGgyDUNU4zKFLGG}iR z-clY~`o?Z`KSmSFbtffFtW8gpl_B$++zpsa4~z^O=%NXl`V`wcqnrT;KIE>=C>NO$@bocO8c2-#QzM^8H5b(4^6 ztbyyE=@A@%W*g|R+&I}BJiH!CY$Oj%r`mn8`oF{}UcHPv1CKoQ1~xXk zPd!c1i?ej9kHK_vhg@lEY;@KNVNm$FmMkk|y+qXvim816TJ{8B->v87bNbQPq`IxBUa_4XHAgz#02oWxFYq@~OVXeaDaTK-`3}i@PN~(;`dT zd#bxtOH4!y_R!ct@2f;qskk4h>9ud<_VY=alD_(zBmS)*@H##k3Bry2LyD0{w@9;m zabYw}lEHkxH`nc;l*^%-SDz*=fTC2`U`3JUSHIS- zA%oVIp@Z!eKrE3Jsd$ItvV00KDv};6e}&gcoT0b%h-AE~#pYI5`zfnFCg$4AI+I!T z{L$$Gv7VaJ{dJOCGa>TiN`Il?Mt<$}%f)rE>_SV>mA`@uzKKig7x^Xrgh(*GK!2!? zOSOfTC)qTc+HsKUyaj@L&&XGBe_L46Bqc4gmNPe?!f9>yDDjgDsl5n z%aV~&slQO`El?BN1S&U)n+cw+&Qct*g1?9>>wq(Uj8L4=&{VlVv^XCv5a7Ei{hQ5| zcY95bH=_iFKEEJ;1cfN(aaLHM2HI^Uaz36eY-SpHxHglsH?t@CeCm5wVh8=oXxM>1 z*)04TBp_I)-|x=#u)m}PS2i$cz*OJ+I$=(46$U+97(}SuUIscWe%+$MY&Hqi6!z>@ zT{_+UQ;Fx;73L_GE>`#VM^tS$Amp>OE8hygKcX2vy1MXxG3uh!+rA+%{2mwUx8nfIe5eOMshx!yBw{WPD&PV&BKF zODWspe>Vlph9^HEd=0twj&rYMdqfD) z&#Cf?jc?QsJE-e#PZA79a9XR#o}ito&UXt|nGQ7uohTPJ96ghC)@g z%j{jwUdBWw&K>Qt-+%4Zlr?#9obD!%bjo!undrxAJ}E))2z(ItJ)Q*Fzh%gVJqA9Q z@8c_3xxkqzt6N;d>MWR7jB5?6vsw3MUyvW~M79(eUhMT6hS#Oxbr>FpFm~DuugT_e zu{o!qtoU%`3Zl0kH4raUKke55>UjgT^EXpW0KC9`jtfU)Z;)<_D~KXvZMRr$n?r5= zP}_dZnm87DdsN#+UfX_k45rbl4Iy9Gc9+$*Db%LpCRfupejMzTC#St*PJ7T)O?yU% zkNfim-PJVLZPMJ@G~X$Pa!GKG5E#MXM9U+ViclFZ)q|4i0f@8A16R; z*+91BAS)eYuw{{s*~_W0=FH;rvBEZIj&mTXVHIM{(j0)@L29T%#T2NAb%kQ}vBI|C z(DAveX-zfVES5*bIKn#15H;ET^BcqxbPl$rJ-T|;I`P|@YbZsbq5FAbdPAG|H$?t#MNQhm=kQkaACi1>mEr^MG|dN| zI7aMNpW`ajgNK<~h1#^0pDX-S##L3bovXC2)4`*h z)?0_cKuL7tY4b?Y6M|+PBw?9tv`pb zw$>b~_ArmnmB!vRjh!UOX?Z$BuZ5ZPhA4j8!fdC+Pf*s4ukcGVa1NKKs}liMeX_y# zq^rl;ZfOa#sQOEoS#6hiZ5MfMCd|HZRc%t{V{L6=hUTA^&uWfCEIgKd&5uclM1^@r zd-~Y~<#?pMQ1Pw^chJGnqmtn!PD}eHR)GS{!a$o)2olYUQk{Lq8hC? zH5W^bS(1UVY#X!Cc+>LP&Dqm(Czfr`-Y8(2i1K&`*BQAZ8T7)1k z4pA#-XVYm(rK2HMQ2o-%=gN^y_6C%%FJa-swz}!Ur^~=Mr0snP4cbEXxlYCrUTA{t zW4_wDACMA}vEjJm@mNexP081{W&=ynbU2&Vq-{?}Al5TILK(qe1d=sQs`_if26|(` zdrRs|;KfF@`<1Yz zC0!#}f^>Bea>7-k^U9^Bd|h%Lxpdyb!W#Ly!R29FzHUIao&vvY>PK24(xp`h zx=#FYYxNGhZVw#AxX(8?d8&9{Ec-qyyRGIv0^c5Hpi=f&@P)97N68`6RA<%|Oqo7j zEt+ItlxSK5N)h~s?KZx(73|a6HMCT+!Ra!(5Ap-09tU?No0P! z7(^(}{V{{t>D6cL`s^BZl*N`mEep`AZ4--m9xD1iD5G0r#^BpE0jyEATL5D=m%JCtxFb?qhOF7*{NNx~}NW-`UJ;{r_ z_*CeXGZUUKG4AoVc)p@<0VCh8fh!JpbNQTU`Ow{!Z2Y`6U3uQw*@NI^x%q55$n{&) zHMsY8Er^lTQ}k({s=(gO!8pbLuH*Vc>*6;rR^vDDoQlnQii0tGY7JOx!!ezEruf8I z6x-S0if$u3BMn67T*KCKa19fj*rq`0$Ta|&mLiV}r>bG~4|a_%57RX#&9VTXF8f5Q ztSK~FBK_%V3G#4miF@*LUVh1}xbQZC8r zqo-Oe-6#BOkkK>aC+ata+MG>Yq}ykv!A(H`UZ+I^P*|}=!?xlMgA1Cq*|d0ZL~DlK zwq%ekGFrC+bgoM!jFkij=RGsgP-8kLUVKeS=CejJaWy|^LZbg=fJEK*sUBivosb%4 z?GR43q!jxuKHQb{b1_ zj%7b)gFY>=R=Llns$wkDcH*3ps@N(1I8IyipB%eZEGn`l)+6$`JR}Ljh=UFkxLN$< zEiDJ`$*`sPqt#75W=;0PAX#uhUSdBh-2)*YDILD9J3Z)(_@Xoh>c|L?Shh^Q%J@HL zs(Ds~O>&B$-vL08_#~3oFDV!NVt!R_+r(m#pkqZA@nFX|uhOxxa2}PEBE$%zyDlu} zx)kV~a>arQJ@!A_{l#ERSK>;GIuB~V6U<0D}s$eSan$=fM&q@vP){Vpwa^ zY4s)_TXs}-1X@75n%Cw;JKVBwy^&jpETI(Ee4D$J-GlPmGdUUun~d11Ah{W1{lWeF z_1OcqJ=*mdd#P=q#Xrd5pe~h``E^)b_Y)sRfr}}wjw`Gq{-P`EjEzB02bC1}L|m=I z#{Pa}=U4V(*w`b@t(s{5t>fwC=#oaxmQ4?R0zAz2Me$5}ZDX3CA<0eeXVRzIJtxo# z(PaO{0fzU})FFAFan|Y4z);D)BNGK{DPTyDPdUHNsr0tXjfuQP*%az7D0#Ja0T-+0 zj_x(wBK}dswUzHlE?NNlXB!kGJW7hZRJ%>RE?Z3bQS(Wv|DY?-9v1K14&!YOdwKVd zi|1^qK4RDCj)pVx5@hh&;$O<6>UR|p)kT}hrR-nK<%Ty<$=TUOWiIm>8#m+0^*2Kk z`~q^ek&{~;9jOg$nx5(=eHZn7#>V`cO*yZ6FH8!$>zlo#aQ&{Z0XX}I=7#pf6MkZK ze8J8n+YrWa8KaQF4=$j+zXonx%@@&5XoN0!LROu?G6>tZ+!aUW63?+DZ=9csEf(Zi zU!732F_CSfZ)L>Xs+glloh^-1EaNlse`N3om184}my6rO#EnP-0>haO(-Uk89EPrS z&E#1D&J5O;Av$m!SPD!)*em$G)o{}8Vf#d2(4JcF^J+L)a#nR^I!4N`9b>3YJMY+Y z*=o~IUa(G+{8gPaPrY5*lll9xlj_qAX|@li!FO4DM#p=b!r(_}PnTuMb?6Lff=QF# zG`bw7eV4?j?ll}|mW*`oHNS*da}Dj#4;E$t`KsJI&JnZI9v2ThDMT_LbDS0&=^rfAA&t)j)AV&}tKpffhN@?kk#ufwl;pAg0Ck z64y-T56#IDEo-nWN;nF+Mb4JUHCG8Mkp@@MDjsCsW=y4M@f3w1iUW0qdXum^9J@L z3!Iy zcUN`XuEBBhwJ^P&L|vkk90>_Z;(ry391hz#nkw^Gg5f#H1r0AV^mu@ z!D;Be79n+PX95F_gwE|cgx)dI4f-Q`XCQPls?m-Jy>%Ko-D{tQ?u5SnY3OVrmbNHk z{FpUoulQ|2Xd*2MQM;B=B9qL{yPHc*q2@U~lP5KW(!oHXx^oKch(a}>z@qiLNudnS zDAaZqgcWG=^{F!5*R-?MQ}qj4ReTjq)n_;7Ia_=BagFxCbd3gQMtJXnmm|iB`ji~k z6F7K$Qfj4vU~y`*)ogVWQe8^rWJ+%E@tZK0Le6nz!dJ-Eo5_2ioXnj`$DCg}3}ei) z6`&o|_Gc}!Dd{E34ASErx@xvczM2gd=|jt*QHc(35;~1dY41&g^8AAa>9HWYDP3R< zC3;-D3B_C4le~qk_i-FPy=3_!kS%kM3R)K8HTWz$av~^=0A!Tu%5y1t8uO)fhC2A6FFmdbWB;Z!|V%`!-g@_l5%V0BAIAj_Y)p!7vu(if!>XaH*g z+p7^j3*-zkwgs~6)?=IoN?(LpLs9@cKZHYwEP;b5L+hk3k`!kJfZ$f@p0l_NwcjbP zgdl_hpNWDWj~q}qaXDg4_v`L;Daht3!s=b7ymghgbkY)ne(35%E~lpom$8~I>RhuQjJh)fi-HF~{%3wbQZ zP;rB}so+j}bEavDkYp;ckss#5Qsqi6)N_ar?zPW6pUjy-nR%mE5Yidll6uY#s#H)f ze{XLmGGaxVU#fO)vO|NG$eQERe@{B@Y5r1WG|I?mBrQ_eG&VYbW<}m}4FHI00KoT| z8bA|pN+N0K!K^WMBB>`Nt-%vunQqJ)pars}Lh#431=NdRMoTY6!I!>3-PSw_k2PgL z-B7w_6i6f34k+ivY&ow&zMRkFb6fiBxi9_ooCdN(7UOR4At2oVbhoZdI6>()<9DV` zh|sE3bSml?Jr*~Kt!oZt&a|7+gs$se>o1Ba+mquM2m!72il5OpBQxDl^kTK3BYTwg z=;dk_I-P*_m&n%O2LxEE)R+Rv_@jmn4Tl2)dK?fyEU(de1LYZjFh(?FC{JsvQ2q@K zl&7PP^3FOyy&kAX6eUUVH(n8_XBC0NXedg?U;reB-vD}_uJq$xt_>Q}!Gu3s(2gi9^XAd`lzn9Qyg6f$;Km@)1a0pvA|2reipH7q|nyIJru@^GrP z3PGoO<$PvT%R1^{JFL+R=*ICj$X-)VB`Z{xz^}2`6IO|g8*iyck9HMD4Jni2 zA*P0}5So5F{JODWL#xqG-1q^E4WsHW?HYEzf+7uL13tiDRr+c&Hv9myz4|OMj81xZ zNkBQ`PjurMMctj1S^JjXYWG;*Ln<-muf;?h|F%*>be60*xcysJi$qy(etAQFy=7 z|ET0R)ul7Zq+8Re4p4;|I8z;)0u3PEvW*P$GD#hGb+gSNAj$jj`J?&gzh@^8oR?uLdZS zx$h#+V-5I3Ha!g56laKEF;Rqyl$s7UwU5qoDtju@aer(N@GUIL{|!*yq-H6kOZ z#=2v^4Yi0qLGnZmAV7-PO&}{!jQ|xN-ygdpV58i{W3H@UOi@WD#u3@2n>{QHfz^w; zdrH=k05U6WP^11(mxLEqbXKeMpqVJ=2n?nfza=Y+6bfw!yVE3r?V$1%)2tv@MGQ;ifkS;XlUQ&Y?7OIWepUt zvS6{W$I7|__4JD06knORG1|T0^o*C&6^{Cuk?^;mdW>iN35qGaRKbWOu+--g%kWl& z0rn*mm3Af=Y}&GzLcro!2yXeUCAh_s)JU+O^Zp%1rTywx{2GFY@b;KWP}{6%>ZGP* zZH5jj7iB9!f@vEwDv)FvR%L!H(N%Upb0-N#=&qoF?2T&{>)VG+a+=n`q-~SB(KaRj z_)|tetZQo#uxugViLCyoj({}SKtO9LAmGzt^#=jRenetGK(GP)5W?(9$Y|rZVh6yY za?HIjv1lPpZE8sPQA0wagy478ZRQo4t6FJm%G~vFOmOzahSc#duV>+Ze5EAF*9tm_ z^6M%8Z_O$H&oMy@=bcHZLr~a>jDZkF6$mXIl{s4#AZA=7-`@%#UjtF?n-oOFG zH_w?VfGnO2CJP5sju_1JvcH>D8aAcUa4MCC>%@{7&ucIRYaB~_XnYzVSI--fHBb6~Zufd0eqs3RF#LYi(>L_uWzG3FqQDU$Glo$k*7&uC>!zG}^ ze62JAY)h0L!Vn!L*7W}AMv0Sl9+a4N-cVvAoi~(VKkAg$3?;OZizxAWLy0^nfeJ^3 z$LWJcXqU-$F^8b)k;W%7hoI`A2l84h!I^_pJ>}7X(2XcRDE-cU*HHr-iR*$-pXItB zF`vHN!1na%8NxqbZ!se8>i;j|x{3!S(?iF zR&>|wN&fKH>LkB+*vW`G>0Ol`ehwT0*`%Y1JL}PM%dj9FVvSa}qoLK6C)Vn49!sku z2%^^Nj6qeorv^i>;cnrSH9h*((DbZBLDQo{$Z|IwG&3pjMO)9?VSx?n=y|Nu(es|Q zY6)nj5iA3Ts=vuEmvoxN#-;pO`%?uFeaAK%^wT6^cR)60D{r$+vrE|g#Mg0HJ+oX%6^k1M7t;|K-#e%$i<_& zH%eNfAo$K1$qtnrR60LW0OZ`lB;k?F+(aJ4_UNP8)oq1umSO87z?ULZLqKBNGyG{2 z6}rt1{H=7bY(v;LXITIuTUrGXwB@Ke+V`~Qs<1$9@#-gn39WuCt}xd^g}OHgmCi|_ zf)->@i7nDkkE>TdElgB#Oh9~s=}XpZPZppJ7dmfDFHN`kxVnoQ#9+=m$>MIswXoPN zl&%f@KGV!i;ae@}0{PkWnu*h4sQz(ldb{;N3>Cukf(rHYHWaha7j5Zc#qMUA`)zIB zone*%cTF3UnJs8z4e|`y7@C)jwI)w3pv>yVFrq5PQYl*9B0it^UnKh&U zG;b&v4e(*rN%{Pf_HKboOqQ&(TL4*-?@9g*+vQBz7z>foB8^xKdE&?IjoQ= zZR>%FjvSXJ(9C5)AYdu(D!Zh#K$aimN$_ayQs0?1JBp7xw;))jtIxf0mE_Q@;8ro11~km3k7Tk*g1Y_ zchNUs(M9pgn^I`yC>z0&2uKI|bR$>;#J&-X#v_Q61;o9>KkNq+J5cPbfikq@+>v@x z3^tDmSE~7{y{747w3cL#Xc)L-0}bo7s?bo#cc~$yuT>4Z91Wa9>cK`iBry{&7x&4l zo9iH>Wcb%J)~V~U$-Jf8#x-kDB%N>kf6r$jmMt8LYL0eF3m0xM$JC~3#oWjt0c)9m z-78@(>!-y_`jv4~O(z@;uH}{Zq-i|!Uvpjwoi_t{qCoaBUrUda|4n9?Uy+bP$q+|^ z*C0>jt%a))`-o%-GQ_;K7UJqAZ#xlLQLg@e_;09e$s2f4j2)Ae+mEJ56iGgf3A<2b zFN(#8Ow;d|Pk;ZQe!s$h4}UDRs&7d3@Wv2Rse8gv`FnHyM6uc-=UJkrJ-IPOZnJ{7 z*98?WtWWQ5JUMDl?ya9F<96(@vT%DI$KgLG$jV1a0^~}j0J1(=|A^{Ro&DlxVc)O#YYF#O^0&CfQ4b9+=ckD+jGOeEY*wD?+k z$K5Px8{2859v`hY)3g{xj$t1?FDDZKb`O*RR8m%;74@9!=_45{bt2X3VegmtxX%q2IE5J8&_XWt$eQW<=6{%HQucU1z zxjS7@Cbf0!BxF*HE-?J#SQKo%hXRYOI^J49mrDUYs<^ug%2$8710ih{0w7Am;YoMVF>gzzmGpN5Om5T5G z0(5dkC1#1mVN$Oa$YFny++nTIp6$fG-)kn8WI@^QXLcIsuExPGLl?T`Q;?7oE+P)M zq=Q?7IH#`*7MhN2fH+gIfKao+vVDE9SSzx>#qg{5q%2bV#izx*eS6+%Y{in6k}>*r zB*a$Uu$~*~L;*O4P6$x!3J}0$EF*+~%Cd$Y+7?c-u@#^hKuuqkWKj`3#c?UEUsQlQ{L&J@@z6Q&YmvX2W1Bw7{k z*;;NZpC!gs@MrBlE|Tq>on1(eNMgn2n)hSOqn@{T_9nLMclv|zYk-3;Rr)#Zr zy5h4=C&m6|-^X=NHa-oV9ayNci}@TPyT~h33pAt_ws{L%t%Y)n_IPcn0;eDNsI{eq z7|{lf0?>;mdED2snSCuJ43N?|-OAO`q7=aca1nlf?u5p1yG#T^(T+A+6-P9qD)L~; z_go)JmHbU%tvBgoy3Xbps|=B7$0hI==Eq<5ZfHu2*Locih;EpmdTs!)8IP8-$5I^& zv6MZU>V$`-?D5o&cR)z1yi9_ztT5qj{CF#Bb7UJ?F4GtHreae|A29G z=i2+VnGHC~L|GQ6G~#AmK2jSomDq@%W!r(i;v;U(i^hvxq-UnK{4y8zza}IPMNPL!c<$sq$n=r3Q-qpelNPdUrOV@GJQt4ln`ZU%`Q1n|+5E`KaET z#T{anrd0!nJA@r#1(bxo0tF;ZN}}gRNmO>HMnG$;$mrSz85yFAjGQTfpalr3a~{ea z8?OlLw2Hv=&1GRWJVD{{0LjNfKOW_(GfaGz$Yl4d;&s*+oy*dq!)+TRH24ZQMsHX1 zgf{gxaPagpV%(PU1UTl*u;M7b)tzNu?}j#wnTeOphqaAYcqSjry?TZ=y8?vv0cY{1 zv$LZ-cFO-p%xd{wixr^KSM1_ciWR7%M&yA z$4~}^NC{UsMsE#MC+DXzYuhSl%=;dgxo~->3`L!CyR4g?^4aXfl!eNq+y=#4TKuvo zSi~;bSkt*3(?}nuow;4*Fph~&gne`(uB|#{Xsq180o6_Vb`HtU0qesa81#$8?!Xo* zC7Xp9XI^d>*AffW*o5@mC9&ad^)%8@z8g{auoM9)+*6{&nwp(5-HLv$Bu`DXu7=XhbV7YeH(EH=s!JPF$bxQ2pvm53iDAM$@WB7AN#o$X!?>4>lPGIfZeY(;o-PvXPT!^}8Z)^h{ zdl$eYHngoxdPV%lhb$)Nx@?(iZ*sdy+dUO7dJ3D|Zq_$3mcVQ<nh^HkWxV-5Eu?=Pz{J53i=Q6pH(^dhTtu z{dcXMWOlkRy|g`StgV&JQ)`8;R2;w&_J(OA;4UTcAWbZKV)08?GdQ`F6sZ@sh=r;U z2XGa2z*t`KOM=5Bc0frTS{?XI027Bw&DvSF>p<~$5&~)~39OtfVZO%?Y_v_*jZTE> zBmnE;K6aR_WDmkkOHf2ZTT(RG`d=sNhiG6gapSsvu^Dx9g(-H>8mZ1P^(I} zO>yNy4;o#^5~l{Vg(uNqf)NfYu5aN%J;94w+-gM4?j7-py!d;OC|pkj**3`$K2Re% z`p7yRUj?jg>EKBXt<9%2DANcRZfDu!EW5oBbroD_brxKdba)0_`Wo_Vjfe&``l%^y z4EJkU;*=Cg>*YptG7qG;k^p|qqDFCYE>P=LROySTGkH<#OIkR2K0@8OzI4KDXKRvm z8;^WOnsSxdRI{Xdk8#Gu>c9Fm1Z+2ov^UJXKF>RZR@Qsh%Y&1kQ&(UUr*5E}8U{LJ z^oJlJoZ4S7Q^62;MJ3Z}vVZ5|T$*Aj&S^BmJz$Fz{TUTUv?LYj?ih10Jv$K*#K6*3D+wW9)AqUon^7j-i=wa|5oJyd3wtU4@vrE%mi!%{x6 zgFEaz4UAM8R2J*8I(5-_AOzCweq}17sio|~@d(7CH>2Wy^?wZ5Sb8yX+ObNB5-ffz z3~iuK$^+&%EeZHwBrrOeyU9+bSs*Y{cJ54*QQdBM5R#SyfO_DeDWpIMzG?x0Wa-EW zUyrlrLjyNEE6U5vfny--)dN>TAjK9@*y2Qf*Lv=qns%6;_6%S0>XEpx8Y3YW;VBDAHAAa?R-!Oe15-^x7@WTD!MZ7MFo;*ztUYKm> zmqbN)%`d|$&)iUg-Yg2`KDes73f%5peyF@(KBqifkSUG%GnfElJ#Ug?z*))o z3gw!Y3ch_QzmR*lfuU*`(8r{2Pw1{pf5qb(8cgOYSxV+yrM83-$o=X!#cu>$V!fm$ zljyhjb$u?~-rPA4r*;qky6zvF8ZT~Hro~^0q>)MV;16)x|Ct)8EO(LO$Q%G=XZT!m zQqw>507h$?v#o4EP6kUwZ&R&b!w`JmQ-$EMrwf8N8wB5&XI&&@SOSCRd6)!d>j30I zlEUhbI9Y(SYGcE61PIy&@?cNG+sl=MQIf&(+dvqS4qQOOIZDFHm$Gw=g`ES6{Dqpd zH6_Aj?{a!rhoNIMgL6J2Oc0_MpR_v_C>~e&;s4C6bhcr168f)$HO_$yy?tAJ=KG4j)_2nu zOARA5B><`FN7b@^c*Dj9Lm#G{MhYkrZ#NhLO5c})`(ZjzSGxY#D3mJm;(w@B;}Os) zv?KbLP!={_tm=z!JR*0V8j>!9TC2dp;xv#zv>_fgv2!cuR~)xd7&=MyI6Ck6;w zI5T?HQg*IP6`(f=&*dx~zQJAy(3{I{@y)apM#CEcBC2EY4d^rj z_Ww(E@?f%wI{5!3JI5BslJ5gdcwtFES?1Ckp)&$#n8g7D6pECxHVqDZYm!{IoL+Mg zMKX%x$=l>=pw&>61JUYGkLIq=#R%C051+(u1vs$cABmqt5q04z8F2J(Imq#F;) z?)TU>_<_2$rOdyF&pLDgLZ|I5y5a-1iQ;Yb#AgNAUl!{CdMQdUpHb^J+M6rq83$#0T8GdhKFcw3$8|Z^&I!!8~N$RRZ)tPX(_jB5z z>DQ(&=qc$oDRxka7Xhm@6~H45`ed4RBbS{jcizsPuX*A}JN^Ta;# z;+iA%NLe~oD=YUZsFRU6$VE|RS(?_OrB#&`|0obKOm&R~g+M+}U!kDxfrIFESPq*k+_liW3n?jfB3piI*jwV&gycPt()(uSC>zO zsYG1v3ad+FoVM%=(Fvw_Tk_fxM3P|D0&^FAIJ#zm$=lrmNjPJvwaj9jxvO4h+A0f< zYRkTvt1On6gW{jmKMHZKlUd9W>DFa+;EZv<8cRl&S5|R!JOQhM%Au~I@ddON>!fQ?R z;Swm!Aso9`uY_kVEdMw)t81-7O)=kzO?pLM%?Xv~jKJ-2zBrep8UKN`5?-u^ZgX>t zj5TIINya!f>b1)&uetijbvN96bmgjR(6BO;gz>yMmISWtDzuya za`G}noGEfCS_iQMGb=D%QK@#Bf1x7E$N%MyY6Nhx@;b&+JfRU~`xq%4kD`o#<%N?1I=R+I`1K(QX#LoGQ-QmXhj~0p~7j;M^NJ#_(!K|1-z%Fg-twICs-m z6V6>b#kmHb6VPEt(6=)>{T`fQyZ6hX)Tx13O#BTKm8ubbKYe`g1BQ<~o<4k>g-ec) z8<)YyT?SttW$+DO85w*WN%u73)Uid-7z2i@# z3|ReVDEn~(5AW|`NC1F4J=pyDTqVcW%~fQ=wJ%K=I?6&vD9+I)6cmt0>ziy{f|o9jQMSLLvlH5>x{4=SJn;M^`3?tzl9uqOwjBR2 zj7s%UmepIQRc)tw+#H0~s5D4)+Ord2;p+}nHT?IPJmvCF<8jd&#>~tmR#D~|4Cx+7 zbF23b&~KYy)bLfgS=RAeRgWD%ie?;Lk+dPhgso;e!#&hyE~x6x|A$i5QuXNPRr)Rg z!zR}qbi%G=cyXSxP>Fj>We^qwuawzEa91LHt-C)BL6depSB7>>^nWl zhyR7Y*krX14bBZVSGwlT4F;q0CUZE;)KMAZd_r`ouDo2U|31|TgVW0oqBC33P2h|{ z2}H436jJgnmz9hab&o&*cGG41phy{DWuC@x%SJe5Lf?mphjsc+mOv~q&v)-SC9{dW z#F(v>m{QszQL9cEXePKai&`TkDy^z6^mBG(6t-VISzXSXbQ#ZuC2K7$!j)H`Q~b0@ zO~l0KjL2-P3`|STO~dNG<`ix-Xfqn#voL-$#FY#`lVLyuNkEZm3oq`x8#}S!SIz&B zU9v;f%DXp8L~Oz(4Kc~r2F-hgK2m*{$kfWj`4%du=_`&1+Az5`Ct z7$CKkMe!3rOp_&SI;PN08EUl?nvH-mX@OM-Ci3GlOTw_>M;c&ewyKpkMlb_HK^qX> z_^a!IStD{Vx5`8?PXTqxuUEG_3yxlC1CH;DlUkvK@06eD$vm zEapjyRA%b&1DK;rHW;vO84FdJ!9=jf@wSiu-5%Lb^Tzg2yeIwTk6izjcRhOFPrUsj z3HFEO2j2GnFFx`4zy0&?IXvkfXc|Nt{lPA2otv+)mdb*kRIkZ2 z{QeN)=xM0K>LOC!S~v#5iZEz(WOT>!lj$|osqd(lajRYc0jCxK*jYsMGf>cvzT_N? z@ZfmCz&y0t{wJ(h6tV*4oJHh>nx-)KI$65NtupX3T-3L(C|V?s{bGu zIsGiEBVp)F!OKL4=m-D_ppB}R>L9;`a`cj&2l*sm=AxQi6m+f9lu9X_9UPAs8Oo}k zPb~m}StEypjdNi-&blfFGNKxr$AvI%#ZSNSH{ScPJB}@|y`~)DA<|4Y9-`j@1X*s? z&4aj=GNvAx{m5N#&EhV}a)G5>n?|M2pQ zE=Du*bADhw#rN^6XWNbX%cuP-6}naK;P>Lk#N_+EhH&|z>XBcSym(%YEAtn}Tikis z1tA5uLzXZUfFplLvegBhR{HK5Xhrj-}*DU|mTi^2AkG|tgzx+qoqO%tz_TRCq zsKVDD|2#>`^jni`xw!U-KAXD+Ao5K{3b%G?0PuTPzlY~(cz&E(q}Xlw88)tWgyYKk z$b_Qf+ujbj=2IS68c4df#X-WfV^M69LMO8B?;?MT4T}BVaddjXnMt-#|mz6i9+Y)D+8FoIw`l|mUwsQ1$`X( zD0}4yfCg~VKD@-n5SttYvY;W<67frE1P)^7DX`R)Ie&E})ru+?Bx<`YZKd1PR=O>1 z#e6yd+eBoqdd-|fx1$^3sa@r%^j0lhrH$Fuk?&?ZlVIMX1Jz@a7LYR#8JZwV1)!m-FMy0F?*9unm_ohO{qp?3K9xqX%Lgoqs=kU8yfVZD*S752(T1JBK4?{Ur;m ztSf1T8PQ~#WHqmGGWrws-~;U*Jh_P;*zm+5ghQ^TmPzEZ7> zs&kpCbFop3d{}%9<@I<}a_pT%|kRCj1(v48ja!3WP}~ae89cxi}2JrhNk-L3&U@O zM$o56727YRnG&13pu2oeayx6O(hfrx3NnG%4LmMtRCT^vK|_6{tu&~9t-4&S#|E7= zKtn%@qk^{WGFSl%1q)Z^g}p5F{J-8ctiQg9!0Syb>|eYSXo>?tgfdH_1KhEE*0p8# z$onTP4MNQx9Ua&$6)G7%k!2m$#=*S2LLRWp-`Kbl1~bVk=RCS6PZeAU_;!6x0F#_Z zI!`jmEh%t*KA4874Z~ngtPW}ym)`d-$nYX7TkmBYyv<6!kZrEmk5m36VbGFGlFW?z$w?(NfFP>u(;)Ybc~=#4>2Ya(fk zL%8gIrJ;Y}`q2MBP6hhuR|WLdi&KLB^F$@Y+aMSC|AL*x4O(ts)Z^E|GQ&_>B`*fQ zll=#Wf6?ovS9N3w=l`iQCZ7@uq<}FY-jG>BS*bpb?Cd`fYP-ylPPpJ9jtE5sxt&={csg=%no+oabTE0r#;NWO4 z$V)ElTqb56AmrLU)IVUaeY+T-C%GVup~{OD&88alW=rZ#Smhu?I_5#aaDS_ts08;h zla5ruR}hw0{Muc-#uuQ9SGIbK$xw{Do`@{&z|$Zte?O}WiG{wF{LF0M3JlqzH&{m3 zV%M!)32F5_AGCMRJD5yDg5o#`&Tjnj55h%XV-RSl(?Rr>SBN^bL0Do4jRl1Y`~&NY z&&wrE+k4Ypa8Zy~^KVaJaP` zG|B?j*|<>lv?&@B8t5X;x~@?P0oqO29?3=%dF}8Ve!C%!1V@6XTaunCN%8-3_cm~L zRn^`9dAYB1XC`;Z+vEju=8}XUB$GgZ5Cw7u1R`$%B1P0DlbM@jlF3Xm_f8-fU@VBS z(n_CV#g^8zj|#Q$6rb9bK7aiiDkxS|tk~KXTVJrnYAa8x{o7XM`To{E=iWOnK}gd7 z=l}ViUVH7e_g*2jPxDNQ5;bxno14n!_-fUmRC;HD=`tCc69I8# zYImxUHo`_Xw-tAkxdDR6Y-V2=;0rdW*X(vlx?d(qHJ{3zaz4=H1;k&KPH}7ne#`q zwIChQG6m;K5!t22>8U-j zD#4E3W_-$zXlWI|Ku*|556DY)caKD%TOJBB&>Tw304ZJt=7RE146PLOT7Ria6*+mi zxb(%)pOuQRruR1BgaR0N{FY@D5-1tP(Lherp!Lg?;NveV0mI2U6~;2yzHGHZJM%^b z_W#q^7depC5*)3ZVwp>OR9xvXKVW6>VmDT~=wy2)$?8p01Fkq?{({$SE7s8D1 zH1vwf4tul2J!S&xaNfP580>aYXZd~VrHopz(ugeF)Q58lVDSqX$!yUk?;2p0O^h3J zSlp~;4E5Ngfn?B4!6}UlV11ph8Qyow$GAgw{SJu?ga|oumq}$=ipSQ4C&v9` zp`MD&qKb>CbV@*{8Bzl>RKsdb61OQn>P+L{q7Z-Yz9$*ZBqpSdcnXLBJ*dZe0EWxp z-e=~WttW{SP^|Zta!GsHXpE%U_)<3=^g}K8kx`;l_LLY(WR<8Gw)CsM-#E4eT1oU_ zC2@1@XrWdXot*F~O*>nWNYJSbQUQ}Ca%fx7qWNX;=yW)AaIIw}%yBC8USJVMvoS6Z zM){!YSxhZa8^FW3Mv8`F?rpH07zlvT8X-%FUs7^%rXtep7*v#L~dE#H$vNXsM1 z%AfcJVvW5l_;Jxsj}(?kytVP@6Wm|}wf#Dq3sR8_EPgr$ro-?G~n*XrwAT%mvW(vaqBy^ZBUexJsv)rvsH+;o3a}htGbyH+X zsU88@0F@;fO_*Rg9I&P^$9z`4XSOW(7$0}Vzm;R=oFuYkO4!6<&m6rx{^Dj+T87e! zlt%Z%7QNF;I|0Q+6ccKaj$)8h7>K7A0|IRR9ZW2cMs$)AdDAN%FJ|xIF64SS_UpTP zeXlZ40)8G%M{OK3O~1iv+cO`uck%0%j4%qH5Bl5VS(q!iVL+cFVb~s*H7PzPn$K?1 z^RXZ!%?^|Mmo~k>>>?4E>u*{BFZTP zOqmTb`2pktN0ljkMrC&829I7eSAdq8R+y{q3oGfYeHHe%W*__bOm_MeoXoCwiOaRY zu3+mjwPgP8{riZ*C6d>ndOvp;^%Df@`yZ=9(sD%cF5fz+ep_M!+4Hkn?ujf(hPyj4{EkKaeL3)cJ*c84x@YfdU%sfWy!>^ z#}B{s#Du4o*G{2b4n-hzH#OUD(}VM4+pgb_=GWO^+^>UBsEG$H*qt92>(SgX+lw6b zrT-6Tr}-kZu4s-@SkoAzfpormiCs(hw$deH>xLUeo+69xo<&YqZ-Ctf*jQ4#qD9JY zkxjIT7>fHL!;*kY$j}5wG+!2Dp-6bzM|vE}tUeU;Nm+KKWa#~rx9z+Y+zVZ;qPZ$< z4$zR?sJh+of1$+vi6LNu|B!5weMkbI<`A^iLpgyNZZWNS@ zb#O_xM((CGtBgpqRmXd?41EdQbP zp@|A01;7?3gL??jlyy_VsI9ux7L8y?O+|-^fWddU$9Ht9)-cjDPsa*F5M%*BtId7r z)Mxx4#nZxc9^j(BOMnHKG)8hjZ02vDtF2xCa-M}q^~_}IW=}Qzza8VTA=$9DTXo@xa5eH zOJ%Y#K)Jo`3aUjOS8(_QUPD)O6b--p33jdUJY3_c2bBV`kg>F?r&UeXW2RnYbo)^MkSnUPE_aq^5SZ@1$fu_lqqRG!k;(X}1sP%f4%nC?ALtv99jo zk2w}dpvz}RLwQl62vpO08)-1*|1m>QiolNvO4{gogEnoWA6P7H^l<|wU233eJ>3dk z{5^&E{DF51==Af46;c!bpf%dCa#w5V2G4zyVpPNjTVsL^N+r(7Ls~hTF1GJJI0?T~ zFd*JXh)V!C#+50ku>r~@o>~!d(GD)$q#5YO{9#Ha3TGQ+WHK-rzS00v>PmNJn%&sy z#w;w1LSN9%?SAddBEkx0Smi^SGfDGRZSZlFl|Hepb4iDbHju=ehW6mr`~bKeMt?{ji@E>hb?dRDtXm;KQTkzNN!po{v*p2Q|znBl+*ua$Wcl#Iey~Ua(r@n zj^x*vc)+}}npWuRITU&C===Qp|KcBi`XAqY=#JYfY1Eg$`o3qs@yY-350^BeO3w#= z{th$(sK{C2;uo}Mb`q0CoP%BXm9CBsARFKN1#bksgxj$EK5Y2%JW*`vP^ZWG$Im{F z>h4G!PCv-*g7J4I$K7F8ulRW~aG&#Ge(Hy}lW6jXq51}VMdOzq{$}P1x&>{enUfMC zas#Kh`k*M9Mb%)YpMite4p1X3fta0KW^QtsR1;;nV!dK3#4dZcE|HGSs7>7?36a_L zn88tNrD62A4dua^mV`U;=DI+upqe_LpXh?#>GYZqeFULnt8)vVZ10L+)V!fr+UQ9x zB=cdkEj5Gl%3XJf68PCREwQgzC0Uzd>9+_ODo(Oi?kauBXKh&Kjy_MypHp6Kw%IAn zrq3~5O2;j?CQ5Y~D!B&Gnn6Hw#IUCrsKS?9jB-K-NEk{z%{sRjlB=Dh;G;9i>SswT zO9{XVCZz(cVSX?n=_1?q&|B#p%1Lvut-~n~EJH|)jGQYB8yuP|j4xLh%J55{%mhWI z)LW~gLaZP^zGz>C%Npp{KZsh-C_BPc?Sk5994>bp(d&HJR zw2Nn1XUAt)X%l2PXG-)HHBnT$J8C~DZ}+~oiAsCle%5$&N-lBmJszBX)VT>w0L-ta zMce>b1wSaT(t@cZUa~Alg(0X4Z9rx^Y?qBVDHs^qrxak2umaB&g5iA=C8dRk#56=p zax~zT<4s0Dp6C@`hLR?IqYh4dVNKg$%{x=5{!)Cwd2c1M7srHl4Gf}~R|doq2n9Bc=t>0ii? z%xx@^-DHmPXyu3cJRQrNfmP`Zxq+1e%b z7IrmYbQA+JJO%{F7HF943?3R)Fm7BFPuwb@Q?Ls0dKcTeiZG+1poHQPJL;7}7CC#c z66=)iq#9bVl+>yKdx>57Qlcml&Zra!Wud0qLk2KtWFfBTXfyt5YgIvI7XG!2$rmnT z>8!N|6&AMugE-hG`4f(Tn3-K*C@0ld>aJ!StT;(SqC{2tTB~4KP6hrcMwLm))lbM( zTnsBaaz(Uj@WglVL#-gbN-BQl#8L6(lg9d!xJLt8)80h)@x)We4p;#DsC#7Xq^(~0 zv>O-wYtYJxvfJmDTJI(ytF94pk}8NlvF&&#`$idTa9hmnu(D?q6~G*DjV$mv*bbWu z;)u;E0y|a6qEIEZJ9--B4toeA(qlP!3%5hkRyZ4p?cEuF4HEO729s;>QZ^>v1Skp> zEOa?fpH5Tp99irJo_@I{w%cOaM3s2$)V`Iw+yt|!P#{e81jMthLINT@IuI1w0>rB> zPD>vpNqv*9QWx$LwEQN#5-Wx^F{D3;++?l3BHcHV$K~fVtyOw5z-L8bEKzS-TtF=? zv&qeQUo2J?4|Qgu%odi23Z5eRckwI#kke8Y%AXTeRPd=}bEw_rf4!4Xk0$TpxBERF ztbz>Kg|Zff56alh+};?C`j?;*+tx-lepb$Oe^6ZCtQ&d|#(kMXNK{s~n=vc?2eq+G zdddfmdONjhmP1$ja^;+NVv3RCKGB%*_ey^$GzMsB@fQ?Ns1{N8QOSZvQn-6Hbq1)s~Km7>fGcHwE-Mp za&Q#d_zeig5Hx4iS;K>2U-3rCMPOC_;e+%i=N={-3dltZ+{=0C{IX62Er`?LTzD?&ZYDT?vw?3}3@7;z#74*Pc8 z4x)=`6j2g)Y8!BiC;kX3vb~Ce{g0S0f1Y*=j6Q>N;GG=H8(DOFA&+LY!W@JX8WWak zii)sBQO={gEY&HDS4fGJYn#(M=sD<@cwvArMbrXjFd589n!n!!i}Iz@CV)$=7vN|U zCjv(L;+bJID5ysK#Mi)0YP=wv#-!@wD@fy-OHCzzr`D>|Lay@k}A{JmqGI7kL=~_^d(K~m%m`ezf$CXGaIzS5Q(@xjEiB?a|avZ!-;3D$J38( z{fgn*(H#>2(swfA5?L{f74W(@HPUzj>n&|k;8&EgiZCybioprb+P=4q86`+Z<0w`( zXwbs+jm_Lk*_DA6wZ5~*kF-dG`R_-1X3?M+L3vjq8l_J{FkT@5+L;+`ht3a1ZQ{0B zx3h{^yw)WqcSe(SG-g+yIy~b)BDFS~%;6Wl*9>pjSdmqU zTE^P0RU9}OX^}7*D}XXC(~R6y@&>|$1yX{S#4y2O*b)+jmrgJRSNF8FV60?s3e>Yi ztqWA4Ey96E08s^&IjcgTQW(oyy($?HsTD}rdz%2(4NokrMQo>U)agsk zHlcws7Awd^c5+R8IDMW^wf&3bpi#S~@0b&2^sAfoNEcqYP?%Vh=bsh?{r>kS0jWT9-q_*T|#JfBXl{TIgQYjgyuFv zoVFd$YlL(J_xwiaMnc_<5J#BCxIZMNZzZ&_5gH`4s1e#pXmKMnOz7lBh@-0GQyQT< zp(TybErg;*=pdnUGU#zrV&Z>RKOCQ&q)X>bjQpW-Ey6-TF2Pc!9#;N^hkbP; z#w1b>q0`ea2O@x(MyEuC7W5v|=`cjwU(u%iWRZ1=NTvj~oe_^n5v`@;X$n-+Atw*x z3REK*=5m09%vNsVL6#Fs_k}GqaikF*)7BBYBf-7(F)<%gx?*B!R=}*Yp>3cop-UCq zCt+PFJ#9a)O4?SaNgv4Mu%Ad11|jbFAscow;rIK0s^K733O5}mV+G@(Z#gmZaLAmC zlPT$6fG9}&<#wB%z>i^*(c*1U&b`U?`FHb@zc)#V#i#rRY@96bL-63Z%-xa-GjNFJ}Ihp|~;+p59-s3VP6M{QCIK)Ao_af!_c>n}_1qU_^Wa%bOZ zD@<)4%7cw!)!B2y6;DP}KuB|uLz-tIQAX?ZfgIzH8-$4{R~hjGQ)4u{t``qadI2T8 z))%FsF=x2RD%i-pbXSPI!Yb?xg;-nS)tYL9&~SNi4dwnu;p2>_wB1L9F-e(een3N* zitY1+P42ZmMQG9a>*C3mQc^KDWzuOBr_nJ!>2!%uda%>QUn(H2rRzjb1k%?7B&|_ zzaSd(RfvpUHt)>qG>;Tgx@jw1TM^T~n!7XDm&TdI8`NPKkpYI|r=Fl5aZmi5qpi1F zCmerIp?FDr&!@Skv$3BIn%=4RPF~*X&wX9KopP3IM!jGgWNC?UBh~X3 zrJ-#j>N$&&aA-t5W>Ju95Km|z|)x zmHwx}Bi%6mVFDUy5_?DdTq6onuOKR?z(tF*`p|FJMi^oI6H;?Pnz$mv)D{~`N}e)t z^^zROa5deC$Y^5l31Nw$MGqWqYcRpWxRm`1Q{x!6UyizAHW;1GgVP^G!~m+UK6{eTe71 z?s?)~p1(Gn1SSalX59rI{VV(=Z%tnO^s-&???1WOgllNIzTvkB`NZ3t^Z1E$0D5g(FZXt*MgeG-%EYt#Wfi9GPn z0NfbDqaHRMXfw2D&lL3La)3oBS+~lrbygd?5Bie&ci*d>+`a3giK2h}le;I;DDjWf&&1uyB*|7Z&t$N{JhPYf z;LhpKYwp18Io~WV{*1bKTl($Ay*6bO@yGt;2>3`wB7c|h+vVX)8=}fU z&{Aa>Az{tnR&d+n(28c8<4#M^D42SfeX&O2&=`hIA~+F>HB=hUZi2Rq4t27qjWGhw zm61LGDYfB2iaRYP9v0ilXh+t{q%AU5kkKK_5;hNNs@P#a1WF9(DxHb*y8=C(tdWT) ze&Mb{)DFl@5dGqP4?W}6c1PY=63x(W>Ki&a&I-9DxoLY3+Z~E!e3F_hn`4Mz6WG%r z3Sg2Jh*TY0u8hGE`myRk>RQH!@r`4^c1K0F1XRroY3UqNn4u`OLsZ1dS7dt2xJzEc3|*NiV5S?+!3@XifZPn-Iha#Mt8Dy?O<~#4 zw$;r?!%2p?4qQnJ0%9v@LZXTGkR=n5q1mxq#JUTBWo!?XP?aegZ_C{Y`>eK($(OdG zYC6I~yAxrB%H8Jk5}@#^Pk=%rZv{$Zfr}DF_yFKWqfpUgKc>(<&?A*?o-c-df#GrvOw$0GnvdMz zNfYyPVjGF!iK%@kbMgJph>fvXv;ULEL^c{^%w?bGbB7;9ou;DkK(J|OYJwzH--1q{ z5oV~)*G7aIM=?5E%kAqDXy*p2@eN>>_jSZHri&{y39G2(L~C|V$tT)K3ioVTcr>b!2?@&0>C|;E$wFAKgp&5m$J$B;ggWjmJ$}Tna33Pui?>Lx z3*uZoLfngCfx>!0HiZDUrqyzQd|mq_1$;}NATZ*+s)W95XTgze|B$8%m5LW9F-14= zqPK61(%ps<)_!1oDc?^}J32X6B!^qRePY712{~RR8^wzrJyMR=nmhTl*_vC`Y7I%Bwk9nef3_udskQMYVo|`8ULi|QAnJIT zmM1;qc7b+8MC=F+pN6A>1~4t435Z5#>8Ol;=nMu(rSNs-d1~P=w zROs7-@|OjK=ramddL5Mz?bs%@B#!{C>RVsNqJ_!<~yQjJWn}l<7kp@Of zF5;^I=5+u^VF~R(uvlrXhNXA=CR|r~oX6!5w$js9?-Vce0S4X@2_7rJ(FXO(e?lO~ zf0sHlOhY8&JSg50{jDW}U1mrm!OU>LMvv%CPXtX&H;oE_k`V;JsP+Lqw}Y3}MXON~ zn3N96aSl;3(Z+w7z(-(8LxW(Do8+u-$k{&mb^`76PZ-l#387rA7{9wdLRK*5V&v3A z8`=|Vm%Dii<5_U2Eftr23_&b2PSDam63!4z{9FPifjN`u(JisUQ)|+9GK|qc>%A=K ze!H865Mw0#{|^G%4P^rvI%IrLJhu=W?n$gH zBy&`IC4;94qI#l`Fh|DiYG}M>>@D)IfRJgV2o*@H;@g)F?2C0$7-LohyIU<0dg=ya zkrt*d1~<$XM9>Yhz==x=8MkYelyq*-a_QVMwJ?wCAT%g_EHzpTi%U2;nNDlUCxO70 zIu}jm6eV+1S#ZI7n7A1K+e`E1O#i@3OLpz!e*cd!D;DDyKT5C`cd?2OEmI${v8;w| zD0l_tgXoumN?tkO2>&^}329bU!RY{Uxdci<8ag<^!pZw>2~ptr9&orb7SWl0%2zB^ zHAO*YCJstq$rY%iB%xR!a+ImW6YDgGUe1b(9P z)1751LHsxtR+yReLN)VYWb4Hr{T$pMAE6d4D{0cAIf^%kH^@)=h=&3QQ4yh);f-7* z!Ym8=x5+sK;3IIpPXa(4o;-D}^hrgjk2L#17PomcIvhaU6c7qW!N?AC18E#vI5K8F z$H*9qoyZtxP$Ofya7W0R0-u=B1Ust)les@ASt7Qui}ZXUDvR4jumH5Nj%4+i$}Cd6 z+3jIeiHn&57BDh^Lg?%PfIi1a%T^|`sZ9I>;j`hlgr!uA1~(J}ZsA&I@-D@88{r*;d!AI|;5m^q?(>Xj@dF!@85vu5FXfFrSkn zkWU)qfA9qY;OdSRX+YLQxZR*zwFeT1brUaT(Jk^ zg@aB3ZCGEH-vq3n@zoO@-2b~iJU2L^XJ*VkuKULso0r>}vI1FYG~c>5GD=l66Kj^k zhg)(;zl<^(>^-f_FseUgnJ3AU={zgYCGRZ7^%xTfhN?wT$1r{)M}FejOst&tgZS_r zvWOh06dq)=qTTT`-f;ZrgV|m27s`7oZSj{#s&$qbZ6Gk0Pb>SB#SmARPXsTmm4-QOH0nX%COxbVH9<=0~%yq(R%9 zKO$S;D6DnfG%M^9*(+plfIiGMF%~L`Vi6-T8I|0 zphU;(lG!fK&H-Qpgl+NLwVVXIGIGNKQy_xwzdf2O>9K%fG4$DT1-?0~xhYlQB;HU} zlFebit7im<1Tg%AFGj&VPD|YRNL181C4AcSFemvAiJWJvlzX0RLI0$(U=fuCWD}D= zHT%}#LPa>yoY_N#o&_>_eX!8eZQb-MmCi0*Y_5`QGkIa~usSSMxI_S!H$$aJ2-dU! zOi3G0LI`YlM}HXK?>>t^h6R^Xof9w1_<KNje;o(C)W$K4(=CTmO#J`Dal66a9$3z_&C<&Hr!iF9fq zL?_!!CMG>^7dQp0{Ir^BVdFcRmJK2HCDD8te=q?VUP`k5VB)4{{3Vy1&gdlD#s(}u z*NJ$N<0vA;c1vgVc)cgzhXZ72qDc-c;OcJk=Ddpz$;8Km^PfhRx$y0`(XBLm5=+SZ zn|Hw)TKuU%t6R2w;PE*@Cuo*#N-jr?NK>@F(m?_I)WSjB_J}Q`D>R|PYdeLi5S1n> z?ea1e(U*WmT6XFoX$8?~Is#F-PZui-p;vxv5z!T|kV&wMu9dBAq~jBlxMn4jC%nLk zL@m(}Q~9V%JJyj)9?h=;w{ydQPHp{t)LmXpozc9Fh=tCLW5)V8ZaU9UGHD!~W3oUc z$|P^~C4hgFXbYeuJ=kt16R~^IbDe_y#Wv&>JLg1Qz;11)gP_!Cs-GZ2Nl0q%GBg`K zQiBkbW+A)>X&5|MHEWB9lf6m)=678tCxQhkdI6pS@x6~p+@2fXuMQ^_!jv!-N$>t! zrCT(`t8IF^={|GQ(m;(1jT#mL6yoS<8*W((L<^~G1&sx4Sc{~@N=x8Ai{y(+iF-di zv`}vD{nC>lh&Jj?<}L313x)cCwknxV@j_L5p)kHs#mW|qYj4lIXu;!bl`#AA%Hj@5 z)Y-e$JrJ?mdWi1VvlpQ8o(6UR_5=C{$JkBoBQ8$7!h22@zgf!r@imy5k~r;JXXETs z8YySuXbA^6n(f4lRl?+>CDF-RI?)qFr`WwGT4MJK)j8F1Ko6)DHTBFls#z#05E^pL z|JkU?-q6jdBXh`SRh&9AN6JvVI%F~^lDi1at@6g_*VWSw90*qnBxIi_XVYTzq zvGFY26fG5n#D$(^WDUvmfChfk(J&)pPDTYSJ{1*yw6JHX#L+3#L{uVFcou1+SDj9g z)RVXWRrN&wPbwNMi!)&( zS5HM+J(oA@$=jBCE;LqvV*Q$rvVRy8u@mfVe^( z4t1DG$VSV17OIYu1+CgsFRY#moAo4zmU=Eck@aNvnMAbFo`^c?d1`c$hCT@s5p7DO zh&C0%PTim-k6-vs6TO{B6TQ)?z*jjn>b?vag9Mnz#%~?%j8lO({*n5mhMqsYM?tDj z?!u8tQ)7@O@geKe(r*4)^UGd1r3ah#Wju4byXbn0An&q$wRFr)mC-4zjh4=1=Y(=Zj|8YB3YdH{EwWGLIiT;?zjE zK&lAD1huGVV#w4K3NVS|{^`uZngx0i)Tb--hn*Ls2ik^+lUe36o%jP~no)v-t20^A zMt3yN#=53fF%Xd%H5nbP^pKMKIE#uAsD>y{wNehaN^zXhz%;B=)RZ*X26Q%!D}gSC zJ&T*#my}x#`()#_6uSh0^Ziah;zi!TvXLX3o{IgX6HfO5pC_TP#wo=aIe|wwfwP84 z*_;2j{b3PA>b{*SZqZ`$1Wsj54{ja>YP$~62}MfuaI+3H!4>L>>p+#jD%jD}Es@2} ztYrx=V&ktG`VwSW%nPkClWapQY}C1t4FgfWVIZ3IU}_y&@VExwf&l4KrcYmhHQ9RZ z@|UFlNJ-I=Zrln$0ZkmEg-9c}X-TlV@^@A^?7>|aA2XAUkC}$?vB2JWGCsDk*j*FcFG`$wedl-SWm_b(x7O2ZD{N-qAwn-%u0)#h;9OS-0Wmb;~a^{ zY+Jf$)`3dy?h2a~9IAAk7v8Hw|4Og*j}#bLagq@;lu3{elW*7-t?wd`cQ@O%<_{?= zzGD7@bB|;$WJU&CVPT4`u-$Bh*#-x&5cYc6U{9LFCq%H{@q-u*Lph1CqOgvGQp0G- zxhAZmjB*XLA<58p70XGk>0z2OH!7+tjl#HSj-#mv||4_C;Jp@(&how=m#ZX z1k!4KB;>4*?TdO&X|+BQmZQ2^A4z1^M@!SNK3a-`4c_9W^|4^qM@#9fk4ZAKK4R~@ zqH>CIW)zzm4WBX#VY&xhI<|eR)sz^2RcYdt@uAjOu+B*IIoA2a_hNKok{gK?#?KXZ zm7kOhp!@pSDMS!67%20?DwkkPovnumHF?gR%;wLF<{#|oVQ|nb zBW6z}V&mc4qLUA*IoMJ6PVDqdluYH6P&W0?dCb;mBBp%=i}C7Rm6PZmOvZE($w6vt zB35_=fm>4C|0F(f635b;p3$N5GXKtgU^5!1)y`_3Rx81JoHxELVWE8dCR+)1XqdrpH z#40RI6AlPil~=4NHb`@uv2^NN#2Y(NEt8p4EYGJ%@F=<MDGixk0ozntMcCpTfc2OM%!!MD+nd61&_9XXrtvdYP85`!^KA%ihz1E2>u5eb>wk`so(1kLH-o}+3Dcn7yl7D5q% z+gc5l7Tigz05@}T%8&*DXuasj3gEble-9aC>8k3s9#helMR_f(Yv;!@yMs{0GMy_- zw2;2+Hd>R$?+Z4ocY!<(9=w4$KjK)A44)f8&W+lX2lJzJVqJZ6ZdX|TDYP$j$wYHG zaSzgOQ^|$?QUQ6frTAM|&}INaPDJFuIij6P0>S3TXr9&5S%V zgDvvT47MnD>Mc*%i2N&HuodfJMMOBFlUL$OL-Pz7Sf?AmkCY z<2Dg_LRw;5MVRGeXiu<{)2+7BG0SQ}sIk?wto0SgVUY|S zG}w5}=m0}Yd^mI7caimD(DHLsJ% zneSz#cZgk6+j1Ba*RU-wr#vT-oSrf{C2sI6C?3 z>Z%u3*A~JhhnBj|(4!rDn=ME5Evn;)34KCO-9 z2&<(p*R@S%Vr2T{W>34eUg~YOKa=~EPfFLd^(Law`Ur+zyd%$45ISKc*@D`@s5Ma= zx~=0y*lM14wy6zvAPzCFKy8?TakLHpF{uqE@RMHMbuLbAFv&kVWvVt<=}i4n)YAPn zh-?}wr9^d{>O$0ulAu0Z+QQY+mrZL3+ZRiWYJr-ZxkQQ%=SKI(r7MtKOYdBA2c(%| zHLIaCm_C(ui9h!FWR-(J6tN1V2m(wxCqh41XgfkPI*AxB<%};ovu@M>U zV0x(7f;FqOuT}yZ?@HU1v`#+3ZXY;0)8_ByT?)ldQ&6230j#jGqq}{6Y36AR$b2f7 z%{+ZVwWo07%;~08_9*2x_y0P&X@7bejb^_t|FWY`E&*o>Y3gV;Ee2$zX;NGHWC4F! zM?qGpX&+9HiBMRlAZFTE)6+}`V1}Gi69OEx)D1?QN}}BteUy6H0w4*bf6F9>`v@mp zq|!={PS|EKmPRb?LmObBHySOs-4*mVE%k8Lrg(MKuDN-J_o3!7u7HhJ!pg~w$fuFx ztD}|XVztsvK`K$L)v}>f!~zxi7j?nPBA_dpG+9EpD}J%H-PC!>6H+fGTBj%|$_#gA zup;YZ0F_KPz#Q%315GysuKQ)JvsA25&Z^H9QLfr2S#Jx?366qIaL86?CUUdaQ9)LG zxeV}teO~s%!~^#0dZrVjpg=AQ!ZkyEOY-4QLxblAElEt+q&XxWl=eCWYe||LvN}?B zk{JwYo{-6#?PiYX9&t>r?n^kx)wbl3E}TqasQii2u4D?Lz4VnZqb;z5JF@Avge@zN z7z6dOj+yHDeIL2)dJq(_0Vs~kJviO>eJprm2ishjn>B`MU;Ch%E9{|n{pm#N3X8`$ zF1B|w(a^ABV}{n0p;@6TLElT^o&1=`0Ak!|xL*=_Ulp~a|P{(Y$Y%!4}7p=I4+Mzc>UrHg#V>Z zSonoo7&^xj1x|p%K=k8e8pXSll?yDpzdH)9U8Ki8AocCB4U3kd*qZ8K-{Q?|g3dfj zgjLp0=U|rHz^s^7eXx3i)4u#ZkHr3=^!5LP6S-VeF}S%c=0P z4^7Y5w$)WuI=U9SpH|ASfJUdRhfEo`hAlngHs*&~H93{|NwLpu@=5jNH z1QEAx-f8K-mKq1FUf@snuK3S@Iyn`Eh{cm4l4DY+XQNeES+=DKAI;(?@M*yjRaVu2 zj!KvRw@?*q5Wt0s=g=ehVvx7T_ul&?1%Fa?#vao=;;iI$mIi-(Hw`-<^PBrWQF;q@ zGCiSv0+NK%_}FRb@m?*K(gF|GZffYb;Z29Xm!FC-8jaaD~ z1-3>LFdp?CZ$Lv@{N(rgkMT}z^kNVnK_o4T3+}91+R|toD!1JxbZ!Bxup0^qHW)1c z+B<;Y78*X-pnv2-c;c2?Ea+I*(3uTGKMbG&uYA9Nk9>WZDZgI8U%GoT1BZ@5^q~=^=7&SEB?CWoqw39`6$G}@)a?_f zu0MXx!1|V|336x$AfYzjRU@k7&-D0|%mZI~Jdu}-@D2G19F3C#n}P&5d@Y+#zTg&% zmLt~o?WpMF8Hr+c0wJd*wK~E=%#!8_?gIyU;6M-JkGb9BwA7Kca1uO#{&7X7dk73^ zc`{_hmasBTA{-?5v|=zcWWqz%!d8_oQ$s}|AXOYDyLf}c#4u?F<-c*n60>k8IXStK z(A_FeEaA>yF8_z{1>o9dY+va@sGVK|%G0DH0C;DTI8cAHZ=aB+CCT-n0d@Pq5)z z7-bWI(D#ymXJ6s&yZVB==uU0tF!R(va)^(tR;J)RlBDd*vN3_@7UH0ik+`odRlI;x z%Zudc0hUn081CicO}p${EdEhJDwJ&&BzF=xQ%IYfea^Di^qqPv>OlHO-}@x=`V&>b zX$0BG9uNba3=?BtGlU6|THz-2|$E(Ue7AXW9 zR4+)op^`Y(rdWiEY9Jy2={5sm=x5Na96-NWXss?u6G*F3ibkdRMK z(wofEuB&uk#xJCPKTYsOL;~6vNTg!RfHjfN8Wl%TQIW9j-qNiSy2O1nM*XgeQPIoCF#`URxlfh z*!&hYA-)XQeWM5r%p>s^y^+5_6%`@zc$F;hk@1t-T4;_uZKPy0*g$_1XLvs!dqH-oL}3z-PC;(RO{#cDFiA6S^l$-U1hjg6cZgHWCQNsrz9wWd#t`Cgou`d# z0VUm*8#kRo`h{u>kjtZB2a~v9H$S4y4}ZddhEqD>%JRn~_r)qu9@G4&CR98AiE0#| z8Xq>Co8Jsu%^HPjpJ5Qhs$kz%`WikI&U1j51bYe>RMP-TY$r4zO!hmCMSBIqJ54kc zsj6{j>HRHTZ7Z9^s4Xvi2*41GI%mY*#4s_ITxv3FFL zh}JDs*?MQZ64s({H?7p%O%+VsQHz(blK}K3Rm)VXmOWLI!s551YKCfIWIGN9|Nq=t zbu~z}3a|8NSUUH+o_?Gr%)0&DOc{BX#iYmJT-MYdP}RIdf1pvNCPEI@55@PqA3|{Q z$#gA^=HmMlnd(}<76kI&OgbF0@?BQORkryTnaLx7W>VIu9m<*wINz6K-Lg}4{DLA~ z)^Ia4M&cJlpN?`*4__2zRsviu0rm9oM+&;F?)&c0`eZRa76l%XmlbVF99~=T7MXGn@=INXk9nq+G;wL`K`KKm$(80Y<5_Rw295%8d`A3^Le0b(_os zkcK}MI@L^k3o5$?b(s`DUc5j+?U$fha_OSF5vE2+>`;+VlAx%2CXTTD4muJO&I)C{FJzpaRjKg%JZ%? z;&bkg*hU7tS$9?#B&Ux#`6GrnvQ+5q<$9=hgo+$pl4X%8MnyVNK4y*m?YzuwHvNxR zhwkE(lPJIW5zZlqI4h8<-hS{P9dhXnZa74%Shr!_rA9RHsjnMj0;tOki!?;(fqZ0c z7usw{#NHmewWkYPu^jqP7IvDLIO3H)_B+|Q@8U~df9YkHZ+ycQS6+4XHJh%z?)uGd zyy53=eA7+++Xe=!+js07+O>Ol&&cTBePgxy`2L%3IdJRhHD{f@_MCIiTX+8TgS%^k zyRWPE4_;TTjStty?C%ZzV?+HT^-KEe{o^A;^*#OdDiOm&xAxbEMn^V|3=EIgc+mTy z+Lj@O2dm0qba;RD(&7Fcwd<<;#;djZ!0=FYq`swA9T=RU!fk}q8Okt;tU$7uWL_{d=I*f_BDu32-=`PK8*4XizH?Rnc)pHhrgqf8OeI)~)Lu9@;k6KX#zEe|UIwptm+Q&^s_XJX{?RHfp`wz*@C; zPygOt>N<8{)mY8Dxxcn&<(}FOOS`AKXVgU>Yq z2P}V=^MF3Atr{9#x#s+}=MQXOH*oH@bGDsx&UxE=w;kZk$~CLjtX{RozOaZbd&cW0 zsDS>lft9tJ`}b1Dc}Wq2)opc}pk_r(tFl8$ZFH>OTie+`RvqlE?V+6%VIUo2Q#VlB znt#22+i^)KtVXICNmk_TkZfSNieOi`a3`_!$4sUb%YJS*unX29lVu+6f|> zft_QcBcl|scGX%(mLvT``^6{-u95!X6IIU4)PFo=meNG+K&`iSU}T^dPO9G0J2JY( zQHg{67-@J~=3_S_zjjZ_Jtfa3@gWYTH_XkWW4mj6`w>5o_V%H{>c~KKWg?NsRtmzI zURZNi$7aayn8Z)5I&^E5c0MN|q_O^+Q&BwX$~Z<;HJD%sis?mi4%KVDdhV|$86Q6{ z(3#ch0Kev}Rp+L7IdPfy@9jU(Z#;IkWiHf=*N28{AZT2Ca6Dw%qzxzCgqi6ia*R#7 z2SJIrM?5&Rh_>oO+lO2cGf3mw~XEb1YmOAs`IU+ zp^@#wbcns9d+CCQ=!npL_6bu*tHzF7hxVSB zmYGo{Ii9Q=)j}Th?(MJB<|8L0ztO$5-tFTframdd8SywxjTs#4-!bX{IDTH7-eYVZ z9Ydk)tqt^B88gDP4T>AzV>mc|5T0%$$)QMV)JY|IFp;T>W(cId17c_ zx_ley-#@ei$*lZm{$>P2JW3>}j;c2>NA~{ZEA+_q%PVq$M{_@?N#Qq5*j)Ak! z(hz{|JyqGZ_1CJyLn9}wh@&+JB4c}fXwS^VfmH)$toH6jtk|G#dWBzszB^@?n4w{_ekLX&jP+);A6t3W*@Ne9J9q8C;OaH! zt?OSqsPS}*$!HaW>>agLrqHh1zvlm|@bwQOOB49K9rMfHHJ;~P6MD>6d7i@$I{ud3 z)dPgn_aEXtzH*K5zYxBtCES(qypvnP=Mp}Jux2!>gHe4nO0XCilH6=aVemfjg5{`cya~l1NX@D zUd(#lL`zhoPXFwC-UePYvWmcg;Hr;~M#IW~>`cnOC0et3bu@sbzJCCNLTy!wqZ|dD z%J*sb0>aB$!tI1PO3}M`bbNTwDw`-t(Vm$rR`a~4NUtIt+9HXNo5APMNYoz*7sJ&C zVv{+gGo$)WU`|5O%~lw5SL0(NNiJ%~Xi?dtiM*xUFXg_Ad!GBHjrPJiI(A@hy^(!= zba!>6QMcJ3QSCiLD-8^d4UB^>=B#$8X(M#0D)T)f0|(sP)hn+*>Bp?*(Tl*hmhw{E z7LxMQ@WLtdc{<_OkbZJoV#XaEy9S*R1lH>PdO*jE_KsEe4~>r3h7UyBs@0LmDVNnj zz8_s&wtTK_FMHP#{u000{CvXVn_k*mvg0~8IVh^Wu3Fp61YOm;Cn$T{_-+F|yGYA;tQrDF+zQQf8FAc|%v(yPP~# zw{CvvhY9)4@L}?XwCT4e@w{Tvw+?ADew^Sl{kFn)c)_)ln|?c#-~_p4O%O@0dv~2u z_D&;x`n}q|lOOi>ks3zTnQ9E>x74`#?OwiXYyEbHN+;hoDxH=qUYPQ23V0LMjgQ@{ z^xMn$RydR2z_+qx@2xyv$gjxnrktbe-{5&6zbokAk5zO|(6^|X|Cgy#({;m3Ktir>%iyOH0U z_}#?s&HScbXGYL1zjE>-$6dTPi=S{NzEt?~*OtAj`Q5~Ch+mE0+xWdDc`x1-ZhoHM z7JdVa)dq&@2cl6Bz!c-n#63hgUECj=TlOOEs{+r$UxpVK5q<%lna}->u#LR(|~wb@Xd>^=~$L6Um5Ll>jwggC?K#k2r>oZr+Kks_(4A(1X>n;RE^-`4^t3OY}hY6qE626n~1$QcU#(5UK zTq&FD)e<7>Z^r!r8B-glZKI*OI?h@Z5E|diC(l;dwJHgd#4dtfKfjsU_;l!0?Q%JC z|19ol_y)oo32);!z;BSBWZ-swX)~RH?A%KDBh*21F%5g6XS|$-pXL2J-uG`q0pb3~ z(7tGFgrDl#Ke*qz479#zbbGW-ei*g&)&j${1oe%!@yaDrV{AJRJydb+{%739b7`Ir z^9<;d`h^{Yck&zJ*LuOGceI9uCtae?)+>z{ZO?R_GbJ)jf3*0KG(qxR8k8KE>EdYc z8L!vP7>oS*?5|JyWaX@~wcjq%i+9uTXLx>1OZd-uUf&X42Vml{H2uX>!ga!`WBPs{ z;R{;AzfD+mO5cB&u==Eo$3t>K8Z9Ao+kvu$Gx=ALrqE22ma(1;RVURWGpqT2YB@h% znv~P^3eud^Op}$MORm;D-?;hWMm_s@AzDET$Ik;tprn^xk3KpQz3u|(y8qf%Hog+= zieA$&9$#LmC44H+()ZK%YFNp?N!K@is4TpRU%TI;H}pv&d*0!@9o%JgtXwa^m%7Bu zy3(qowM)wokGI00@k(p>5MjZcrvF8rRpK~~P-FTtu#>^b0qg?x>Yi=Yu~B~ZK$^YF zoqk;BE}#!^7arf1+*4R?Bdq7!llvXq1^zE^SHE+JyZH1T?#d^k<(KEKd`jGvZx?svm%g7jC7kB}A<``4mxlj>=Y=idZ}2R+p1%Kw zDd88VgnvS~701#wTig9q!pd*Qp89zG*6JX>R`r3w`T#yOgY`YN`rg6%-WtL+E$?d5 ziz!+)9N*BTbjq~VZ85!s~SZo-RO!VzKdvaa9QknmCqKTe7dD6Hk0 ziO2Q25kDoLnRr#{e`b6O->RR{m6nm6BIMKfR@tyVLMJQ^N0> z5`Hh?N=y1*BfPvN{99APAD$BaL&BAo^q-g#eliI!n=WWZ@Sd#gtqw#NMyng}{($t- zsZ#iVNLXXmH2iObTig6QQ_}w<;WJy(e>X|*`CI{4$d%#Ba^<-4Tm`NoR~uJ}tDUQZ ztCMRMSDCAe>m;t(TywbQa?RtK&(+PfAh{NDE#g|tbu!l}TuZnjt{$!m*Qs1fxt4J) z=Q@pR1=s0Zui-j_>rAedT&uWxxmI(nNv^ZF&gNRnbq?3LT<3AE<2s*fJ=bfwUdMF- z*M(dcac$s=x%#*+=DLLI^<0;7UB-1e*T&>}1J@N?S8`p&bv4&DT${MA<+_gRdalh} zZ{)gx>*u&`zlk<-GEang?3c*- zn65WmemwuKe8aK-+jRBu{JU<`vH!ba^YQ$<@rL91chil>{_p4CayaSzx6@BS>$HXoAC;|1j_~s2r_G9OqD+R)FaSvkWIQZu}m4943 zbc5#|7Z=@#73|pg=qAB`T)c!y>^Qh-tI9tvjv8zocPqD7quWQM&uS2rT&RcM-nDLQ z(5i2@#%Br&xoc8<%h+9SUyZImDW4YWLaROC)#jPefUM;@R(P_mQ?{JmOkZ8)Ddsna z-z%M0NWUB48`&OT>38dyopJLKSFoBVs`u|$AH8r+*^?dV4?7$zHx@iEBK*2er|0FU z_QiyC$*!$y)48%DHLvS=l)XbkDvdRz9}e+LFJW#szy9cwq1xWze&+sumI-#80Svf)n?-hJMivhCgJB~4N4Eu?69F3bbUat_ohWob(2$!wlo|f=m@m}FQ7;-O+ zFfa1F_`I^|N{g{I%NG8x7#hw{d=h?Bn;VCH4V&+qiGPXT0)A=uUwD>1Bn^Lo=cO&- zFHH$=AY9?Mg#5N}M`)X!EDc{mSavroz?$RT%G?0!@7Av$AGvv~fA5OZ(a4#m9Er9v z&9&9dDUsS;!gYW0OjBB$40E>`vFYPP*;h40^nR83BGs)*1|re5zO(JhhPR6F^iPKd zZ;4(PT^w(UFW$KMP3iX<^XmKQ>8pdd!0oIK?bun5F1P>__VD&JxH4Lke*Z}FeH#Ag zQS)QH)GEMVak_FFWC@rB((m5D_(XJ(hCc&wp4t-r+>~$|iPdJW4DJyO^DV%)-SoIYHK5G~oLY<#&+J#mpm!9=0U;s6BP1w)FS%EIZ5|exlt` zetY@tud;5QEWaXJi>MmBRFA6;Ea-W%jdHl;s*p=w{|(ZLV*9 z6s<_PJGwi8<^9}YZ|^OM{r8Q?Bk{)d;6d$}=HlGF)2e7HH+67$J2@ZPc z_czhb;?0lb5_>E3q`B5Ke1vD&*{)`ibyI!J*kpgnrF|;^DXrGLj?}Lox}|>L($Vp; zzM=Y+hA)Q44u8zejnyv`R$f0cX!p%KhsNp$Hs3r-VwMePd1*S!u@|S;Xlr9qdh7b= zoy?8(P|g`Uoh>q*gZWHC`&afKU~0rb^ZxfdH+S*}q?c{|VwXADF)I^oy?S({x>Xa> ztSa@MCtvBre*#URSa`GHiE~@R7tvRXx6}7)k}-W6K85$PSEb>zkb`Hngx^iW2)F6` zw-a7Nc3JPvxfJzvfx|_e8#I zb4J1s)rJOejNeVSd3}9se4x$}q~XEZm8?9q7sgpNnM|0z-LscP#iLANjr5O<)*yiE z_x6ui>%H#6_3O#&9T$|1*Z&)L$lGWS7aTkVb}-++d35y3(UBe8E=N|T@vimVTuI6c z4US6LfxNy-p6Z*@{%;8Smt0Hb%7X|Lqnkv|r1O=ZDLT6P57J)5FAXnAYUZL8lKsNO{Vqlb`UPhDC3hQ%b|i zo9?L*Ucs|yFn#|To;A0ahGp{>ji+IKE1c6HQ{B$iuh$Bzfw2L(2{A=Hu=A*iQjD9t zal-PbX_cY){pB34lA=fVm>rm63jIbFVBp2H>%N^Wb`gAEb3OKeD6NE=@Hs zx(Dwb+{O_8d#d%Fqci!HxoW1L?CIYz1Zu`LCDfbtYw7qR4X5{mZ|E}3Jq@RK>ovy? zEPK<+WqVdG8}$AfSuL9RI=@r-r7t!wyL|n!tJW{OetN{J?eeH0?ILvCh`VG!S4&v* zU*Wg;x;MU_TSM=4+V4v*DQ>1K%}+eEmS0+QG!>R+#H#J2OTQ~oN%2q`?j|f=x}W)S z#c$y!d305ENB{Nx!*$X6S7WEU9^^U2(f5ecbK)jalbrPWT)H%`_Yfs{S(WD&x(J>_ z-cr)24^GpJBU2;7GZDkK@qyjd`l?%*-DBCKo@L1l2J)-AYkm z!wK@n{Y*q0CZxzDTnqLvT zRq;|erP_Ks={f9)Q5zc{MpKtx7T!wrehCY=%S7#sio7a`OI&o=gJi#r>y)*_X#=0~BxMb$Hk)8w&WPO32I>CjL?l(VX!>W0L#M@E1x{PG?6{s} zH;?)$Ce9gWORE)@hoeA9FJ0XNEXhd7KRkr{sfDgtE zm<5X?CWoA6ZEDR#%}D&8nt|jy+{JcOYYEnxA0h4tw1bZ3Up-pfcMc`q z>6ezx&n?Btt;h}O9C&l7f7qP1`+&#{(nI-Ax{ixo46BXOhq%sqPP~1pjaT??xfo;q zR}ZRK#{v2*a7xy{HfcMxquTEE`;F0xN@$~iQDZfI-5A$t7hd;|-?`JXve@?KF zGS2rYL*57eDM84VmxQxErOpZF<$O|77-{qDS$c|pf#2?*<%d4`h1t;0DE4Galk>B| zoM3Tqe&BLG*}vS+hrWN4ALL8LZGKQJ`hI?6un0(e|GZ8r6J$&N(xSgTL&b4_SP*2w za;AgF91#Az&)_aRIXH!X8v;LHU~jpQk5*>S_T!-9?`MnPqMr-@CI~3s&nwrUkZTM4 z)BM7cHJQ~uiTS^x*v^K(BnsD&0D!{vg&^3FyDs!Q{Je?|gR?H`@GDjPFv3#Up?{pw zSqR#M=v z;f^p23g9Z^F9@>!`+|ihb@?@rzyv=$RE^#{Uo} zw=ee>f(tLWCUa?NP3CR>xwB3O%WdHrpitgz{#oJDtY5g0lik)9+cJT_B?JiCA@G0G z4-0c?)zDv-53`rDYpbr#;Ee?#bH2YB8sUTBpEEiBE>b*S?ow;;rmB0!&w52a@I*-d zy_A{pqqY^f0IDkHf^Zd0?x{Ndb@PI}kbY~9tZ8%Kzg&d?c_~;6u2r7bm7B%kCZU&o zZN8ZCw0oX^Q5O_KJ^4bVWN>$$?}f#3kUu4JU+A5eIji7z`t!2>EXwJ!GP2nVd;G(l zo_|5c%l?$|_Y^Rc`KN51c#-YIa|?Sx3UJT&m-wH8SDx{g=7UqRTm9DvWpF3Z3bHx= z_5QmHANH3QPxqFK+=4SG{)Qmum9l39;f5m{ydW1W=T{)Lt|h@asu`* zbDO+;d)A{4-lnZfGfV3wC{}1q?w+_nByx7g*>9cKHqYC@zc(Ut$j0i{PHuTWrd8p3 zf3eU2OgNMV3;YG(WAd-ywfS>$IW(9{R3@cjP|bS{Idyqywd7MC6worKI{Abw=tiu8 z!t5F0Ym2Yx_M=t_C5a-sfj<1US0tmTo@7+mhKzbG-?sDRikajTr~>sSw1#?QK!83F@CNVdxza858(R{fiqirng1HyoT2OXQ2(r;^9Pw z8>y2YlO#u+ompz+{R>(o1~_vvBhxA|Xa;MCPUObLCJluA3>axj4=@d_s~P;@yR`T; z8B%DKAV2imGRGxBLga_vlpjcr%(e7*)LjxI8|Dgymy;QT{^7hyjEqT)yeDY^&$tV^ zl3F+QN~7q^b-BM&6CFML%F?HW%#hE^yqY2PF+`;W$j+;4(A^sT(y9M+>Q9~eSEv5g zUgCf4{aNC7|9ok))%u0~munu+D}U(O7j!>7cD4NPybZUO|Me#B6QBOz?Y|UyZ^ITU zTlWq8WbaJFhj^CHZyJ7-=cpz88J?H5gnz*E+?KEmRo(o!3VtChi)pFPGWOZA?~Jv*?Toiw^|rlt{_357eCKm_ z2KTJE=bC$NzV8F~J$2u|Jg_+H{$ zYEM|m=Ui3%Oc7lSxlklCSFL;|>t*Soi@72R4U7=YHYj?0j?^h8l`Z7xQnO?bgkCZ0 z>Rr^~ETD{J*-XCRP>`Ww2&+Oq^z!+zm|zq*3ZX|Io6FMPC?^gv07EB;N>m7PMG%2P zU`SG>^YndmWEoK6P`K`ovS zXrUbF2Yck5kX<%c$e@ytFU#2q9-T5+#DO^>#z6f9^~iu5&8YyqeEgy1?mH3Q)|bs5Py++LYNW_qF|^2)-Aw5c?2ii zkq{431oS|rP?HffQVIc~5o1_H%D^s;xeGky(C0*0M&5Z5B8m($rzi;&<`R}~%J*Ri zLi4q$yx&Jy@*nmqc9dt&UwCrQ($mgh$DDPtnxxl@7rCdU8{GX(?!MtY?tbV~?!N0s zcAvOwRqSy+bJBp_@A}L4*}eMt&+8s-IUMwHr{_9P_usj1;U4ZAzWu=J-{F4mDNlU- zFSxH={=?nv7kd+5KkvY&;)}h*Uwy~Vt=)gI_te!p_B{T37kd}{%GT#^{Kmx_?)k%8 zPX7J5mu&j}H~-@Kg;!ki-@bBH?u#!@TtdDlFUq5_DTJ?`f&B%)_1Glrq6zS^k44Q+|k>=|L{BB9Pe5A&+wJMWY;GL|7qRGKfUk4 zzy86$U-X^unr_d(@V%>w@Bh2-C+p_U>V3X`>i2&5#V;40%RKSdkAD8+3+_1k!{PEJ z-?=*A+k>m_fAc%P{C|&q;IF>=&fd8veR|X1?%q4{hA+U+O3vhRN`e{}n&A6a_t z+>>wma^LGZH@`G+r1pP^yYG0a|Nnpd_TEGp87a!%E0IvNlhUwBQIQc^W@KcAPzu>A zO7s_lC@IK?eYCI(o z=gu)${$K0S{$7_P29^ke!KUU`R-#st4wxa?N|-WtvcF)Bsq%lW*Adqv{IBbEMNPQ< zwcGVwy`8oC4C;U1kA0|x{`L7xsPW`O@`}HKj}H5^C`_zhsd{g2y6N0avLsHdXYW?seckGpz_TPz ztVgrAamCQdPb(~KC)OcP6t*qcC_g#0q)n{VH0s(vbkzIWE$t)LrLW&|i)atj-ds9L ztfLz?m$f{!yOXeVnppd$SX>uApWRowWJRoh^_nsooymICxpbabZ~0d9{?6R+kLe|6 zVqNUHl9`_K>>1tiHDVp4f9*V_sfGXMWnW@#ApJt#Y^A(GV>y^u8*V8L559RI`s8vX zv97qW)j`sTs1dC6Zm1doLCpi$XFY0&U^i5`6aQ=_PE+sH>?rMvC>Sesno=_y<|B(BDvB* ztlvgBor@I~&eU7!C)P}jBtGAt`7c?nj1cRu+^i8+G^6D=Rwjsb^Q}Ynw9Vzgu`9F0 zTHJcWun||$o2M&_#G3Fk$8*~KmK8kES5JhO=wDU{U5td2MRx61w;Xa}Cm{)mFKy(U zn~H|lMq=j`3CYuS)D2f#?mky0J{mAwaC-b|i|mb11LEo^tTpx=%Su*m@4P^q1;+e> zP#o75k%JF!5oaIp8h_zshfI>?WD-Uk2&eR%`e0@2_jwg0`-pX)bryFB%`2aGBu9yL z9N#6aKYYe7zY|w#qeJ;KPpbI2{Sl0$R>bqiZ#!^XO_BDBkP>FK(ZAw49T)rPLXsA# zGqJvDcfH`M8}0Ygq=Z>w5)#+-1MZ0U$}Rq}jy!^uw4(j&XIr zILQi#bxY;gQG=@fA{nxBVm-W5=sVYs#asKxUJ~mRx6^O$wFoqyA!{bq9}EPq{y51Q z<3-j%tW~A?@Af}S7`{i=PpqS#emrgPrYF6SY=l^E*G^0Bxpr@%jckHgvuG!$?b~;` z>?_$UvF5(7SvVb%6+}h8NUX(ukEgOs^S24$g&yGpTV=w>Mw40YsgW}f>v)Fyi3|5w zJ{yvA{5^kG&7wy@G~0okmssCyO}k>USKBj)T$or(3}lmQjtEwzV6>WWeW5GbCQ|op zLY|WoW*$gLb`}TUkLQx>=q2AytTT8%7DV)Ic<_^4n^^a_eki?I75$Z&g76H9UT0Dk z;@`0Ac%Sh9+*+N1_X*FJ|8LLZ_?PbQvp4>)6O|FzzFk0322bDrT>kOu<=3u{4!1_= zdswSAQPcjbCj2!2_uCUBY$J-l5X4zZ!k>tLuA?849dA06AFD~J4bSw~hhSusLy#K%7N8H6z(#H5ZX(1vl^3UfJ=EVq~FD)q{ zcEJ1qHgJ~yKfj%XuMW*NXe~vbCR*3<{@U$b!TllpP1`Cd`JY2&{5_lo@%0AI!d@O{ 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 literal 0 HcmV?d00001 diff --git a/openfeature-provider/android/demo-app/build.gradle.kts b/openfeature-provider/android/demo-app/build.gradle.kts new file mode 100644 index 00000000..47796b78 --- /dev/null +++ b/openfeature-provider/android/demo-app/build.gradle.kts @@ -0,0 +1,86 @@ +import java.util.Properties + +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) +} + +// Load local.properties for secrets +val localProperties = Properties().apply { + val localPropertiesFile = rootProject.file("local.properties") + if (localPropertiesFile.exists()) { + load(localPropertiesFile.inputStream()) + } +} + +android { + namespace = "com.spotify.confidence.demo" + compileSdk = 34 + + defaultConfig { + applicationId = "com.spotify.confidence.demo" + minSdk = 26 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + + // Read client secret from local.properties + buildConfigField( + "String", + "CONFIDENCE_CLIENT_SECRET", + "\"${localProperties.getProperty("confidence.clientSecret", "")}\"" + ) + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" + } + + buildFeatures { + viewBinding = true + buildConfig = true + } + + packaging { + resources { + // Exclude proto files that conflict with protobuf-javalite + excludes += "google/protobuf/*.proto" + excludes += "google/api/*.proto" + excludes += "google/type/*.proto" + } + } +} + +dependencies { + implementation(project(":confidence-provider")) + + // OpenFeature SDK for Android + implementation(libs.openfeature.android) + + // Android core + implementation("androidx.core:core-ktx:1.12.0") + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.11.0") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") + + // Coroutines + implementation(libs.bundles.coroutines) +} diff --git a/openfeature-provider/android/demo-app/src/main/AndroidManifest.xml b/openfeature-provider/android/demo-app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..f471c7ac --- /dev/null +++ b/openfeature-provider/android/demo-app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + diff --git a/openfeature-provider/android/demo-app/src/main/kotlin/com/spotify/confidence/demo/MainActivity.kt b/openfeature-provider/android/demo-app/src/main/kotlin/com/spotify/confidence/demo/MainActivity.kt new file mode 100644 index 00000000..3539edb3 --- /dev/null +++ b/openfeature-provider/android/demo-app/src/main/kotlin/com/spotify/confidence/demo/MainActivity.kt @@ -0,0 +1,372 @@ +package com.spotify.confidence.demo + +import android.os.Bundle +import android.util.Log +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import com.spotify.confidence.android.ConfidenceLocalProvider +import com.spotify.confidence.android.LocalProviderConfig +import com.spotify.confidence.demo.databinding.ActivityMainBinding +import dev.openfeature.kotlin.sdk.ImmutableContext +import dev.openfeature.kotlin.sdk.OpenFeatureAPI +import dev.openfeature.kotlin.sdk.Value +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicLong + +class MainActivity : AppCompatActivity() { + + private lateinit var binding: ActivityMainBinding + private val logBuilder = StringBuilder() + private val dateFormat = SimpleDateFormat("HH:mm:ss.SSS", Locale.US) + + // Benchmark state + private var benchmarkJob: Job? = null + private val isBenchmarkRunning = AtomicBoolean(false) + private val totalRequests = AtomicInteger(0) + private val totalErrors = AtomicInteger(0) + private val totalLatencyNanos = AtomicLong(0) + private val minLatencyNanos = AtomicLong(Long.MAX_VALUE) + private val maxLatencyNanos = AtomicLong(0) + private var benchmarkStartTime = 0L + private val latencyHistogram = mutableListOf() // Store latencies for percentile calculation + + companion object { + private const val TAG = "ConfidenceDemo" + // Set via BuildConfig from local.properties: confidence.clientSecret=YOUR_SECRET + private const val CLIENT_SECRET = BuildConfig.CONFIDENCE_CLIENT_SECRET + private const val FLAG_KEY = "mattias-boolean-flag.enabled" + private const val UI_UPDATE_INTERVAL_MS = 100L + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + setupButtons() + log("Demo app started") + log("Client Secret: ${CLIENT_SECRET.take(8)}...") + log("Flag to resolve: $FLAG_KEY") + } + + private fun setupButtons() { + binding.initButton.setOnClickListener { + initializeProvider() + } + + binding.resolveButton.setOnClickListener { + resolveFlag() + } + + binding.benchmarkButton.setOnClickListener { + if (isBenchmarkRunning.get()) { + stopBenchmark() + } else { + startBenchmark() + } + } + } + + private fun initializeProvider() { + binding.initButton.isEnabled = false + binding.statusText.text = "Status: Initializing..." + log("Initializing provider...") + + lifecycleScope.launch { + try { + withContext(Dispatchers.IO) { + val config = LocalProviderConfig.Builder() + .build() + + val provider = ConfidenceLocalProvider.create( + clientSecret = CLIENT_SECRET, + config = config + ) + + log("Provider created: ${provider.metadata.name}") + + val context = ImmutableContext( + targetingKey = "vahid", + attributes = mapOf( + "user_id" to Value.String("vahid"), + "country" to Value.String("SE"), + "platform" to Value.String("android"), + "app_version" to Value.String("1.0.0") + ) + ) + + log("Setting evaluation context: targeting_key=vahid") + OpenFeatureAPI.setEvaluationContext(context) + + log("Calling provider.initialize()...") + provider.initialize(context) + + OpenFeatureAPI.setProvider(provider) + log("Provider set successfully!") + } + + withContext(Dispatchers.Main) { + binding.statusText.text = "Status: Ready" + binding.statusText.setTextColor(getColor(android.R.color.holo_green_dark)) + binding.resolveButton.isEnabled = true + binding.benchmarkButton.isEnabled = true + binding.initButton.text = "Re-init" + binding.initButton.isEnabled = true + log("Provider initialization complete!") + } + + } catch (e: Exception) { + Log.e(TAG, "Failed to initialize", e) + withContext(Dispatchers.Main) { + binding.statusText.text = "Status: Error" + binding.statusText.setTextColor(getColor(android.R.color.holo_red_dark)) + binding.initButton.isEnabled = true + log("ERROR: ${e.message}") + } + } + } + } + + private fun resolveFlag() { + binding.resolveButton.isEnabled = false + log("Resolving flag: $FLAG_KEY") + + lifecycleScope.launch { + try { + val client = OpenFeatureAPI.getClient() + val startTime = System.currentTimeMillis() + + val evaluation = withContext(Dispatchers.IO) { + client.getBooleanDetails(FLAG_KEY, false) + } + + val duration = System.currentTimeMillis() - startTime + + withContext(Dispatchers.Main) { + binding.flagValueText.text = "Value: ${evaluation.value}" + binding.variantText.text = "Variant: ${evaluation.variant ?: "N/A"}" + binding.reasonText.text = "Reason: ${evaluation.reason ?: "N/A"}" + + val color = if (evaluation.value) { + getColor(android.R.color.holo_green_dark) + } else { + getColor(android.R.color.holo_red_dark) + } + binding.flagValueText.setTextColor(color) + + log("=== RESOLUTION RESULT ===") + log("Value: ${evaluation.value}, Variant: ${evaluation.variant}, Duration: ${duration}ms") + log("=========================") + + binding.resolveButton.isEnabled = true + } + + } catch (e: Exception) { + Log.e(TAG, "Failed to resolve flag", e) + withContext(Dispatchers.Main) { + binding.flagValueText.text = "Value: ERROR" + binding.flagValueText.setTextColor(getColor(android.R.color.holo_red_dark)) + log("ERROR resolving flag: ${e.message}") + binding.resolveButton.isEnabled = true + } + } + } + } + + private fun startBenchmark() { + // Reset stats + totalRequests.set(0) + totalErrors.set(0) + totalLatencyNanos.set(0) + minLatencyNanos.set(Long.MAX_VALUE) + maxLatencyNanos.set(0) + synchronized(latencyHistogram) { + latencyHistogram.clear() + } + benchmarkStartTime = System.nanoTime() + + isBenchmarkRunning.set(true) + binding.benchmarkButton.text = "STOP" + binding.benchmarkButton.setBackgroundColor(getColor(android.R.color.holo_red_dark)) + binding.benchmarkCard.visibility = View.VISIBLE + binding.resolveButton.isEnabled = false + binding.initButton.isEnabled = false + + log("=== BENCHMARK STARTED ===") + log("Press STOP to end the benchmark") + + // Start the benchmark worker + benchmarkJob = lifecycleScope.launch { + val client = OpenFeatureAPI.getClient() + + // UI update job + val uiUpdateJob = launch { + while (isActive && isBenchmarkRunning.get()) { + updateBenchmarkUI() + delay(UI_UPDATE_INTERVAL_MS) + } + } + + // Benchmark worker - run continuously until stopped + withContext(Dispatchers.IO) { + while (isBenchmarkRunning.get()) { + val startNanos = System.nanoTime() + try { + client.getBooleanDetails(FLAG_KEY, false) + val latencyNanos = System.nanoTime() - startNanos + + totalRequests.incrementAndGet() + totalLatencyNanos.addAndGet(latencyNanos) + + // Update min/max atomically + var current = minLatencyNanos.get() + while (latencyNanos < current) { + if (minLatencyNanos.compareAndSet(current, latencyNanos)) break + current = minLatencyNanos.get() + } + + current = maxLatencyNanos.get() + while (latencyNanos > current) { + if (maxLatencyNanos.compareAndSet(current, latencyNanos)) break + current = maxLatencyNanos.get() + } + + // Store for percentile calculation (limit to last 10000 samples) + synchronized(latencyHistogram) { + if (latencyHistogram.size >= 10000) { + latencyHistogram.removeAt(0) + } + latencyHistogram.add(latencyNanos) + } + + } catch (e: Exception) { + totalErrors.incrementAndGet() + } + } + } + + uiUpdateJob.cancel() + } + } + + private fun stopBenchmark() { + isBenchmarkRunning.set(false) + benchmarkJob?.cancel() + + binding.benchmarkButton.text = "Benchmark" + binding.benchmarkButton.setBackgroundColor(getColor(android.R.color.holo_blue_dark)) + binding.resolveButton.isEnabled = true + binding.initButton.isEnabled = true + + // Final UI update + updateBenchmarkUI() + binding.benchmarkStatusText.text = "Stopped" + binding.benchmarkStatusText.setTextColor(getColor(android.R.color.darker_gray)) + + // Log final results + val requests = totalRequests.get() + val errors = totalErrors.get() + val elapsedSeconds = (System.nanoTime() - benchmarkStartTime) / 1_000_000_000.0 + val rps = if (elapsedSeconds > 0) requests / elapsedSeconds else 0.0 + val avgLatencyMs = if (requests > 0) (totalLatencyNanos.get() / requests) / 1_000_000.0 else 0.0 + + val (p50, p95, p99) = calculatePercentiles() + + log("=== BENCHMARK COMPLETED ===") + log("Total Requests: $requests") + log("Total Errors: $errors") + log("Duration: %.2f seconds".format(elapsedSeconds)) + log("RPS: %.1f".format(rps)) + log("Avg Latency: %.2f ms".format(avgLatencyMs)) + log("Min Latency: %.2f ms".format(minLatencyNanos.get() / 1_000_000.0)) + log("Max Latency: %.2f ms".format(maxLatencyNanos.get() / 1_000_000.0)) + log("P50: %.2f ms, P95: %.2f ms, P99: %.2f ms".format(p50, p95, p99)) + log("===========================") + } + + private fun updateBenchmarkUI() { + runOnUiThread { + val requests = totalRequests.get() + val errors = totalErrors.get() + val elapsedNanos = System.nanoTime() - benchmarkStartTime + val elapsedSeconds = elapsedNanos / 1_000_000_000.0 + + // Calculate RPS + val rps = if (elapsedSeconds > 0) requests / elapsedSeconds else 0.0 + + // Calculate average latency + val avgLatencyMs = if (requests > 0) { + (totalLatencyNanos.get() / requests) / 1_000_000.0 + } else { + 0.0 + } + + // Calculate percentiles + val (p50, p95, p99) = calculatePercentiles() + + // Update UI + binding.totalRequestsText.text = "%,d".format(requests) + binding.rpsText.text = "%.1f".format(rps) + binding.avgLatencyText.text = "%.2f ms".format(avgLatencyMs) + + val minMs = if (minLatencyNanos.get() == Long.MAX_VALUE) 0.0 else minLatencyNanos.get() / 1_000_000.0 + val maxMs = maxLatencyNanos.get() / 1_000_000.0 + binding.minMaxLatencyText.text = "%.1f / %.1f ms".format(minMs, maxMs) + + binding.percentilesText.text = "%.1f / %.1f / %.1f ms".format(p50, p95, p99) + binding.errorsText.text = errors.toString() + + if (isBenchmarkRunning.get()) { + binding.benchmarkStatusText.text = "Running... (%.1fs)".format(elapsedSeconds) + binding.benchmarkStatusText.setTextColor(getColor(android.R.color.holo_blue_dark)) + } + } + } + + private fun calculatePercentiles(): Triple { + synchronized(latencyHistogram) { + if (latencyHistogram.isEmpty()) { + return Triple(0.0, 0.0, 0.0) + } + + val sorted = latencyHistogram.sorted() + val size = sorted.size + + val p50Index = (size * 0.50).toInt().coerceIn(0, size - 1) + val p95Index = (size * 0.95).toInt().coerceIn(0, size - 1) + val p99Index = (size * 0.99).toInt().coerceIn(0, size - 1) + + return Triple( + sorted[p50Index] / 1_000_000.0, + sorted[p95Index] / 1_000_000.0, + sorted[p99Index] / 1_000_000.0 + ) + } + } + + private fun log(message: String) { + val timestamp = dateFormat.format(Date()) + val logLine = "[$timestamp] $message\n" + Log.d(TAG, message) + + runOnUiThread { + logBuilder.append(logLine) + // Keep log size manageable + if (logBuilder.length > 5000) { + logBuilder.delete(0, logBuilder.length - 4000) + } + binding.logText.text = logBuilder.toString() + } + } +} diff --git a/openfeature-provider/android/demo-app/src/main/res/drawable/ic_launcher_foreground.xml b/openfeature-provider/android/demo-app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000..e15b0eea --- /dev/null +++ b/openfeature-provider/android/demo-app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/openfeature-provider/android/demo-app/src/main/res/layout/activity_main.xml b/openfeature-provider/android/demo-app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..4c107cee --- /dev/null +++ b/openfeature-provider/android/demo-app/src/main/res/layout/activity_main.xml @@ -0,0 +1,339 @@ + + + + + + + + + +