Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Features:
- android: enable the filtering of unroutable families by default. (:issues: `#2267 <2267>`)
- instrumentation: add timers and warnings to platform-provided callbacks (:issue: `#2300 <2300>`)
- iOS: add support for integrating Envoy Mobile via the Swift Package Manager
- iOS: add support for registering a platform KV store (:issue: `#2334 <2334>`)
Comment thread
goaway marked this conversation as resolved.
Outdated
- iOS: A documentation archive is now included in the GitHub release artifact (:issue: `#2335 <2335>`)
Comment thread
goaway marked this conversation as resolved.

0.4.6 (April 26, 2022)
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 iplementation.
Comment thread
goaway marked this conversation as resolved.
Outdated
- (NSString *_Nullable)readValueForKey:(NSString *)key;

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

/// Save a value to the key value store implementation.
Comment thread
goaway marked this conversation as resolved.
Outdated
- (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
16 changes: 15 additions & 1 deletion 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 @@ -355,6 +356,18 @@ open class EngineBuilder: NSObject {
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.
Comment thread
goaway marked this conversation as resolved.
Outdated
@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
31 changes: 31 additions & 0 deletions library/swift/KeyValueStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
@_implementationOnly import EnvoyEngine
import Foundation

/// `KeyValueStore` is an interface that may be implemented to provide access to an arbitrary
Comment thread
goaway marked this conversation as resolved.
/// key-value store implementation that may be made accessible to native Envoy Mobile code.

public protocol KeyValueStore {
func readValue(forKey key: String) -> String?
func saveValue(_ value: String, toKey key: String)
func removeKey(_ key: String)
}

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