From 52d7a00554161d4d7e704631b0f0206a4eaff369 Mon Sep 17 00:00:00 2001 From: Jose Ulises Nino Rivera Date: Fri, 4 Dec 2020 09:18:36 -0800 Subject: [PATCH] config: add ability to register native filters (#1193) Description: adding templating to register native filters. Risk Level: low - optional feature Testing: used in example apps Docs Changes: pending Signed-off-by: Jose Nino Signed-off-by: JP Simard --- .../kotlin/hello_world/MainActivity.kt | 8 ++-- .../swift/hello_world/ViewController.swift | 9 +++-- mobile/library/common/config_template.cc | 6 +++ mobile/library/common/jni/jni_interface.cc | 12 +++++- mobile/library/common/main_interface.h | 5 +++ .../io/envoyproxy/envoymobile/engine/BUILD | 1 + .../engine/EnvoyConfiguration.java | 36 ++++++++++++----- .../envoymobile/engine/EnvoyEngineImpl.java | 8 ++-- .../engine/EnvoyNativeFilterConfig.java | 18 +++++++++ .../envoymobile/engine/JniLibrary.java | 13 +++++- .../engine/EnvoyConfigurationTest.kt | 29 +++++++++++--- .../envoyproxy/envoymobile/EngineBuilder.kt | 29 +++++++++++--- .../envoymobile/EngineBuilderTest.kt | 10 +++++ mobile/library/objective-c/BUILD | 1 + .../library/objective-c/EnvoyConfiguration.m | 40 +++++++++++++------ mobile/library/objective-c/EnvoyEngine.h | 20 ++++++++-- mobile/library/objective-c/EnvoyEngineImpl.m | 2 +- .../objective-c/EnvoyNativeFilterConfig.m | 16 ++++++++ mobile/library/swift/src/EngineBuilder.swift | 32 +++++++++++---- .../swift/test/EngineBuilderTests.swift | 35 +++++++++++++--- .../HttpBridgeTests/CancelStreamTest.swift | 2 +- .../HttpBridgeTests/ReceiveDataTest.swift | 2 +- .../HttpBridgeTests/ReceiveErrorTest.swift | 2 +- .../test/HttpBridgeTests/SendDataTest.swift | 2 +- .../HttpBridgeTests/SendHeadersTest.swift | 2 +- .../HttpBridgeTests/SendTrailersTest.swift | 2 +- 26 files changed, 274 insertions(+), 68 deletions(-) create mode 100644 mobile/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyNativeFilterConfig.java create mode 100644 mobile/library/objective-c/EnvoyNativeFilterConfig.m diff --git a/mobile/examples/kotlin/hello_world/MainActivity.kt b/mobile/examples/kotlin/hello_world/MainActivity.kt index f8814bf1877c..02604c3d638c 100644 --- a/mobile/examples/kotlin/hello_world/MainActivity.kt +++ b/mobile/examples/kotlin/hello_world/MainActivity.kt @@ -39,15 +39,17 @@ class MainActivity : Activity() { private lateinit var viewAdapter: ResponseRecyclerViewAdapter private lateinit var engine: Engine + @Suppress("MaxLineLength") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) engine = AndroidEngineBuilder(application) - .addFilter { DemoFilter() } - .addFilter { BufferDemoFilter() } - .addFilter { AsyncDemoFilter() } + .addPlatformFilter { DemoFilter() } + .addPlatformFilter { BufferDemoFilter() } + .addPlatformFilter { AsyncDemoFilter() } .addStringAccessor("demo-accessor", DemoStringAccessor()) + .addNativeFilter("envoy.filters.http.buffer", "{\"@type\":\"type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer\",\"max_request_bytes\":5242880}") .setOnEngineRunning { Log.d("MainActivity", "Envoy async internal setup completed") } .build() diff --git a/mobile/examples/swift/hello_world/ViewController.swift b/mobile/examples/swift/hello_world/ViewController.swift index b092336ea50b..39fda3eeee46 100644 --- a/mobile/examples/swift/hello_world/ViewController.swift +++ b/mobile/examples/swift/hello_world/ViewController.swift @@ -19,9 +19,12 @@ final class ViewController: UITableViewController { do { NSLog("starting Envoy...") let engine = try EngineBuilder() - .addFilter(factory: DemoFilter.init) - .addFilter(factory: BufferDemoFilter.init) - .addFilter(factory: AsyncDemoFilter.init) + .addPlatformFilter(factory: DemoFilter.init) + .addPlatformFilter(factory: BufferDemoFilter.init) + .addPlatformFilter(factory: AsyncDemoFilter.init) + .addNativeFilter(name: "envoy.filters.http.buffer", + // swiftlint:disable:next line_length + typedConfig: "{\"@type\":\"type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer\",\"max_request_bytes\":5242880}") .setOnEngineRunning { NSLog("Envoy async internal setup completed") } .build() self.streamClient = engine.streamClient() diff --git a/mobile/library/common/config_template.cc b/mobile/library/common/config_template.cc index bdf864dbbc4f..35a48e2c6733 100644 --- a/mobile/library/common/config_template.cc +++ b/mobile/library/common/config_template.cc @@ -8,6 +8,11 @@ const char* platform_filter_template = R"( platform_filter_name: {{ platform_filter_name }} )"; +const char* native_filter_template = R"( + - name: {{ native_filter_name }} + typed_config: {{ native_filter_typed_config }} +)"; + const char* config_template = R"( static_resources: listeners: @@ -41,6 +46,7 @@ const char* config_template = R"( max_interval: 60s http_filters: {{ platform_filter_chain }} +{{ native_filter_chain }} - name: envoy.filters.http.dynamic_forward_proxy typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.FilterConfig diff --git a/mobile/library/common/jni/jni_interface.cc b/mobile/library/common/jni/jni_interface.cc index 60fd27870fbc..00aed8dced3b 100644 --- a/mobile/library/common/jni/jni_interface.cc +++ b/mobile/library/common/jni/jni_interface.cc @@ -73,13 +73,21 @@ Java_io_envoyproxy_envoymobile_engine_JniLibrary_templateString(JNIEnv* env, } extern "C" JNIEXPORT jstring JNICALL -Java_io_envoyproxy_envoymobile_engine_JniLibrary_filterTemplateString(JNIEnv* env, - jclass // class +Java_io_envoyproxy_envoymobile_engine_JniLibrary_platformFilterTemplateString(JNIEnv* env, + jclass // class ) { jstring result = env->NewStringUTF(platform_filter_template); return result; } +extern "C" JNIEXPORT jstring JNICALL +Java_io_envoyproxy_envoymobile_engine_JniLibrary_nativeFilterTemplateString(JNIEnv* env, + jclass // class +) { + jstring result = env->NewStringUTF(native_filter_template); + return result; +} + extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_recordCounterInc( JNIEnv* env, jclass, // class diff --git a/mobile/library/common/main_interface.h b/mobile/library/common/main_interface.h index 22ecd4e1bb79..e35922746d17 100644 --- a/mobile/library/common/main_interface.h +++ b/mobile/library/common/main_interface.h @@ -23,6 +23,11 @@ extern const char* config_template; */ extern const char* platform_filter_template; +/** + * Template configuration used for dynamic creation of the native filter chain. + */ +extern const char* native_filter_template; + /** * Initialize an underlying HTTP stream. * @param engine, handle to the engine that will manage this stream. diff --git a/mobile/library/java/src/io/envoyproxy/envoymobile/engine/BUILD b/mobile/library/java/src/io/envoyproxy/envoymobile/engine/BUILD index 79ad48ed5c29..92226f4b5a28 100644 --- a/mobile/library/java/src/io/envoyproxy/envoymobile/engine/BUILD +++ b/mobile/library/java/src/io/envoyproxy/envoymobile/engine/BUILD @@ -28,6 +28,7 @@ java_library( "EnvoyEngineImpl.java", "EnvoyHTTPFilterCallbacksImpl.java", "EnvoyHTTPStream.java", + "EnvoyNativeFilterConfig.java", "EnvoyNativeResourceRegistry.java", "EnvoyNativeResourceReleaser.java", "EnvoyNativeResourceWrapper.java", diff --git a/mobile/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java b/mobile/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java index 3f98b68baf0f..5f7515114aa0 100644 --- a/mobile/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java +++ b/mobile/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java @@ -15,11 +15,12 @@ public class EnvoyConfiguration { public final Integer dnsRefreshSeconds; public final Integer dnsFailureRefreshSecondsBase; public final Integer dnsFailureRefreshSecondsMax; - public final List httpFilterFactories; + public final List httpPlatformFilterFactories; public final Integer statsFlushSeconds; public final String appVersion; public final String appId; public final String virtualClusters; + public final List nativeFilterChain; public final Map stringAccessors; private static final Pattern UNRESOLVED_KEY_PATTERN = Pattern.compile("\\{\\{ (.+) \\}\\}"); @@ -37,23 +38,27 @@ public class EnvoyConfiguration { * @param appVersion the App Version of the App using this Envoy Client. * @param appId the App ID of the App using this Envoy Client. * @param virtualClusters the JSON list of virtual cluster configs. + * @param nativeFilterChain the configuration for native filters. + * @param httpPlatformFilterFactories the configuration for platform filters. * @param stringAccesssors platform string accessors to register. */ public EnvoyConfiguration(String statsDomain, int connectTimeoutSeconds, int dnsRefreshSeconds, int dnsFailureRefreshSecondsBase, int dnsFailureRefreshSecondsMax, - List httpFilterFactories, int statsFlushSeconds, - String appVersion, String appId, String virtualClusters, + int statsFlushSeconds, String appVersion, String appId, + String virtualClusters, List nativeFilterChain, + List httpPlatformFilterFactories, Map stringAccessors) { this.statsDomain = statsDomain; this.connectTimeoutSeconds = connectTimeoutSeconds; this.dnsRefreshSeconds = dnsRefreshSeconds; this.dnsFailureRefreshSecondsBase = dnsFailureRefreshSecondsBase; this.dnsFailureRefreshSecondsMax = dnsFailureRefreshSecondsMax; - this.httpFilterFactories = httpFilterFactories; this.statsFlushSeconds = statsFlushSeconds; this.appVersion = appVersion; this.appId = appId; this.virtualClusters = virtualClusters; + this.nativeFilterChain = nativeFilterChain; + this.httpPlatformFilterFactories = httpPlatformFilterFactories; this.stringAccessors = stringAccessors; } @@ -62,19 +67,31 @@ public EnvoyConfiguration(String statsDomain, int connectTimeoutSeconds, int dns * configuration. * * @param templateYAML the template configuration to resolve. + * @param platformFilterTemplateYAML helper template to build platform http filters. + * @param nativeFilterTemplateYAML helper template to build native http filters. * @return String, the resolved template. * @throws ConfigurationException, when the template provided is not fully * resolved. */ - String resolveTemplate(final String templateYAML, final String filterTemplateYAML) { + String resolveTemplate(final String templateYAML, final String platformFilterTemplateYAML, + final String nativeFilterTemplateYAML) { final StringBuilder filterConfigBuilder = new StringBuilder(); - for (EnvoyHTTPFilterFactory filterFactory : httpFilterFactories) { - String filterConfig = - filterTemplateYAML.replace("{{ platform_filter_name }}", filterFactory.getFilterName()); + for (EnvoyHTTPFilterFactory filterFactory : httpPlatformFilterFactories) { + String filterConfig = platformFilterTemplateYAML.replace("{{ platform_filter_name }}", + filterFactory.getFilterName()); filterConfigBuilder.append(filterConfig); } String filterConfigChain = filterConfigBuilder.toString(); + final StringBuilder nativeFilterConfigBuilder = new StringBuilder(); + for (EnvoyNativeFilterConfig filter : nativeFilterChain) { + String nativeFilterConfig = + nativeFilterTemplateYAML.replace("{{ native_filter_name }}", filter.name) + .replace("{{ native_filter_typed_config }}", filter.typedConfig); + nativeFilterConfigBuilder.append(nativeFilterConfig); + } + String nativeFilterConfigChain = nativeFilterConfigBuilder.toString(); + String resolvedConfiguration = templateYAML.replace("{{ stats_domain }}", statsDomain) .replace("{{ platform_filter_chain }}", filterConfigChain) @@ -88,7 +105,8 @@ String resolveTemplate(final String templateYAML, final String filterTemplateYAM .replace("{{ device_os }}", "Android") .replace("{{ app_version }}", appVersion) .replace("{{ app_id }}", appId) - .replace("{{ virtual_clusters }}", virtualClusters); + .replace("{{ virtual_clusters }}", virtualClusters) + .replace("{{ native_filter_chain }}", nativeFilterConfigChain); final Matcher unresolvedKeys = UNRESOLVED_KEY_PATTERN.matcher(resolvedConfiguration); if (unresolvedKeys.find()) { diff --git a/mobile/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyEngineImpl.java b/mobile/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyEngineImpl.java index d980932ac501..fc05d150ed97 100644 --- a/mobile/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyEngineImpl.java +++ b/mobile/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyEngineImpl.java @@ -67,7 +67,8 @@ public int runWithConfig(String configurationYAML, String logLevel, @Override public int runWithConfig(EnvoyConfiguration envoyConfiguration, String logLevel, EnvoyOnEngineRunning onEngineRunning) { - for (EnvoyHTTPFilterFactory filterFactory : envoyConfiguration.httpFilterFactories) { + + for (EnvoyHTTPFilterFactory filterFactory : envoyConfiguration.httpPlatformFilterFactories) { JniLibrary.registerFilterFactory(filterFactory.getFilterName(), new JvmFilterFactoryContext(filterFactory)); } @@ -78,8 +79,9 @@ public int runWithConfig(EnvoyConfiguration envoyConfiguration, String logLevel, new JvmStringAccessorContext(entry.getValue())); } - return runWithConfig(envoyConfiguration.resolveTemplate(JniLibrary.templateString(), - JniLibrary.filterTemplateString()), + return runWithConfig(envoyConfiguration.resolveTemplate( + JniLibrary.templateString(), JniLibrary.platformFilterTemplateString(), + JniLibrary.nativeFilterTemplateString()), logLevel, onEngineRunning); } diff --git a/mobile/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyNativeFilterConfig.java b/mobile/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyNativeFilterConfig.java new file mode 100644 index 000000000000..bb486758b6b2 --- /dev/null +++ b/mobile/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyNativeFilterConfig.java @@ -0,0 +1,18 @@ +package io.envoyproxy.envoymobile.engine; + +/* Datatype used by the EnvoyConfiguration to create a native http filter chain. */ +public class EnvoyNativeFilterConfig { + public final String name; + public final String typedConfig; + + /** + * Create a new instance of the configuration + * + * @param name the name of the filter. + * @param typedConfig the filter configuration. + */ + public EnvoyNativeFilterConfig(String name, String typedConfig) { + this.name = name; + this.typedConfig = typedConfig; + } +} diff --git a/mobile/library/java/src/io/envoyproxy/envoymobile/engine/JniLibrary.java b/mobile/library/java/src/io/envoyproxy/envoymobile/engine/JniLibrary.java index a1c40c01d657..055f9355d121 100644 --- a/mobile/library/java/src/io/envoyproxy/envoymobile/engine/JniLibrary.java +++ b/mobile/library/java/src/io/envoyproxy/envoymobile/engine/JniLibrary.java @@ -198,9 +198,18 @@ protected static native int runEngine(long engine, String config, String logLeve * filter config chains. * * @return A template that may be used as a starting point for constructing - * filter platform filter configuration. + * platform filter configuration. */ - public static native String filterTemplateString(); + public static native String platformFilterTemplateString(); + + /** + * Provides a configuration template that may be used for building native + * filter config chains. + * + * @return A template that may be used as a starting point for constructing + * native filter configuration. + */ + public static native String nativeFilterTemplateString(); /** * Register a string accessor to get strings from the platform. diff --git a/mobile/library/java/test/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt b/mobile/library/java/test/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt index 6a49a409b09c..f20e6ff7710b 100644 --- a/mobile/library/java/test/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt +++ b/mobile/library/java/test/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt @@ -16,6 +16,8 @@ mock_template: max_interval: {{ dns_failure_refresh_rate_seconds_max }}s platform_filter_chain: {{ platform_filter_chain }} + native_filter_chain: +{{ native_filter_chain }} stats_flush_interval: {{ stats_flush_interval_seconds }}s os: {{ device_os }} app_version: {{ app_version }} @@ -23,18 +25,28 @@ mock_template: virtual_clusters: {{ virtual_clusters }} """ -private const val FILTER_CONFIG = - """ +private const val PLATFORM_FILTER_CONFIG = +""" - platform_filter_name: {{ platform_filter_name }} """ +private const val NATIVE_FILTER_CONFIG = +""" + - name: {{ native_filter_name }} + typed_config: {{ native_filter_typed_config }} +""" + class EnvoyConfigurationTest { @Test fun `resolving with default configuration resolves with values`() { - val envoyConfiguration = EnvoyConfiguration("stats.foo.com", 123, 234, 345, 456, emptyList(), 567, "v1.2.3", "com.mydomain.myapp", "[test]", emptyMap()) + val envoyConfiguration = EnvoyConfiguration( + "stats.foo.com", 123, 234, 345, 456, 567, "v1.2.3", "com.mydomain.myapp", "[test]", + listOf(EnvoyNativeFilterConfig("filter_name", "test_config")), + emptyList(), emptyMap() + ) - val resolvedTemplate = envoyConfiguration.resolveTemplate(TEST_CONFIG, FILTER_CONFIG) + val resolvedTemplate = envoyConfiguration.resolveTemplate(TEST_CONFIG, PLATFORM_FILTER_CONFIG, NATIVE_FILTER_CONFIG) assertThat(resolvedTemplate).contains("stats_domain: stats.foo.com") assertThat(resolvedTemplate).contains("connect_timeout: 123s") assertThat(resolvedTemplate).contains("dns_refresh_rate: 234s") @@ -45,14 +57,19 @@ class EnvoyConfigurationTest { assertThat(resolvedTemplate).contains("app_version: v1.2.3") assertThat(resolvedTemplate).contains("app_id: com.mydomain.myapp") assertThat(resolvedTemplate).contains("virtual_clusters: [test]") + assertThat(resolvedTemplate).contains("filter_name") + assertThat(resolvedTemplate).contains("test_config") } @Test fun `resolve templates with invalid templates will throw on build`() { - val envoyConfiguration = EnvoyConfiguration("stats.foo.com", 123, 234, 345, 456, emptyList(), 567, "v1.2.3", "com.mydomain.myapp", "[test]", emptyMap()) + val envoyConfiguration = EnvoyConfiguration( + "stats.foo.com", 123, 234, 345, 456, 567, "v1.2.3", "com.mydomain.myapp", "[test]", + emptyList(), emptyList(), emptyMap() + ) try { - envoyConfiguration.resolveTemplate("{{ missing }}", "") + envoyConfiguration.resolveTemplate("{{ missing }}", "", "") fail("Unresolved configuration keys should trigger exception.") } catch (e: EnvoyConfiguration.ConfigurationException) { assertThat(e.message).contains("missing") diff --git a/mobile/library/kotlin/src/io/envoyproxy/envoymobile/EngineBuilder.kt b/mobile/library/kotlin/src/io/envoyproxy/envoymobile/EngineBuilder.kt index 0105fc9f006b..0bab28103798 100644 --- a/mobile/library/kotlin/src/io/envoyproxy/envoymobile/EngineBuilder.kt +++ b/mobile/library/kotlin/src/io/envoyproxy/envoymobile/EngineBuilder.kt @@ -3,6 +3,7 @@ package io.envoyproxy.envoymobile import io.envoyproxy.envoymobile.engine.EnvoyConfiguration import io.envoyproxy.envoymobile.engine.EnvoyEngine import io.envoyproxy.envoymobile.engine.EnvoyEngineImpl +import io.envoyproxy.envoymobile.engine.EnvoyNativeFilterConfig import io.envoyproxy.envoymobile.engine.types.EnvoyHTTPFilterFactory import io.envoyproxy.envoymobile.engine.types.EnvoyStringAccessor import java.util.UUID @@ -27,11 +28,12 @@ open class EngineBuilder( private var dnsRefreshSeconds = 60 private var dnsFailureRefreshSecondsBase = 2 private var dnsFailureRefreshSecondsMax = 10 - private var filterChain = mutableListOf() private var statsFlushSeconds = 60 private var appVersion = "unspecified" private var appId = "unspecified" private var virtualClusters = "[]" + private var platformFilterChain = mutableListOf() + private var nativeFilterChain = mutableListOf() private var stringAccessors = mutableMapOf() /** @@ -109,7 +111,7 @@ open class EngineBuilder( } /** - * Add an HTTP filter factory used to create filters for streams sent by this client. + * Add an HTTP filter factory used to create platform filters for streams sent by this client. * * @param name Custom name to use for this filter factory. Useful for having * more meaningful trace logs, but not required. Should be unique @@ -118,9 +120,25 @@ open class EngineBuilder( * * @return this builder. */ - fun addFilter(name: String = UUID.randomUUID().toString(), factory: () -> Filter): + fun addPlatformFilter(name: String = UUID.randomUUID().toString(), factory: () -> Filter): EngineBuilder { - this.filterChain.add(FilterFactory(name, factory)) + this.platformFilterChain.add(FilterFactory(name, factory)) + return this + } + + /** + * Add an HTTP filter config used to create native filters for streams sent by this client. + * + * @param name Custom name to use for this filter factory. Useful for having + * more meaningful trace logs, but not required. Should be unique + * per filter. + * @param typedConfig config string for the filter. + * + * @return this builder. + */ + fun addNativeFilter(name: String = UUID.randomUUID().toString(), typedConfig: String): + EngineBuilder { + this.nativeFilterChain.add(EnvoyNativeFilterConfig(name, typedConfig)) return this } @@ -201,7 +219,8 @@ open class EngineBuilder( EnvoyConfiguration( statsDomain, connectTimeoutSeconds, dnsRefreshSeconds, dnsFailureRefreshSecondsBase, dnsFailureRefreshSecondsMax, - filterChain, statsFlushSeconds, appVersion, appId, virtualClusters, stringAccessors + statsFlushSeconds, appVersion, appId, virtualClusters, nativeFilterChain, + platformFilterChain, stringAccessors ), logLevel, onEngineRunning ) diff --git a/mobile/library/kotlin/test/io/envoyproxy/envoymobile/EngineBuilderTest.kt b/mobile/library/kotlin/test/io/envoyproxy/envoymobile/EngineBuilderTest.kt index fbae551bd329..b96cafd25928 100644 --- a/mobile/library/kotlin/test/io/envoyproxy/envoymobile/EngineBuilderTest.kt +++ b/mobile/library/kotlin/test/io/envoyproxy/envoymobile/EngineBuilderTest.kt @@ -99,4 +99,14 @@ class EngineBuilderTest { val engine = engineBuilder.build() as EngineImpl assertThat(engine.envoyConfiguration!!.virtualClusters).isEqualTo("[test]") } + + @Test + fun `specifying native filters overrides default`() { + engineBuilder = EngineBuilder(Standard()) + engineBuilder.addEngineType { envoyEngine } + engineBuilder.addNativeFilter("name", "config") + + val engine = engineBuilder.build() as EngineImpl + assertThat(engine.envoyConfiguration!!.nativeFilterChain.size).isEqualTo(1) + } } diff --git a/mobile/library/objective-c/BUILD b/mobile/library/objective-c/BUILD index d5f2af2798bb..f3a6907768aa 100644 --- a/mobile/library/objective-c/BUILD +++ b/mobile/library/objective-c/BUILD @@ -17,6 +17,7 @@ objc_library( "EnvoyHTTPFilterCallbacksImpl.m", "EnvoyHTTPFilterFactory.m", "EnvoyHTTPStreamImpl.m", + "EnvoyNativeFilterConfig.m", "EnvoyNetworkMonitor.m", ], hdrs = [ diff --git a/mobile/library/objective-c/EnvoyConfiguration.m b/mobile/library/objective-c/EnvoyConfiguration.m index 7930ccae2fd4..8cef88e328cb 100644 --- a/mobile/library/objective-c/EnvoyConfiguration.m +++ b/mobile/library/objective-c/EnvoyConfiguration.m @@ -9,11 +9,13 @@ - (instancetype)initWithStatsDomain:(NSString *)statsDomain dnsRefreshSeconds:(UInt32)dnsRefreshSeconds dnsFailureRefreshSecondsBase:(UInt32)dnsFailureRefreshSecondsBase dnsFailureRefreshSecondsMax:(UInt32)dnsFailureRefreshSecondsMax - filterChain:(NSArray *)httpFilterFactories statsFlushSeconds:(UInt32)statsFlushSeconds appVersion:(NSString *)appVersion appId:(NSString *)appId - virtualClusters:(NSString *)virtualClusters { + virtualClusters:(NSString *)virtualClusters + nativeFilterChain:(NSArray *)nativeFilterChain + platformFilterChain: + (NSArray *)httpPlatformFilterFactories { self = [super init]; if (!self) { return nil; @@ -24,26 +26,39 @@ - (instancetype)initWithStatsDomain:(NSString *)statsDomain self.dnsRefreshSeconds = dnsRefreshSeconds; self.dnsFailureRefreshSecondsBase = dnsFailureRefreshSecondsBase; self.dnsFailureRefreshSecondsMax = dnsFailureRefreshSecondsMax; - self.httpFilterFactories = httpFilterFactories; + self.httpPlatformFilterFactories = httpPlatformFilterFactories; self.statsFlushSeconds = statsFlushSeconds; self.appVersion = appVersion; self.appId = appId; self.virtualClusters = virtualClusters; + self.nativeFilterChain = nativeFilterChain; return self; } - (nullable NSString *)resolveTemplate:(NSString *)templateYAML { - NSString *filterConfigChain = [[NSString alloc] init]; - NSString *filterTemplate = [[NSString alloc] initWithUTF8String:platform_filter_template]; - for (EnvoyHTTPFilterFactory *filterFactory in self.httpFilterFactories) { - NSString *filterConfig = - [filterTemplate stringByReplacingOccurrencesOfString:@"{{ platform_filter_name }}" - withString:filterFactory.filterName]; - filterConfigChain = [filterConfigChain stringByAppendingString:filterConfig]; + NSString *platformFilterConfigChain = [[NSString alloc] init]; + NSString *platformFilterTemplate = [[NSString alloc] initWithUTF8String:platform_filter_template]; + for (EnvoyHTTPFilterFactory *filterFactory in self.httpPlatformFilterFactories) { + NSString *platformFilterConfig = + [platformFilterTemplate stringByReplacingOccurrencesOfString:@"{{ platform_filter_name }}" + withString:filterFactory.filterName]; + platformFilterConfigChain = + [platformFilterConfigChain stringByAppendingString:platformFilterConfig]; + } + + NSString *nativeFilterConfigChain = [[NSString alloc] init]; + NSString *nativeFilterTemplate = [[NSString alloc] initWithUTF8String:native_filter_template]; + for (EnvoyNativeFilterConfig *filterConfig in self.nativeFilterChain) { + NSString *nativeFilterConfig = + [[nativeFilterTemplate stringByReplacingOccurrencesOfString:@"{{ native_filter_name }}" + withString:filterConfig.name] + stringByReplacingOccurrencesOfString:@"{{ native_filter_typed_config }}" + withString:filterConfig.typedConfig]; + nativeFilterConfigChain = [nativeFilterConfigChain stringByAppendingString:nativeFilterConfig]; } NSDictionary *templateKeysToValues = @{ - @"platform_filter_chain" : filterConfigChain, + @"platform_filter_chain" : platformFilterConfigChain, @"stats_domain" : self.statsDomain, @"connect_timeout_seconds" : [NSString stringWithFormat:@"%lu", (unsigned long)self.connectTimeoutSeconds], @@ -58,7 +73,8 @@ - (nullable NSString *)resolveTemplate:(NSString *)templateYAML { @"device_os" : @"iOS", @"app_version" : self.appVersion, @"app_id" : self.appId, - @"virtual_clusters" : self.virtualClusters + @"virtual_clusters" : self.virtualClusters, + @"native_filter_chain" : nativeFilterConfigChain, }; for (NSString *templateKey in templateKeysToValues) { diff --git a/mobile/library/objective-c/EnvoyEngine.h b/mobile/library/objective-c/EnvoyEngine.h index 93dfe43b5f04..e4d89da9a9b0 100644 --- a/mobile/library/objective-c/EnvoyEngine.h +++ b/mobile/library/objective-c/EnvoyEngine.h @@ -218,6 +218,17 @@ extern const int kEnvoyFilterResumeStatusResumeIteration; @end +#pragma mark - EnvoyNativeFilterConfig + +@interface EnvoyNativeFilterConfig : NSObject + +@property (nonatomic, strong) NSString *name; +@property (nonatomic, strong) NSString *typedConfig; + +- (instancetype)initWithName:(NSString *)name typedConfig:(NSString *)typedConfig; + +@end + #pragma mark - EnvoyConfiguration /// Typed configuration that may be used for starting Envoy. @@ -228,11 +239,12 @@ extern const int kEnvoyFilterResumeStatusResumeIteration; @property (nonatomic, assign) UInt32 dnsRefreshSeconds; @property (nonatomic, assign) UInt32 dnsFailureRefreshSecondsBase; @property (nonatomic, assign) UInt32 dnsFailureRefreshSecondsMax; -@property (nonatomic, strong) NSArray *httpFilterFactories; @property (nonatomic, assign) UInt32 statsFlushSeconds; @property (nonatomic, strong) NSString *appVersion; @property (nonatomic, strong) NSString *appId; @property (nonatomic, strong) NSString *virtualClusters; +@property (nonatomic, strong) NSArray *nativeFilterChain; +@property (nonatomic, strong) NSArray *httpPlatformFilterFactories; /** Create a new instance of the configuration. @@ -242,11 +254,13 @@ extern const int kEnvoyFilterResumeStatusResumeIteration; dnsRefreshSeconds:(UInt32)dnsRefreshSeconds dnsFailureRefreshSecondsBase:(UInt32)dnsFailureRefreshSecondsBase dnsFailureRefreshSecondsMax:(UInt32)dnsFailureRefreshSecondsMax - filterChain:(NSArray *)httpFilterFactories statsFlushSeconds:(UInt32)statsFlushSeconds appVersion:(NSString *)appVersion appId:(NSString *)appId - virtualClusters:(NSString *)virtualClusters; + virtualClusters:(NSString *)virtualClusters + nativeFilterChain:(NSArray *)nativeFilterChain + platformFilterChain: + (NSArray *)httpPlatformFilterFactories; /** Resolves the provided configuration template using properties on this configuration. diff --git a/mobile/library/objective-c/EnvoyEngineImpl.m b/mobile/library/objective-c/EnvoyEngineImpl.m index 5007a91e2735..61727279e3a8 100644 --- a/mobile/library/objective-c/EnvoyEngineImpl.m +++ b/mobile/library/objective-c/EnvoyEngineImpl.m @@ -270,7 +270,7 @@ - (int)runWithConfig:(EnvoyConfiguration *)config return kEnvoyFailure; } - for (EnvoyHTTPFilterFactory *filterFactory in config.httpFilterFactories) { + for (EnvoyHTTPFilterFactory *filterFactory in config.httpPlatformFilterFactories) { [self registerFilterFactory:filterFactory]; } diff --git a/mobile/library/objective-c/EnvoyNativeFilterConfig.m b/mobile/library/objective-c/EnvoyNativeFilterConfig.m new file mode 100644 index 000000000000..860c22ed6e89 --- /dev/null +++ b/mobile/library/objective-c/EnvoyNativeFilterConfig.m @@ -0,0 +1,16 @@ +#import "library/objective-c/EnvoyEngine.h" + +@implementation EnvoyNativeFilterConfig + +- (instancetype)initWithName:(NSString *)name typedConfig:(NSString *)typedConfig { + self = [super init]; + if (!self) { + return nil; + } + + self.name = name; + self.typedConfig = typedConfig; + return self; +} + +@end diff --git a/mobile/library/swift/src/EngineBuilder.swift b/mobile/library/swift/src/EngineBuilder.swift index 2c6d796f8401..b0ecf3dd82eb 100644 --- a/mobile/library/swift/src/EngineBuilder.swift +++ b/mobile/library/swift/src/EngineBuilder.swift @@ -21,9 +21,10 @@ public final class EngineBuilder: NSObject { private var statsFlushSeconds: UInt32 = 60 private var appVersion: String = "unspecified" private var appId: String = "unspecified" - private var filterChain: [EnvoyHTTPFilterFactory] = [] + private var platformFilterChain: [EnvoyHTTPFilterFactory] = [] private var virtualClusters: String = "[]" private var onEngineRunning: (() -> Void)? + private var nativeFilterChain: [EnvoyNativeFilterConfig] = [] // MARK: - Public @@ -109,7 +110,7 @@ public final class EngineBuilder: NSObject { return self } - /// Add an HTTP filter factory used to construct filters for streams sent by this client. + /// Add an HTTP platform filter factory used to construct filters for streams sent by this client. /// /// - parameter name: Custom name to use for this filter factory. Useful for having /// more meaningful trace logs, but not required. Should be unique @@ -118,10 +119,26 @@ public final class EngineBuilder: NSObject { /// /// - returns: This builder. @discardableResult - public func addFilter(name: String = UUID().uuidString, - factory: @escaping () -> Filter) -> EngineBuilder + public func addPlatformFilter(name: String = UUID().uuidString, + factory: @escaping () -> Filter) -> EngineBuilder { - self.filterChain.append(EnvoyHTTPFilterFactory(filterName: name, factory: factory)) + self.platformFilterChain.append(EnvoyHTTPFilterFactory(filterName: name, factory: factory)) + return self + } + + /// Add an HTTP native filter factory used to construct filters for streams sent by this client. + /// + /// - parameter name: Custom name to use for this filter factory. Useful for having + /// more meaningful trace logs, but not required. Should be unique + /// per factory registered. + /// - parameter typedConfig: Config string for the filter. + /// + /// - returns: This builder. + @discardableResult + public func addNativeFilter(name: String = UUID().uuidString, + typedConfig: String) -> EngineBuilder + { + self.nativeFilterChain.append(EnvoyNativeFilterConfig(name: name, typedConfig: typedConfig)) return self } @@ -185,11 +202,12 @@ public final class EngineBuilder: NSObject { dnsRefreshSeconds: self.dnsRefreshSeconds, dnsFailureRefreshSecondsBase: self.dnsFailureRefreshSecondsBase, dnsFailureRefreshSecondsMax: self.dnsFailureRefreshSecondsMax, - filterChain: self.filterChain, statsFlushSeconds: self.statsFlushSeconds, appVersion: self.appVersion, appId: self.appId, - virtualClusters: self.virtualClusters) + virtualClusters: self.virtualClusters, + nativeFilterChain: self.nativeFilterChain, + platformFilterChain: self.platformFilterChain) return EngineImpl(config: config, logLevel: self.logLevel, engine: engine, onEngineRunning: self.onEngineRunning) } diff --git a/mobile/library/swift/test/EngineBuilderTests.swift b/mobile/library/swift/test/EngineBuilderTests.swift index f64cb0295873..9e94ef4ae0cd 100644 --- a/mobile/library/swift/test/EngineBuilderTests.swift +++ b/mobile/library/swift/test/EngineBuilderTests.swift @@ -18,6 +18,8 @@ mock_template: app_version: {{ app_version }} app_id: {{ app_id }} virtual_clusters: {{ virtual_clusters }} + native_filter_chain: +{{ native_filter_chain }} """ private struct TestFilter: Filter {} @@ -101,13 +103,13 @@ final class EngineBuilderTests: XCTestCase { func testAddingPlatformFiltersToConfigurationWhenRunningEnvoy() throws { let expectation = self.expectation(description: "Run called with expected data") MockEnvoyEngine.onRunWithConfig = { config, _ in - XCTAssertEqual(1, config.httpFilterFactories.count) + XCTAssertEqual(1, config.httpPlatformFilterFactories.count) expectation.fulfill() } _ = try EngineBuilder() .addEngineType(MockEnvoyEngine.self) - .addFilter(factory: TestFilter.init) + .addPlatformFilter(factory: TestFilter.init) .build() self.waitForExpectations(timeout: 0.01) } @@ -183,6 +185,20 @@ final class EngineBuilderTests: XCTestCase { self.waitForExpectations(timeout: 0.01) } + func testAddingNativeFiltersToConfigurationWhenRunningEnvoy() throws { + let expectation = self.expectation(description: "Run called with expected data") + MockEnvoyEngine.onRunWithConfig = { config, _ in + XCTAssertEqual(1, config.nativeFilterChain.count) + expectation.fulfill() + } + + _ = try EngineBuilder() + .addEngineType(MockEnvoyEngine.self) + .addNativeFilter(name: "test_name", typedConfig: "config") + .build() + self.waitForExpectations(timeout: 0.01) + } + func testResolvesYAMLWithIndividuallySetValues() throws { let filterFactory = EnvoyHTTPFilterFactory(filterName: "TestFilter", factory: TestFilter.init) let config = EnvoyConfiguration(statsDomain: "stats.envoyproxy.io", @@ -190,11 +206,15 @@ final class EngineBuilderTests: XCTestCase { dnsRefreshSeconds: 300, dnsFailureRefreshSecondsBase: 400, dnsFailureRefreshSecondsMax: 500, - filterChain: [filterFactory], statsFlushSeconds: 600, appVersion: "v1.2.3", appId: "com.envoymobile.ios", - virtualClusters: "[test]") + virtualClusters: "[test]", + nativeFilterChain: + [EnvoyNativeFilterConfig(name: "filter_name", + typedConfig: "test_config"), + ], + platformFilterChain: [filterFactory]) let resolvedYAML = try XCTUnwrap(config.resolveTemplate(kMockTemplate)) XCTAssertTrue(resolvedYAML.contains("stats_domain: stats.envoyproxy.io")) XCTAssertTrue(resolvedYAML.contains("connect_timeout: 200s")) @@ -207,6 +227,8 @@ final class EngineBuilderTests: XCTestCase { XCTAssertTrue(resolvedYAML.contains("app_version: v1.2.3")) XCTAssertTrue(resolvedYAML.contains("app_id: com.envoymobile.ios")) XCTAssertTrue(resolvedYAML.contains("virtual_clusters: [test]")) + XCTAssertTrue(resolvedYAML.contains("name: filter_name")) + XCTAssertTrue(resolvedYAML.contains("typed_config: test_config")) } func testReturnsNilWhenUnresolvedValueInTemplate() { @@ -215,11 +237,12 @@ final class EngineBuilderTests: XCTestCase { dnsRefreshSeconds: 300, dnsFailureRefreshSecondsBase: 400, dnsFailureRefreshSecondsMax: 500, - filterChain: [], statsFlushSeconds: 600, appVersion: "v1.2.3", appId: "com.envoymobile.ios", - virtualClusters: "[test]") + virtualClusters: "[test]", + nativeFilterChain: [], + platformFilterChain: []) XCTAssertNil(config.resolveTemplate("{{ missing }}")) } } diff --git a/mobile/library/swift/test/HttpBridgeTests/CancelStreamTest.swift b/mobile/library/swift/test/HttpBridgeTests/CancelStreamTest.swift index ebfb1c6c0de7..64ddc5a52449 100644 --- a/mobile/library/swift/test/HttpBridgeTests/CancelStreamTest.swift +++ b/mobile/library/swift/test/HttpBridgeTests/CancelStreamTest.swift @@ -40,7 +40,7 @@ final class CancelStreamTests: XCTestCase { let expectation = self.expectation(description: "Run called with expected cancellation") let client = try EngineBuilder(yaml: config) .addLogLevel(.debug) - .addFilter(factory: DemoFilter.init) + .addPlatformFilter(factory: DemoFilter.init) .build() .streamClient() diff --git a/mobile/library/swift/test/HttpBridgeTests/ReceiveDataTest.swift b/mobile/library/swift/test/HttpBridgeTests/ReceiveDataTest.swift index 80df0784d126..3dc10fd6d1a2 100644 --- a/mobile/library/swift/test/HttpBridgeTests/ReceiveDataTest.swift +++ b/mobile/library/swift/test/HttpBridgeTests/ReceiveDataTest.swift @@ -51,7 +51,7 @@ final class ReceiveDataTests: XCTestCase { """ let client = try EngineBuilder(yaml: config) .addLogLevel(.debug) - .addFilter(factory: DemoFilter.init) + .addPlatformFilter(factory: DemoFilter.init) .build() .streamClient() diff --git a/mobile/library/swift/test/HttpBridgeTests/ReceiveErrorTest.swift b/mobile/library/swift/test/HttpBridgeTests/ReceiveErrorTest.swift index 43559bc28e67..970a928663b9 100644 --- a/mobile/library/swift/test/HttpBridgeTests/ReceiveErrorTest.swift +++ b/mobile/library/swift/test/HttpBridgeTests/ReceiveErrorTest.swift @@ -49,7 +49,7 @@ final class ReceiveErrorTests: XCTestCase { let expectation = self.expectation(description: "Run called with expected error") let client = try EngineBuilder(yaml: config) .addLogLevel(.debug) - .addFilter(factory: DemoFilter.init) + .addPlatformFilter(factory: DemoFilter.init) .build() .streamClient() diff --git a/mobile/library/swift/test/HttpBridgeTests/SendDataTest.swift b/mobile/library/swift/test/HttpBridgeTests/SendDataTest.swift index 455716238eb9..78c9fc0a316f 100644 --- a/mobile/library/swift/test/HttpBridgeTests/SendDataTest.swift +++ b/mobile/library/swift/test/HttpBridgeTests/SendDataTest.swift @@ -53,7 +53,7 @@ final class SendDataTests: XCTestCase { let expectation = self.expectation(description: "Run called with expected http status") let client = try EngineBuilder(yaml: config) .addLogLevel(.debug) - .addFilter(factory: DemoFilter.init) + .addPlatformFilter(factory: DemoFilter.init) .build() .streamClient() diff --git a/mobile/library/swift/test/HttpBridgeTests/SendHeadersTest.swift b/mobile/library/swift/test/HttpBridgeTests/SendHeadersTest.swift index c0ae45f6d2dd..41e562a1261a 100644 --- a/mobile/library/swift/test/HttpBridgeTests/SendHeadersTest.swift +++ b/mobile/library/swift/test/HttpBridgeTests/SendHeadersTest.swift @@ -50,7 +50,7 @@ final class SendHeadersTests: XCTestCase { let expectation = self.expectation(description: "Run called with expected http status") let client = try EngineBuilder(yaml: config) .addLogLevel(.debug) - .addFilter(factory: DemoFilter.init) + .addPlatformFilter(factory: DemoFilter.init) .build() .streamClient() diff --git a/mobile/library/swift/test/HttpBridgeTests/SendTrailersTest.swift b/mobile/library/swift/test/HttpBridgeTests/SendTrailersTest.swift index 78ae29163f65..d08f701fb025 100644 --- a/mobile/library/swift/test/HttpBridgeTests/SendTrailersTest.swift +++ b/mobile/library/swift/test/HttpBridgeTests/SendTrailersTest.swift @@ -54,7 +54,7 @@ final class SendTrailersTests: XCTestCase { let expectation = self.expectation(description: "Run called with expected http status") let client = try EngineBuilder(yaml: config) .addLogLevel(.debug) - .addFilter(factory: DemoFilter.init) + .addPlatformFilter(factory: DemoFilter.init) .build() .streamClient()