Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Bugfixes:

Features:

- android: add support for registering a platform KV store (:issue: `#2134 <2134>`)
- Android & iOS: add support for registering a platform KV store (:issue: `#2134 <2134>`) (:issue: `#2335 <2335>`)
- api: add option to extend the keepalive timeout when any frame is received on the owning HTTP/2 connection. (:issue:`#2229 <2229>`)
- api: add option to control whether Envoy should drain connections after a soft DNS refresh completes. (:issue:`#2225 <2225>`, :issue:`#2242 <2242>`)
- configuration: enable h2 ping by default. (:issue: `#2270 <2270>`)
Expand Down
6 changes: 5 additions & 1 deletion library/objective-c/EnvoyConfiguration.m
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ - (instancetype)initWithAdminInterfaceEnabled:(BOOL)adminInterfaceEnabled
(NSArray<EnvoyHTTPFilterFactory *> *)httpPlatformFilterFactories
stringAccessors:
(NSDictionary<NSString *, EnvoyStringAccessor *> *)
stringAccessors {
stringAccessors
keyValueStores:
(NSDictionary<NSString *, id<EnvoyKeyValueStore>> *)
keyValueStores {
self = [super init];
if (!self) {
return nil;
Expand Down Expand Up @@ -73,6 +76,7 @@ - (instancetype)initWithAdminInterfaceEnabled:(BOOL)adminInterfaceEnabled
self.nativeFilterChain = nativeFilterChain;
self.httpPlatformFilterFactories = httpPlatformFilterFactories;
self.stringAccessors = stringAccessors;
self.keyValueStores = keyValueStores;
return self;
}

Expand Down
21 changes: 20 additions & 1 deletion library/objective-c/EnvoyEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,21 @@ extern const int kEnvoyFilterResumeStatusResumeIteration;

@end

#pragma mark - EnvoyKeyValueStore

@protocol EnvoyKeyValueStore

/// Read a value from the key value store implementation.
- (NSString *_Nullable)readValueForKey:(NSString *)key;

/// Save a value to the key value store implementation.
- (void)saveValue:(NSString *)value toKey:(NSString *)key;

/// Remove a value from the key value store implementation.
- (void)removeKey:(NSString *)key;

@end

#pragma mark - EnvoyNativeFilterConfig

@interface EnvoyNativeFilterConfig : NSObject
Expand Down Expand Up @@ -360,6 +375,7 @@ extern const int kEnvoyFilterResumeStatusResumeIteration;
@property (nonatomic, strong) NSArray<EnvoyNativeFilterConfig *> *nativeFilterChain;
@property (nonatomic, strong) NSArray<EnvoyHTTPFilterFactory *> *httpPlatformFilterFactories;
@property (nonatomic, strong) NSDictionary<NSString *, EnvoyStringAccessor *> *stringAccessors;
@property (nonatomic, strong) NSDictionary<NSString *, id<EnvoyKeyValueStore>> *keyValueStores;

/**
Create a new instance of the configuration.
Expand Down Expand Up @@ -397,7 +413,10 @@ extern const int kEnvoyFilterResumeStatusResumeIteration;
(NSArray<EnvoyHTTPFilterFactory *> *)httpPlatformFilterFactories
stringAccessors:
(NSDictionary<NSString *, EnvoyStringAccessor *> *)
stringAccessors;
stringAccessors
keyValueStores:
(NSDictionary<NSString *, id<EnvoyKeyValueStore>> *)
keyValueStores;

/**
Resolves the provided configuration template using properties on this configuration.
Expand Down
51 changes: 51 additions & 0 deletions library/objective-c/EnvoyEngineImpl.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#import "library/common/main_interface.h"
#import "library/common/types/c_types.h"
#import "library/common/extensions/key_value/platform/c_types.h"

#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
Expand Down Expand Up @@ -394,6 +395,46 @@ static envoy_data ios_get_string(const void *context) {
return toManagedNativeString(accessor.getEnvoyString());
}

static envoy_data ios_kv_store_read(envoy_data native_key, const void *context) {
// This code block runs inside the Envoy event loop. Therefore, an explicit autoreleasepool block
// is necessary to act as a breaker for any Objective-C allocation that happens.
@autoreleasepool {
id<EnvoyKeyValueStore> keyValueStore = (__bridge id<EnvoyKeyValueStore>)context;
NSString *key = [[NSString alloc] initWithBytes:native_key.bytes
length:native_key.length
encoding:NSUTF8StringEncoding];
NSString *value = [keyValueStore readValueForKey:key];
return value != nil ? toManagedNativeString(value) : envoy_nodata;
}
}

static void ios_kv_store_save(envoy_data native_key, envoy_data native_value, const void *context) {
// This code block runs inside the Envoy event loop. Therefore, an explicit autoreleasepool block
// is necessary to act as a breaker for any Objective-C allocation that happens.
@autoreleasepool {
id<EnvoyKeyValueStore> keyValueStore = (__bridge id<EnvoyKeyValueStore>)context;
NSString *key = [[NSString alloc] initWithBytes:native_key.bytes
length:native_key.length
encoding:NSUTF8StringEncoding];
NSString *value = [[NSString alloc] initWithBytes:native_key.bytes
length:native_key.length
encoding:NSUTF8StringEncoding];
[keyValueStore saveValue:value toKey:key];
}
}

static void ios_kv_store_remove(envoy_data native_key, const void *context) {
// This code block runs inside the Envoy event loop. Therefore, an explicit autoreleasepool block
// is necessary to act as a breaker for any Objective-C allocation that happens.
@autoreleasepool {
id<EnvoyKeyValueStore> keyValueStore = (__bridge id<EnvoyKeyValueStore>)context;
NSString *key = [[NSString alloc] initWithBytes:native_key.bytes
length:native_key.length
encoding:NSUTF8StringEncoding];
[keyValueStore removeKey:key];
}
}

static void ios_track_event(envoy_map map, const void *context) {
// This code block runs inside the Envoy event loop. Therefore, an explicit autoreleasepool block
// is necessary to act as a breaker for any Objective-C allocation that happens.
Expand Down Expand Up @@ -492,6 +533,16 @@ - (int)registerStringAccessor:(NSString *)name accessor:(EnvoyStringAccessor *)a
return register_platform_api(name.UTF8String, accessorStruct);
}

- (int)registerKeyValueStore:(NSString *)name keyValueStore:(id<EnvoyKeyValueStore>)keyValueStore {
envoy_kv_store *api = safe_malloc(sizeof(envoy_kv_store));
api->save = ios_kv_store_save;
api->read = ios_kv_store_read;
api->remove = ios_kv_store_remove;
api->context = CFBridgingRetain(keyValueStore);

return register_platform_api(name.UTF8String, api);
}

- (int)runWithConfig:(EnvoyConfiguration *)config logLevel:(NSString *)logLevel {
NSString *templateYAML = [[NSString alloc] initWithUTF8String:config_template];
return [self runWithTemplate:templateYAML config:config logLevel:logLevel];
Expand Down
1 change: 1 addition & 0 deletions library/swift/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ swift_library(
"FinalStreamIntel.swift",
"Headers.swift",
"HeadersBuilder.swift",
"KeyValueStore.swift",
"LogLevel.swift",
"PulseClient.swift",
"PulseClientImpl.swift",
Expand Down
18 changes: 16 additions & 2 deletions library/swift/EngineBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ open class EngineBuilder: NSObject {
private var nativeFilterChain: [EnvoyNativeFilterConfig] = []
private var platformFilterChain: [EnvoyHTTPFilterFactory] = []
private var stringAccessors: [String: EnvoyStringAccessor] = [:]
private var keyValueStores: [String: EnvoyKeyValueStore] = [:]
private var directResponses: [DirectResponse] = []

// MARK: - Public
Expand Down Expand Up @@ -348,13 +349,25 @@ open class EngineBuilder: NSObject {
/// - parameter name: the name of the accessor.
/// - parameter accessor: lambda to access a string from the platform layer.
///
/// - returns this builder.
/// - returns: This builder.
@discardableResult
public func addStringAccessor(name: String, accessor: @escaping () -> String) -> Self {
self.stringAccessors[name] = EnvoyStringAccessor(block: accessor)
return self
}

/// Register a key-value store implementation for internal use.
///
/// - parameter name: the name of the KV store.
/// - parameter keyValueStore: the KV store implementation.
///
/// - returns: This builder.
@discardableResult
public func addKeyValueStore(name: String, keyValueStore: KeyValueStore) -> Self {
self.keyValueStores[name] = KeyValueStoreImpl(implementation: keyValueStore)
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.
Expand Down Expand Up @@ -482,7 +495,8 @@ open class EngineBuilder: NSObject {
.joined(separator: "\n"),
nativeFilterChain: self.nativeFilterChain,
platformFilterChain: self.platformFilterChain,
stringAccessors: self.stringAccessors
stringAccessors: self.stringAccessors,
keyValueStores: self.keyValueStores
)

switch self.base {
Expand Down
36 changes: 36 additions & 0 deletions library/swift/KeyValueStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
@_implementationOnly import EnvoyEngine
import Foundation

/// `KeyValueStore` is an interface that may be implemented to provide access to an arbitrary
/// key-value store implementation that may be made accessible to native Envoy Mobile code.
public protocol KeyValueStore {
/// Read a value from the key value store implementation.
func readValue(forKey key: String) -> String?

/// Save a value to the key value store implementation.
func saveValue(_ value: String, toKey key: String)

/// Remove a value from the key value store implementation.
func removeKey(_ key: String)
}

/// KeyValueStoreImpl is an internal type used for mapping calls from the common library layer.
internal class KeyValueStoreImpl: EnvoyKeyValueStore {
internal let implementation: KeyValueStore

init(implementation: KeyValueStore) {
self.implementation = implementation
}

func readValue(forKey key: String) -> String? {
return implementation.readValue(forKey: key)
}

func saveValue(_ value: String, toKey key: String) {
implementation.saveValue(value, toKey: key)
}

func removeKey(_ key: String) {
implementation.removeKey(key)
}
}
45 changes: 42 additions & 3 deletions test/swift/EngineBuilderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,42 @@ final class EngineBuilderTests: XCTestCase {
self.waitForExpectations(timeout: 0.01)
}

func testAddingKeyValueStoreToConfigurationWhenRunningEnvoy() {
let expectation = self.expectation(description: "Run called with expected data")
MockEnvoyEngine.onRunWithConfig = { config, _ in
XCTAssertEqual("bar", config.keyValueStores["name"]?.readValue(forKey: "foo"))
expectation.fulfill()
}

let testStore: KeyValueStore = {
class TestStore: KeyValueStore {
private var dict: [String: String] = [:]

func readValue(forKey key: String) -> String? {
return dict[key]
}

func saveValue(_ value: String, toKey key: String) {
dict[key] = value
}

func removeKey(_ key: String) {
dict[key] = nil
}
}

return TestStore()
}()

testStore.saveValue("bar", toKey: "foo")

_ = EngineBuilder()
.addEngineType(MockEnvoyEngine.self)
.addKeyValueStore(name: "name", keyValueStore: testStore)
.build()
self.waitForExpectations(timeout: 0.01)
}

func testResolvesYAMLWithIndividuallySetValues() throws {
let config = EnvoyConfiguration(
adminInterfaceEnabled: false,
Expand Down Expand Up @@ -452,7 +488,8 @@ final class EngineBuilderTests: XCTestCase {
platformFilterChain: [
EnvoyHTTPFilterFactory(filterName: "TestFilter", factory: TestFilter.init),
],
stringAccessors: [:]
stringAccessors: [:],
keyValueStores: [:]
)
let resolvedYAML = try XCTUnwrap(config.resolveTemplate(kMockTemplate))
XCTAssertTrue(resolvedYAML.contains("&connect_timeout 200s"))
Expand Down Expand Up @@ -532,7 +569,8 @@ final class EngineBuilderTests: XCTestCase {
platformFilterChain: [
EnvoyHTTPFilterFactory(filterName: "TestFilter", factory: TestFilter.init),
],
stringAccessors: [:]
stringAccessors: [:],
keyValueStores: [:]
)
let resolvedYAML = try XCTUnwrap(config.resolveTemplate(kMockTemplate))
XCTAssertTrue(resolvedYAML.contains("&dns_lookup_family V4_PREFERRED"))
Expand Down Expand Up @@ -573,7 +611,8 @@ final class EngineBuilderTests: XCTestCase {
directResponses: "",
nativeFilterChain: [],
platformFilterChain: [],
stringAccessors: [:]
stringAccessors: [:],
keyValueStores: [:]
)
XCTAssertNil(config.resolveTemplate("{{ missing }}"))
}
Expand Down