From 6d70296c090eb111f89804371cf538c00820f86b Mon Sep 17 00:00:00 2001 From: Jose Ulises Nino Rivera Date: Wed, 10 Feb 2021 16:27:01 -0800 Subject: [PATCH] ios: add string accessor API (#1202) Description: iOS parallel to #1188 Risk Level: low Testing: example app. Pending test with assertion filter. Docs Changes: pending full API docs Signed-off-by: Jose Nino Signed-off-by: JP Simard --- .../swift/hello_world/ViewController.swift | 1 + mobile/library/common/api/c_types.h | 4 +-- mobile/library/common/api/external.cc | 2 +- mobile/library/common/jni/jni_interface.cc | 4 +-- mobile/library/objective-c/BUILD | 1 + .../library/objective-c/EnvoyConfiguration.m | 8 +++--- mobile/library/objective-c/EnvoyEngine.h | 16 ++++++++++-- mobile/library/objective-c/EnvoyEngineImpl.m | 25 +++++++++++++++++++ .../library/objective-c/EnvoyStringAccessor.m | 15 +++++++++++ mobile/library/swift/src/EngineBuilder.swift | 19 ++++++++++++-- .../swift/test/EngineBuilderTests.swift | 20 +++++++++++++-- 11 files changed, 101 insertions(+), 14 deletions(-) create mode 100644 mobile/library/objective-c/EnvoyStringAccessor.m diff --git a/mobile/examples/swift/hello_world/ViewController.swift b/mobile/examples/swift/hello_world/ViewController.swift index 439c967cad99..6c5b640bc83c 100644 --- a/mobile/examples/swift/hello_world/ViewController.swift +++ b/mobile/examples/swift/hello_world/ViewController.swift @@ -26,6 +26,7 @@ final class ViewController: UITableViewController { // 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") } + .addStringAccessor(name: "string_accessor", accessor: { return "DemoStringAccessor" }) .build() self.streamClient = engine.streamClient() self.pulseClient = engine.pulseClient() diff --git a/mobile/library/common/api/c_types.h b/mobile/library/common/api/c_types.h index 1b6d8ff40fb8..bf52671dd605 100644 --- a/mobile/library/common/api/c_types.h +++ b/mobile/library/common/api/c_types.h @@ -8,7 +8,7 @@ extern "C" { // function pointers #endif -typedef envoy_data (*envoy_get_string_f)(void* context); +typedef envoy_data (*envoy_get_string_f)(const void* context); #ifdef __cplusplus } // function pointers @@ -21,5 +21,5 @@ typedef envoy_data (*envoy_get_string_f)(void* context); // types. typedef struct { envoy_get_string_f get_string; - void* context; + const void* context; } envoy_string_accessor; diff --git a/mobile/library/common/api/external.cc b/mobile/library/common/api/external.cc index 1f5ec2a2f760..0ba920d1b5f1 100644 --- a/mobile/library/common/api/external.cc +++ b/mobile/library/common/api/external.cc @@ -21,7 +21,7 @@ void registerApi(std::string name, void* api) { registry_[name] = api; } // before any reads occur. void* retrieveApi(std::string name) { void* api = registry_[name]; - ASSERT(api); + ASSERT(api, fmt::format("{} not registered", name)); return api; } diff --git a/mobile/library/common/jni/jni_interface.cc b/mobile/library/common/jni/jni_interface.cc index 81b0404f855a..42c1e8c446fd 100644 --- a/mobile/library/common/jni/jni_interface.cc +++ b/mobile/library/common/jni/jni_interface.cc @@ -654,9 +654,9 @@ static const void* jvm_http_filter_init(const void* context) { // EnvoyStringAccessor -static envoy_data jvm_get_string(void* context) { +static envoy_data jvm_get_string(const void* context) { JNIEnv* env = get_env(); - jobject j_context = static_cast(context); + jobject j_context = static_cast(const_cast(context)); jclass jcls_JvmStringAccessorContext = env->GetObjectClass(j_context); jmethodID jmid_getString = env->GetMethodID(jcls_JvmStringAccessorContext, "getString", "()Ljava/nio/ByteBuffer;"); diff --git a/mobile/library/objective-c/BUILD b/mobile/library/objective-c/BUILD index f3a6907768aa..f5a2a85b1488 100644 --- a/mobile/library/objective-c/BUILD +++ b/mobile/library/objective-c/BUILD @@ -19,6 +19,7 @@ objc_library( "EnvoyHTTPStreamImpl.m", "EnvoyNativeFilterConfig.m", "EnvoyNetworkMonitor.m", + "EnvoyStringAccessor.m", ], hdrs = [ "EnvoyEngine.h", diff --git a/mobile/library/objective-c/EnvoyConfiguration.m b/mobile/library/objective-c/EnvoyConfiguration.m index 8cef88e328cb..8ad8b4e96226 100644 --- a/mobile/library/objective-c/EnvoyConfiguration.m +++ b/mobile/library/objective-c/EnvoyConfiguration.m @@ -14,8 +14,9 @@ - (instancetype)initWithStatsDomain:(NSString *)statsDomain appId:(NSString *)appId virtualClusters:(NSString *)virtualClusters nativeFilterChain:(NSArray *)nativeFilterChain - platformFilterChain: - (NSArray *)httpPlatformFilterFactories { + platformFilterChain:(NSArray *)httpPlatformFilterFactories + stringAccessors: + (NSDictionary *)stringAccessors { self = [super init]; if (!self) { return nil; @@ -26,12 +27,13 @@ - (instancetype)initWithStatsDomain:(NSString *)statsDomain self.dnsRefreshSeconds = dnsRefreshSeconds; self.dnsFailureRefreshSecondsBase = dnsFailureRefreshSecondsBase; self.dnsFailureRefreshSecondsMax = dnsFailureRefreshSecondsMax; - self.httpPlatformFilterFactories = httpPlatformFilterFactories; self.statsFlushSeconds = statsFlushSeconds; self.appVersion = appVersion; self.appId = appId; self.virtualClusters = virtualClusters; self.nativeFilterChain = nativeFilterChain; + self.httpPlatformFilterFactories = httpPlatformFilterFactories; + self.stringAccessors = stringAccessors; return self; } diff --git a/mobile/library/objective-c/EnvoyEngine.h b/mobile/library/objective-c/EnvoyEngine.h index 70f473256efe..3b56d3755318 100644 --- a/mobile/library/objective-c/EnvoyEngine.h +++ b/mobile/library/objective-c/EnvoyEngine.h @@ -225,6 +225,16 @@ extern const int kEnvoyFilterResumeStatusResumeIteration; @end +#pragma mark - EnvoyStringAccessor + +@interface EnvoyStringAccessor : NSObject + +@property (nonatomic, copy) NSString * (^getEnvoyString)(); + +- (instancetype)initWithBlock:(NSString * (^)())block; + +@end + #pragma mark - EnvoyNativeFilterConfig @interface EnvoyNativeFilterConfig : NSObject @@ -252,6 +262,7 @@ extern const int kEnvoyFilterResumeStatusResumeIteration; @property (nonatomic, strong) NSString *virtualClusters; @property (nonatomic, strong) NSArray *nativeFilterChain; @property (nonatomic, strong) NSArray *httpPlatformFilterFactories; +@property (nonatomic, strong) NSDictionary *stringAccessors; /** Create a new instance of the configuration. @@ -266,8 +277,9 @@ extern const int kEnvoyFilterResumeStatusResumeIteration; appId:(NSString *)appId virtualClusters:(NSString *)virtualClusters nativeFilterChain:(NSArray *)nativeFilterChain - platformFilterChain: - (NSArray *)httpPlatformFilterFactories; + platformFilterChain:(NSArray *)httpPlatformFilterFactories + stringAccessors: + (NSDictionary *)stringAccessors; /** 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 9b44c051dadc..0b9bcf8ba1c6 100644 --- a/mobile/library/objective-c/EnvoyEngineImpl.m +++ b/mobile/library/objective-c/EnvoyEngineImpl.m @@ -2,6 +2,8 @@ #import "library/objective-c/EnvoyBridgeUtility.h" #import "library/objective-c/EnvoyHTTPFilterCallbacksImpl.h" +#include "library/common/api/c_types.h" + #import "library/common/main_interface.h" #import "library/common/types/c_types.h" @@ -286,6 +288,11 @@ static void ios_http_filter_release(const void *context) { return; } +static envoy_data ios_get_string(const void *context) { + EnvoyStringAccessor *accessor = (__bridge EnvoyStringAccessor *)context; + return toManagedNativeString(accessor.getEnvoyString()); +} + @implementation EnvoyEngineImpl { envoy_engine_t _engineHandle; } @@ -330,6 +337,16 @@ - (int)registerFilterFactory:(EnvoyHTTPFilterFactory *)filterFactory { return kEnvoySuccess; } +- (int)registerStringAccessor:(NSString *)name accessor:(EnvoyStringAccessor *)accessor { + // TODO(goaway): Everything here leaks, but it's all be tied to the life of the engine. + // This will need to be updated for https://github.com/lyft/envoy-mobile/issues/332 + envoy_string_accessor *accessorStruct = safe_malloc(sizeof(envoy_string_accessor)); + accessorStruct->get_string = ios_get_string; + accessorStruct->context = CFBridgingRetain(accessor); + + return register_platform_api(name.UTF8String, accessorStruct); +} + - (int)runWithConfig:(EnvoyConfiguration *)config logLevel:(NSString *)logLevel onEngineRunning:(nullable void (^)())onEngineRunning { @@ -343,6 +360,10 @@ - (int)runWithConfig:(EnvoyConfiguration *)config [self registerFilterFactory:filterFactory]; } + for (NSString *name in config.stringAccessors) { + [self registerStringAccessor:name accessor:config.stringAccessors[name]]; + } + return [self runWithConfigYAML:resolvedYAML logLevel:logLevel onEngineRunning:onEngineRunning]; } @@ -359,6 +380,10 @@ - (int)runWithTemplate:(NSString *)yaml [self registerFilterFactory:filterFactory]; } + for (NSString *name in config.stringAccessors) { + [self registerStringAccessor:name accessor:config.stringAccessors[name]]; + } + return [self runWithConfigYAML:resolvedYAML logLevel:logLevel onEngineRunning:onEngineRunning]; } diff --git a/mobile/library/objective-c/EnvoyStringAccessor.m b/mobile/library/objective-c/EnvoyStringAccessor.m new file mode 100644 index 000000000000..c59930d6731b --- /dev/null +++ b/mobile/library/objective-c/EnvoyStringAccessor.m @@ -0,0 +1,15 @@ +#import "library/objective-c/EnvoyEngine.h" + +@implementation EnvoyStringAccessor + +- (instancetype)initWithBlock:(NSString * (^)())block { + self = [super init]; + if (!self) { + return nil; + } + + self.getEnvoyString = block; + return self; +} + +@end diff --git a/mobile/library/swift/src/EngineBuilder.swift b/mobile/library/swift/src/EngineBuilder.swift index 9c81eab08de4..7434f983f188 100644 --- a/mobile/library/swift/src/EngineBuilder.swift +++ b/mobile/library/swift/src/EngineBuilder.swift @@ -21,10 +21,11 @@ public final class EngineBuilder: NSObject { private var statsFlushSeconds: UInt32 = 60 private var appVersion: String = "unspecified" private var appId: String = "unspecified" - private var platformFilterChain: [EnvoyHTTPFilterFactory] = [] private var virtualClusters: String = "[]" private var onEngineRunning: (() -> Void)? private var nativeFilterChain: [EnvoyNativeFilterConfig] = [] + private var platformFilterChain: [EnvoyHTTPFilterFactory] = [] + private var stringAccessors: [String: EnvoyStringAccessor] = [:] // MARK: - Public @@ -142,6 +143,19 @@ public final class EngineBuilder: NSObject { return self } + /// Add a string accessor to this Envoy Client. + /// + /// - parameter name: the name of the accessor. + /// - parameter accessor: lambda to access a string from the platform layer. + /// + /// - returns this builder. + @discardableResult + public func addStringAccessor(name: String, + accessor: @escaping () -> String) -> EngineBuilder { + self.stringAccessors[name] = EnvoyStringAccessor(block: accessor) + return self + } + /// Set a closure to be called when the engine finishes its async startup and begins running. /// /// - parameter closure: The closure to be called. @@ -202,7 +216,8 @@ public final class EngineBuilder: NSObject { appId: self.appId, virtualClusters: self.virtualClusters, nativeFilterChain: self.nativeFilterChain, - platformFilterChain: self.platformFilterChain) + platformFilterChain: self.platformFilterChain, + stringAccessors: self.stringAccessors) switch self.base { case .custom(let yaml): diff --git a/mobile/library/swift/test/EngineBuilderTests.swift b/mobile/library/swift/test/EngineBuilderTests.swift index 8eeb163c1ec7..8511bf00f067 100644 --- a/mobile/library/swift/test/EngineBuilderTests.swift +++ b/mobile/library/swift/test/EngineBuilderTests.swift @@ -199,6 +199,20 @@ final class EngineBuilderTests: XCTestCase { self.waitForExpectations(timeout: 0.01) } + func testAddingStringAccessorToConfigurationWhenRunningEnvoy() throws { + let expectation = self.expectation(description: "Run called with expected data") + MockEnvoyEngine.onRunWithConfig = { config, _ in + XCTAssertEqual("hello", config.stringAccessors["name"]?.getEnvoyString()) + expectation.fulfill() + } + + _ = try EngineBuilder() + .addEngineType(MockEnvoyEngine.self) + .addStringAccessor(name: "name", accessor: { "hello" }) + .build() + self.waitForExpectations(timeout: 0.01) + } + func testResolvesYAMLWithIndividuallySetValues() throws { let filterFactory = EnvoyHTTPFilterFactory(filterName: "TestFilter", factory: TestFilter.init) let config = EnvoyConfiguration(statsDomain: "stats.envoyproxy.io", @@ -214,7 +228,8 @@ final class EngineBuilderTests: XCTestCase { [EnvoyNativeFilterConfig(name: "filter_name", typedConfig: "test_config"), ], - platformFilterChain: [filterFactory]) + platformFilterChain: [filterFactory], + stringAccessors: [:]) let resolvedYAML = try XCTUnwrap(config.resolveTemplate(kMockTemplate)) XCTAssertTrue(resolvedYAML.contains("stats_domain: stats.envoyproxy.io")) XCTAssertTrue(resolvedYAML.contains("connect_timeout: 200s")) @@ -242,7 +257,8 @@ final class EngineBuilderTests: XCTestCase { appId: "com.envoymobile.ios", virtualClusters: "[test]", nativeFilterChain: [], - platformFilterChain: []) + platformFilterChain: [], + stringAccessors: [:]) XCTAssertNil(config.resolveTemplate("{{ missing }}")) } }