diff --git a/Package.resolved b/Package.resolved index cc9085cf..7914bd79 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-crypto", "state" : { - "revision" : "33a20e650c33f6d72d822d558333f2085effa3dc", - "version" : "2.5.0" + "revision" : "60f13f60c4d093691934dc6cfdf5f508ada1f894", + "version" : "2.6.0" } } ], diff --git a/Sources/Compute/Cache.swift b/Sources/Compute/Cache.swift new file mode 100644 index 00000000..ed627553 --- /dev/null +++ b/Sources/Compute/Cache.swift @@ -0,0 +1,84 @@ +// +// Cache.swift +// +// +// Created by Andrew Barba on 9/13/23. +// + +public struct Cache: Sendable { + + public static func getOrSet(_ key: String, _ handler: () async throws -> (FetchResponse, CachePolicy)) async throws -> Entry { + let trx = try await Fastly.Cache.getOrSet(key) { + let (res, cachePolicy) = try await handler() + let length = res.headers[.contentLength].flatMap(Int.init) + return await (.body(res.body.body, length: length), cachePolicy) + } + return try .init(trx) + } + + public static func getOrSet(_ key: String, _ handler: () async throws -> ([UInt8], CachePolicy)) async throws -> Entry { + let trx = try await Fastly.Cache.getOrSet(key) { + let (bytes, cachePolicy) = try await handler() + return (.bytes(bytes), cachePolicy) + } + return try .init(trx) + } + + public static func getOrSet(_ key: String, _ handler: () async throws -> (String, CachePolicy)) async throws -> Entry { + let trx = try await Fastly.Cache.getOrSet(key) { + let (text, cachePolicy) = try await handler() + return (.bytes(.init(text.utf8)), cachePolicy) + } + return try .init(trx) + } + + public static func getOrSet(_ key: String, _ handler: () async throws -> (Data, CachePolicy)) async throws -> Entry { + let trx = try await Fastly.Cache.getOrSet(key) { + let (data, cachePolicy) = try await handler() + return (.bytes(data.bytes), cachePolicy) + } + return try .init(trx) + } + + public static func getOrSet(_ key: String, _ handler: () async throws -> ([String: Sendable], CachePolicy)) async throws -> Entry { + let trx = try await Fastly.Cache.getOrSet(key) { + let (json, cachePolicy) = try await handler() + let data = try JSONSerialization.data(withJSONObject: json) + return (.bytes(data.bytes), cachePolicy) + } + return try .init(trx) + } + + public static func getOrSet(_ key: String, _ handler: () async throws -> ([Sendable], CachePolicy)) async throws -> Entry { + let trx = try await Fastly.Cache.getOrSet(key) { + let (json, cachePolicy) = try await handler() + let data = try JSONSerialization.data(withJSONObject: json) + return (.bytes(data.bytes), cachePolicy) + } + return try .init(trx) + } +} + +extension Cache { + + public struct Entry: Sendable { + + public let body: ReadableBody + + public let age: Int + + public let hits: Int + + public let contentLength: Int + + public let state: CacheState + + internal init(_ trx: Fastly.Cache.Transaction) throws { + self.body = try ReadableWasiBody(trx.getBody()) + self.age = try trx.getAge() + self.hits = try trx.getHits() + self.contentLength = try trx.getLength() + self.state = try trx.getState() + } + } +} diff --git a/Sources/Compute/Fastly/FastlyBody.swift b/Sources/Compute/Fastly/FastlyBody.swift index 2aaf5991..e6015007 100644 --- a/Sources/Compute/Fastly/FastlyBody.swift +++ b/Sources/Compute/Fastly/FastlyBody.swift @@ -33,6 +33,10 @@ extension Fastly { try wasi(fastly_http_body__close(handle)) } + public mutating func abandon() throws { + try wasi(fastly_http_body__abandon(handle)) + } + @discardableResult public mutating func write(_ bytes: [UInt8], location: BodyWriteEnd = .back) throws -> Int { defer { used = true } diff --git a/Sources/Compute/Fastly/FastlyCache.swift b/Sources/Compute/Fastly/FastlyCache.swift new file mode 100644 index 00000000..13d08687 --- /dev/null +++ b/Sources/Compute/Fastly/FastlyCache.swift @@ -0,0 +1,144 @@ +// +// FastlyCache.swift +// +// +// Created by Andrew Barba on 9/13/23. +// + +import ComputeRuntime + +extension Fastly { + public struct Cache: Sendable { + + public typealias InsertResult = (data: HandlerData, cachePolicy: CachePolicy) + + public static func getOrSet(_ key: String, _ handler: () async throws -> InsertResult) async throws -> Transaction { + // Open the transaction + let trx = try Transaction.lookup(key) + + // Get current transaction state + let state = try trx.getState() + + // If state is usable then return the body from cache + guard state.contains(.mustInsertOrUpdate) else { + return trx + } + + do { + // If its not usable then begin executing handler with new value + let (data, cachePolicy) = try await handler() + + // Get an instance to the insert handle + var writer = try trx.insertAndStreamBack(cachePolicy: cachePolicy, length: data.length) + + // Append bytes from handler to writeable body + switch data { + case .body(let body, _): + try writer.body.append(body) + case .bytes(let bytes): + try writer.body.write(bytes) + } + + // Finish the body + try writer.body.close() + + return writer.transaction + } catch { + // Cancel the transaction if something went wrong + try trx.cancel() + + // Rethrow original error + throw error + } + } + } +} + +extension Fastly.Cache { + public enum HandlerData { + case body(_ body: Fastly.Body, length: Int? = nil) + case bytes(_ bytes: [UInt8]) + + public var length: Int? { + switch self { + case .body(_, let length): + return length + case .bytes(let bytes): + return bytes.count + } + } + } +} + +extension Fastly.Cache { + public struct Transaction: Sendable { + + internal let handle: WasiHandle + + internal init(_ handle: WasiHandle) { + self.handle = handle + } + + public static func lookup(_ key: String) throws -> Transaction { + var handle: WasiHandle = 0 + let options = CacheLookupOptions.none + var config = CacheLookupConfig() + try wasi(fastly_cache__cache_transaction_lookup(key, key.utf8.count, options.rawValue, &config, &handle)) + return Transaction(handle) + } + + public func insertAndStreamBack(cachePolicy: CachePolicy, length: Int?) throws -> (body: Fastly.Body, transaction: Transaction) { + var bodyHandle: WasiHandle = 0 + var cacheHandle: WasiHandle = 0 + var options: CacheWriteOptions = [] + var config = CacheWriteConfig() + config.max_age_ns = .init(cachePolicy.maxAge) * 1_000_000_000 + if cachePolicy.staleMaxAge > 0 { + options.insert(.staleWhileRevalidateNs) + config.stale_while_revalidate_ns = .init(cachePolicy.staleMaxAge) * 1_000_000_000 + } + if let length { + options.insert(.length) + config.length = .init(length) + } + try wasi(fastly_cache__cache_transaction_insert_and_stream_back(handle, options.rawValue, &config, &bodyHandle, &cacheHandle)) + return (.init(bodyHandle), .init(cacheHandle)) + } + + public func getState() throws -> CacheState { + var value: UInt8 = 0 + try wasi(fastly_cache__cache_get_state(handle, &value)) + return CacheState(rawValue: value) + } + + public func getAge() throws -> Int { + var value: UInt64 = 0 + try wasi(fastly_cache__cache_get_age_ns(handle, &value)) + return .init(value / 1_000_000_000) + } + + public func getLength() throws -> Int { + var value: UInt64 = 0 + try wasi(fastly_cache__cache_get_length(handle, &value)) + return .init(value) + } + + public func getHits() throws -> Int { + var value: UInt64 = 0 + try wasi(fastly_cache__cache_get_hits(handle, &value)) + return .init(value) + } + + public func getBody() throws -> Fastly.Body { + var bodyHandle: WasiHandle = 0 + let options = CacheGetBodyOptions.none + var config = CacheGetBodyConfig() + try wasi(fastly_cache__cache_get_body(handle, options.rawValue, &config, &bodyHandle)) + return .init(bodyHandle) + } + + public func cancel() throws { + try wasi(fastly_cache__cache_transaction_cancel(handle)) + } + } +} diff --git a/Sources/Compute/Fastly/FastlyStubs.swift b/Sources/Compute/Fastly/FastlyStubs.swift index f2d1d475..ae89ef88 100644 --- a/Sources/Compute/Fastly/FastlyStubs.swift +++ b/Sources/Compute/Fastly/FastlyStubs.swift @@ -11,22 +11,7 @@ /// avoid link failure. /// When running with Compute runtime library, they are ignored completely. #if !arch(wasm32) - -/* TYPES */ - -struct DynamicBackendConfig: Sendable { - var host_override: UnsafePointer! = nil - var host_override_len: Int = 0 - var connect_timeout_ms: Int = 0 - var first_byte_timeout_ms: Int = 0 - var between_bytes_timeout_ms: Int = 0 - var ssl_min_version: Int = 0 - var ssl_max_version: Int = 0 - var cert_hostname: UnsafePointer! = nil - var cert_hostname_len: Int = 0 - var sni_hostname: UnsafePointer! = nil - var sni_hostname_len: Int = 0 -} +import ComputeRuntime /* FASTLY_ABI */ @@ -72,6 +57,8 @@ func fastly_http_body__append(_ dest: WasiHandle, _ src: WasiHandle) -> Int32 { func fastly_http_body__close(_ handle: WasiHandle) -> Int32 { fatalError() } +func fastly_http_body__abandon(_ handle: WasiHandle) -> Int32 { fatalError() } + func fastly_http_body__write(_ handle: WasiHandle, _ data: UnsafePointer!, _ data_len: Int, _ body_end: Int32, _ nwritten: UnsafeMutablePointer!) -> Int32 { fatalError() } func fastly_http_body__read(_ handle: WasiHandle, _ data: UnsafeMutablePointer!, _ data_max_len: Int, _ nwritten: UnsafeMutablePointer!) -> Int32 { fatalError() } @@ -170,4 +157,49 @@ func fastly_http_resp__framing_headers_mode_set(_ resp_handle: WasiHandle, _ mod func fastly_http_resp__http_keepalive_mode_set(_ resp_handle: WasiHandle, _ mode: UInt32) -> Int32 { fatalError() } +func fastly_cache__cache_transaction_lookup( + _ cache_key: UnsafePointer!, + _ cache_key_len: Int, + _ options_mask: UInt32, + _ config: UnsafeMutablePointer!, + _ ret: UnsafeMutablePointer! +) -> Int32 { fatalError() } + +func fastly_cache__cache_transaction_insert_and_stream_back( + _ handle: WasiHandle, + _ options_mask: UInt32, + _ config: UnsafeMutablePointer!, + _ ret_body: UnsafeMutablePointer!, + _ ret_cache: UnsafeMutablePointer! +) -> Int32 { fatalError() } + +func fastly_cache__cache_get_state( + _ handle: WasiHandle, + _ ret: UnsafeMutablePointer! +) -> Int32 { fatalError() } + +func fastly_cache__cache_get_age_ns( + _ handle: WasiHandle, + _ ret: UnsafeMutablePointer! +) -> Int32 { fatalError() } + +func fastly_cache__cache_get_length( + _ handle: WasiHandle, + _ ret: UnsafeMutablePointer! +) -> Int32 { fatalError() } + +func fastly_cache__cache_get_hits( + _ handle: WasiHandle, + _ ret: UnsafeMutablePointer! +) -> Int32{ fatalError() } + +func fastly_cache__cache_get_body( + _ handle: WasiHandle, + _ options_mask: UInt32, + _ config: UnsafeMutablePointer!, + _ ret: UnsafeMutablePointer! +) -> Int32 { fatalError() } + +func fastly_cache__cache_transaction_cancel(_ handle: WasiHandle) -> Int32 { fatalError() } + #endif diff --git a/Sources/Compute/Fastly/FastlyTypes.swift b/Sources/Compute/Fastly/FastlyTypes.swift index f6728b3c..24c9bd0d 100644 --- a/Sources/Compute/Fastly/FastlyTypes.swift +++ b/Sources/Compute/Fastly/FastlyTypes.swift @@ -318,29 +318,6 @@ public typealias MultiValueCursor = Int32 public typealias MultiValueCursorResult = Int64 -public struct CacheOverrideTag: OptionSet { - - public let rawValue: UInt32 - - public init(rawValue: UInt32) { - self.rawValue = rawValue - } -} - -extension CacheOverrideTag { - public static let none: CacheOverrideTag = [] - public static let pass = CacheOverrideTag(rawValue: 1 << 0) - public static let ttl = CacheOverrideTag(rawValue: 1 << 1) - public static let swr = CacheOverrideTag(rawValue: 1 << 2) - public static let pci = CacheOverrideTag(rawValue: 1 << 3) -} - -public enum CachePolicy: Sendable { - case origin - case pass - case ttl(_ seconds: Int, staleWhileRevalidate: Int = 0, pciCompliant: Bool = false) -} - public typealias HeaderCount = Int32 public typealias IsDone = Int32 @@ -376,15 +353,6 @@ public enum BodyScanContinuation: Sendable { } public struct BackendConfigOptions: OptionSet, Sendable { - - public let rawValue: UInt32 - - public init(rawValue: UInt32) { - self.rawValue = rawValue - } -} - -extension BackendConfigOptions { public static let reserved = BackendConfigOptions(rawValue: 1 << 0) public static let hostOverride = BackendConfigOptions(rawValue: 1 << 1) public static let connectTimeout = BackendConfigOptions(rawValue: 1 << 2) @@ -397,6 +365,111 @@ extension BackendConfigOptions { public static let caCert = BackendConfigOptions(rawValue: 1 << 9) public static let ciphers = BackendConfigOptions(rawValue: 1 << 10) public static let sniHostname = BackendConfigOptions(rawValue: 1 << 11) + + public let rawValue: UInt32 + public init(rawValue: UInt32) { + self.rawValue = rawValue + } +} + +public struct CacheOverrideTag: OptionSet, Sendable { + public static let pass = CacheOverrideTag(rawValue: 1 << 0) + public static let ttl = CacheOverrideTag(rawValue: 1 << 1) + public static let swr = CacheOverrideTag(rawValue: 1 << 2) + public static let pci = CacheOverrideTag(rawValue: 1 << 3) + + public static let none: CacheOverrideTag = [] + + public let rawValue: UInt32 + public init(rawValue: UInt32) { + self.rawValue = rawValue + } +} + +public struct CacheState: OptionSet, Sendable { + public static let found = CacheState(rawValue: 1 << 0) + public static let usable = CacheState(rawValue: 1 << 1) + public static let stale = CacheState(rawValue: 1 << 2) + public static let mustInsertOrUpdate = CacheState(rawValue: 1 << 3) + + public static let none: CacheState = [] + + public let rawValue: UInt8 + public init(rawValue: UInt8) { + self.rawValue = rawValue + } +} + +public enum CachePolicy: Sendable { + case origin + case pass + case ttl(_ seconds: Int, staleWhileRevalidate: Int = 0, pciCompliant: Bool = false) + + public var maxAge: Int { + switch self { + case .origin: + return 0 + case .pass: + return 0 + case .ttl(let seconds, _, _): + return seconds + } + } + + public var staleMaxAge: Int { + switch self { + case .origin: + return 0 + case .pass: + return 0 + case .ttl(_, let seconds, _): + return seconds + } + } +} + +public struct CacheWriteOptions: OptionSet, Sendable { + public static let reserved = CacheWriteOptions(rawValue: 1 << 0) + public static let requestHeaders = CacheWriteOptions(rawValue: 1 << 1) + public static let varyRule = CacheWriteOptions(rawValue: 1 << 2) + public static let initialAgeNs = CacheWriteOptions(rawValue: 1 << 3) + public static let staleWhileRevalidateNs = CacheWriteOptions(rawValue: 1 << 4) + public static let surrogateKeys = CacheWriteOptions(rawValue: 1 << 5) + public static let length = CacheWriteOptions(rawValue: 1 << 6) + public static let userMetadata = CacheWriteOptions(rawValue: 1 << 7) + public static let sensitiveData = CacheWriteOptions(rawValue: 1 << 8) + + public static let none: CacheWriteOptions = [] + + public let rawValue: UInt32 + public init(rawValue: UInt32) { + self.rawValue = rawValue + } +} + +public struct CacheLookupOptions: OptionSet, Sendable { + public static let reserved = CacheLookupOptions(rawValue: 1 << 0) + public static let requestHeaders = CacheLookupOptions(rawValue: 1 << 1) + + public static let none: CacheLookupOptions = [] + + public let rawValue: UInt32 + public init(rawValue: UInt32) { + self.rawValue = rawValue + } +} + +public struct CacheGetBodyOptions: OptionSet, Sendable { + public static let reserved = CacheLookupOptions(rawValue: 1 << 0) + public static let from = CacheLookupOptions(rawValue: 1 << 1) + public static let to = CacheLookupOptions(rawValue: 1 << 2) + + public static let none: CacheGetBodyOptions = [] + + public let rawValue: UInt32 + public init(rawValue: UInt32) { + self.rawValue = rawValue + } } public let maxHeaderLength = 69000 diff --git a/Sources/Compute/JWT/JWT.swift b/Sources/Compute/JWT/JWT.swift index da0f62fc..9b0bd708 100644 --- a/Sources/Compute/JWT/JWT.swift +++ b/Sources/Compute/JWT/JWT.swift @@ -151,6 +151,7 @@ extension JWT { @discardableResult public func verify( key: String, + using algorithm: Algorithm? = nil, issuer: String? = nil, subject: String? = nil, expiration: Bool = true @@ -159,7 +160,7 @@ extension JWT { let input = token.components(separatedBy: ".").prefix(2).joined(separator: ".") // Ensure the signatures match - try verifySignature(input, signature: signature, key: key, using: algorithm) + try verifySignature(input, signature: signature, key: key, using: algorithm ?? self.algorithm) // Ensure the jwt is not expired if expiration, self.expired == true { diff --git a/Sources/ComputeRuntime/include/ComputeRuntime.h b/Sources/ComputeRuntime/include/ComputeRuntime.h index b210acf0..864243b0 100644 --- a/Sources/ComputeRuntime/include/ComputeRuntime.h +++ b/Sources/ComputeRuntime/include/ComputeRuntime.h @@ -19,24 +19,6 @@ typedef uint32_t WasiHandle; -typedef struct DynamicBackendConfig { - const char* host_override; - size_t host_override_len; - size_t connect_timeout_ms; - size_t first_byte_timeout_ms; - size_t between_bytes_timeout_ms; - size_t ssl_min_version; - size_t ssl_max_version; - const char* cert_hostname; - size_t cert_hostname_len; - const char* ca_cert; - size_t ca_cert_len; - const char* ciphers; - size_t ciphers_len; - const char* sni_hostname; - size_t sni_hostname_len; -} DynamicBackendConfig; - /* FASTLY_ABI */ WASM_IMPORT("fastly_abi", "init") @@ -96,6 +78,9 @@ int fastly_http_body__append(WasiHandle dest, WasiHandle src); WASM_IMPORT("fastly_http_body", "close") int fastly_http_body__close(WasiHandle handle); +WASM_IMPORT("fastly_http_body", "abandon") +int fastly_http_body__abandon(WasiHandle handle); + WASM_IMPORT("fastly_http_body", "write") int fastly_http_body__write(WasiHandle handle, const uint8_t* data, size_t data_len, int body_end, size_t* nwritten); @@ -211,6 +196,24 @@ int fastly_http_req__redirect_to_grip_proxy(const char *backend, size_t backend_ WASM_IMPORT("fastly_http_req", "downstream_tls_ja3_md5") int fastly_http_req__downstream_tls_ja3_md5(uint8_t *value, size_t *nwritten); +typedef struct DynamicBackendConfig { + const char* host_override; + size_t host_override_len; + size_t connect_timeout_ms; + size_t first_byte_timeout_ms; + size_t between_bytes_timeout_ms; + size_t ssl_min_version; + size_t ssl_max_version; + const char* cert_hostname; + size_t cert_hostname_len; + const char* ca_cert; + size_t ca_cert_len; + const char* ciphers; + size_t ciphers_len; + const char* sni_hostname; + size_t sni_hostname_len; +} DynamicBackendConfig; + WASM_IMPORT("fastly_http_req", "register_dynamic_backend") int fastly_http_req__register_dynamic_backend(const char *name, size_t name_len, @@ -267,5 +270,70 @@ int fastly_http_resp__framing_headers_mode_set(WasiHandle resp_handle, uint32_t WASM_IMPORT("fastly_http_resp", "http_keepalive_mode_set") int fastly_http_resp__http_keepalive_mode_set(WasiHandle resp_handle, uint32_t mode); +/* FASTLY_CACHE */ + +typedef struct CacheLookupConfig { + // * A full request handle, but used only for its headers + WasiHandle request_headers; +} CacheLookupConfig; + +typedef struct CacheGetBodyConfig { + uint64_t start; + uint64_t end; +} CacheGetBodyConfig; + +typedef struct CacheWriteConfig { + uint64_t max_age_ns; + uint32_t request_headers; + const uint8_t *vary_rule_ptr; + size_t vary_rule_len; + uint64_t initial_age_ns; + uint64_t stale_while_revalidate_ns; + const uint8_t *surrogate_keys_ptr; + size_t surrogate_keys_len; + uint64_t length; + const uint8_t *user_metadata_ptr; + size_t user_metadata_len; +} CacheWriteConfig; + +WASM_IMPORT("fastly_cache", "lookup") +int cache_lookup(const char *cache_key, size_t cache_key_len, uint32_t options_mask, + CacheLookupConfig *config, + WasiHandle *ret); + +WASM_IMPORT("fastly_cache", "insert") +int fastly_cache__cache_insert(const char *cache_key, size_t cache_key_len, uint32_t options_mask, + CacheWriteConfig *config, WasiHandle *ret); + +WASM_IMPORT("fastly_cache", "transaction_lookup") +int fastly_cache__cache_transaction_lookup(const char *cache_key, size_t cache_key_len, uint32_t options_mask, + CacheLookupConfig *config, + WasiHandle *ret); + +WASM_IMPORT("fastly_cache", "transaction_insert_and_stream_back") +int fastly_cache__cache_transaction_insert_and_stream_back(WasiHandle handle, uint32_t options_mask, CacheWriteConfig *config, + WasiHandle *ret_body, + WasiHandle *ret_cache); + +WASM_IMPORT("fastly_cache", "transaction_cancel") +int fastly_cache__cache_transaction_cancel(WasiHandle handle); + +WASM_IMPORT("fastly_cache", "get_state") +int fastly_cache__cache_get_state(WasiHandle handle, uint8_t *ret); + +WASM_IMPORT("fastly_cache", "get_length") +int fastly_cache__cache_get_length(WasiHandle handle, uint64_t *ret); + +WASM_IMPORT("fastly_cache", "get_age_ns") +int fastly_cache__cache_get_age_ns(WasiHandle handle, uint64_t *ret); + +WASM_IMPORT("fastly_cache", "get_hits") +int fastly_cache__cache_get_hits(WasiHandle handle, uint64_t *ret); + +WASM_IMPORT("fastly_cache", "get_body") +int fastly_cache__cache_get_body(WasiHandle handle, uint32_t options_mask, + CacheGetBodyConfig *config, + WasiHandle *ret); + #pragma GCC diagnostic pop #endif /* ComputeRuntime_h */