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
53 changes: 16 additions & 37 deletions Sources/GRPCCore/Call/Client/CallOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,46 +77,27 @@ public struct CallOptions: Sendable {
/// reported to the client. Hedging is only suitable for idempotent RPCs.
public var executionPolicy: RPCExecutionPolicy?

/// Whether compression is enabled or not for request and response messages.
public var compression: Compression

public struct Compression: Sendable {
/// Whether request messages should be compressed.
///
/// Note that this option is _advisory_: transports are not required to support compression.
public var requests: Bool

/// Whether response messages are permitted to be compressed.
public var responses: Bool

/// Creates a new ``Compression`` configuration.
///
/// - Parameters:
/// - requests: Whether request messages should be compressed.
/// - responses: Whether response messages may be compressed.
public init(requests: Bool, responses: Bool) {
self.requests = requests
self.responses = responses
}

/// Sets ``requests`` and ``responses`` to `true`.
public static var enabled: Self {
Self(requests: true, responses: true)
}

/// Sets ``requests`` and ``responses`` to `false`.
public static var disabled: Self {
Self(requests: false, responses: false)
}
}
/// The compression used for the call.
///
/// Compression in gRPC is asymmetrical: the server may compress response messages using a
/// different algorithm than the client used to compress request messages. This configuration
/// controls the compression used by the client for request messages.
///
/// Note that this configuration is advisory: not all transports support compression and may
/// ignore this configuration. Transports which support compression will use this configuration
/// in preference to the algorithm configured at a transport level. If the transport hasn't
/// enabled the use of the algorithm then compression won't be used for the call.
///
/// If `nil` the value configured on the transport will be used instead.
public var compression: CompressionAlgorithm?

internal init(
timeout: Duration?,
waitForReady: Bool?,
maxRequestMessageBytes: Int?,
maxResponseMessageBytes: Int?,
executionPolicy: RPCExecutionPolicy?,
compression: Compression
compression: CompressionAlgorithm?
) {
self.timeout = timeout
self.waitForReady = waitForReady
Expand All @@ -131,17 +112,15 @@ public struct CallOptions: Sendable {
extension CallOptions {
/// Default call options.
///
/// The default values defer values to the underlying transport. In most cases this means values
/// are `nil`, with the exception of ``compression-swift.property`` which is set
/// to ``Compression-swift.struct/disabled``.
/// The default values (`nil`) defer values to the underlying transport.
public static var defaults: Self {
Self(
timeout: nil,
waitForReady: nil,
maxRequestMessageBytes: nil,
maxResponseMessageBytes: nil,
executionPolicy: nil,
compression: .disabled
compression: nil
)
}
}
Expand Down
129 changes: 129 additions & 0 deletions Sources/GRPCCore/Coding/CompressionAlgorithm.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright 2024, gRPC Authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/// Message compression algorithms.
public struct CompressionAlgorithm: Hashable, Sendable {
@_spi(Package)
public enum Value: UInt8, Hashable, Sendable, CaseIterable {
case none = 0
case deflate
case gzip
}

@_spi(Package)
public let value: Value

fileprivate init(_ algorithm: Value) {
self.value = algorithm
}

/// No compression, sometimes referred to as 'identity' compression.
public static var none: Self {
Self(.none)
}

/// The 'deflate' compression algorithm.
public static var deflate: Self {
Self(.deflate)
}

/// The 'gzip' compression algorithm.
public static var gzip: Self {
Self(.gzip)
}
}

/// A set of compression algorithms.
public struct CompressionAlgorithmSet: OptionSet, Hashable, Sendable {
public var rawValue: UInt32

public init(rawValue: UInt32) {
self.rawValue = rawValue
}

private init(value: CompressionAlgorithm.Value) {
self.rawValue = 1 << value.rawValue
}

/// No compression, sometimes referred to as 'identity' compression.
public static var none: Self {
return Self(value: .none)
}

/// The 'deflate' compression algorithm.
public static var deflate: Self {
return Self(value: .deflate)
}

/// The 'gzip' compression algorithm.
public static var gzip: Self {
return Self(value: .gzip)
}

/// All compression algorithms.
public static var all: Self {
return [.gzip, .deflate, .none]
}

/// Returns whether a given algorithm is present in the set.
///
/// - Parameter algorithm: The algorithm to check.
public func contains(_ algorithm: CompressionAlgorithm) -> Bool {
return self.contains(CompressionAlgorithmSet(value: algorithm.value))
}
}

extension CompressionAlgorithmSet {
/// A sequence of ``CompressionAlgorithm`` values present in the set.
public var elements: Elements {
Elements(algorithmSet: self)
}

/// A sequence of ``CompressionAlgorithm`` values present in a ``CompressionAlgorithmSet``.
public struct Elements: Sequence {
public typealias Element = CompressionAlgorithm

private let algorithmSet: CompressionAlgorithmSet

init(algorithmSet: CompressionAlgorithmSet) {
self.algorithmSet = algorithmSet
}

public func makeIterator() -> Iterator {
return Iterator(algorithmSet: self.algorithmSet)
}

public struct Iterator: IteratorProtocol {
private let algorithmSet: CompressionAlgorithmSet
private var iterator: IndexingIterator<[CompressionAlgorithm.Value]>

init(algorithmSet: CompressionAlgorithmSet) {
self.algorithmSet = algorithmSet
self.iterator = CompressionAlgorithm.Value.allCases.makeIterator()
}

public mutating func next() -> CompressionAlgorithm? {
while let value = self.iterator.next() {
if self.algorithmSet.contains(CompressionAlgorithmSet(value: value)) {
return CompressionAlgorithm(value)
}
}

return nil
}
}
}
}
2 changes: 1 addition & 1 deletion Sources/GRPCHTTP2Core/Client/GRPCClientStreamHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ final class GRPCClientStreamHandler: ChannelDuplexHandler {
methodDescriptor: MethodDescriptor,
scheme: Scheme,
outboundEncoding: CompressionAlgorithm,
acceptedEncodings: [CompressionAlgorithm],
acceptedEncodings: CompressionAlgorithmSet,
maximumPayloadSize: Int,
skipStateMachineAssertions: Bool = false
) {
Expand Down
55 changes: 28 additions & 27 deletions Sources/GRPCHTTP2Core/Compression/CompressionAlgorithm.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,40 @@
* limitations under the License.
*/

/// Supported message compression algorithms.
///
/// These algorithms are indicated in the "grpc-encoding" header. As such, a lack of "grpc-encoding"
/// header indicates that there is no message compression.
public struct CompressionAlgorithm: Hashable, Sendable {
/// Identity compression; "no" compression but indicated via the "grpc-encoding" header.
public static let identity = CompressionAlgorithm(.identity)
public static let deflate = CompressionAlgorithm(.deflate)
public static let gzip = CompressionAlgorithm(.gzip)
@_spi(Package) import GRPCCore

// The order here is important: most compression to least.
public static let all: [CompressionAlgorithm] = [.gzip, .deflate, .identity]

public var name: String {
return self.algorithm.rawValue
extension CompressionAlgorithm {
init?(name: String) {
self.init(name: name[...])
}

internal enum Algorithm: String {
case identity
case deflate
case gzip
init?(name: Substring) {
switch name {
case "gzip":
self = .gzip
case "deflate":
self = .deflate
case "identity":
self = .none
default:
return nil
}
}

internal let algorithm: Algorithm

private init(_ algorithm: Algorithm) {
self.algorithm = algorithm
var name: String {
switch self.value {
case .gzip:
return "gzip"
case .deflate:
return "deflate"
case .none:
return "identity"
}
}
}

internal init?(rawValue: String) {
guard let algorithm = Algorithm(rawValue: rawValue) else {
return nil
}
self.algorithm = algorithm
extension CompressionAlgorithmSet {
var count: Int {
self.rawValue.nonzeroBitCount
}
}
Loading