Browse Source

Move CompressionAlgorithm to core (#1858)

Motivation:

`CallOptions` only let you enable/disable compression, it doesn't allow
you to control which algorithm should be used. This is an unnecessary
limitation. This was done because `CompressionAlgorithm` lives in the
http2 module.

Modifications:

- Move `CompressionAlgorithm` to the core module
- Rename 'identity' to 'none' as that's clearer for users
- Add extensions in the http2 module to create an algorithm from its
  name
- Add a `CompressionAlgorithmSet` type which uses an option set which
  allows for cheaper updates.
- Update call options

Result:

- `CallOptions` is more flexible
- Updating the call options set is cheaper
George Barnett 1 năm trước cách đây
mục cha
commit
dac2d68ab5

+ 16 - 37
Sources/GRPCCore/Call/Client/CallOptions.swift

@@ -77,38 +77,19 @@ 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?,
@@ -116,7 +97,7 @@ public struct CallOptions: Sendable {
     maxRequestMessageBytes: Int?,
     maxResponseMessageBytes: Int?,
     executionPolicy: RPCExecutionPolicy?,
-    compression: Compression
+    compression: CompressionAlgorithm?
   ) {
     self.timeout = timeout
     self.waitForReady = waitForReady
@@ -131,9 +112,7 @@ 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,
@@ -141,7 +120,7 @@ extension CallOptions {
       maxRequestMessageBytes: nil,
       maxResponseMessageBytes: nil,
       executionPolicy: nil,
-      compression: .disabled
+      compression: nil
     )
   }
 }

+ 129 - 0
Sources/GRPCCore/Coding/CompressionAlgorithm.swift

@@ -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
+      }
+    }
+  }
+}

+ 1 - 1
Sources/GRPCHTTP2Core/Client/GRPCClientStreamHandler.swift

@@ -35,7 +35,7 @@ final class GRPCClientStreamHandler: ChannelDuplexHandler {
     methodDescriptor: MethodDescriptor,
     scheme: Scheme,
     outboundEncoding: CompressionAlgorithm,
-    acceptedEncodings: [CompressionAlgorithm],
+    acceptedEncodings: CompressionAlgorithmSet,
     maximumPayloadSize: Int,
     skipStateMachineAssertions: Bool = false
   ) {

+ 28 - 27
Sources/GRPCHTTP2Core/Compression/CompressionAlgorithm.swift

@@ -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
   }
 }

+ 46 - 42
Sources/GRPCHTTP2Core/GRPCStreamStateMachine.swift

@@ -32,12 +32,29 @@ enum GRPCStreamStateMachineConfiguration {
     var methodDescriptor: MethodDescriptor
     var scheme: Scheme
     var outboundEncoding: CompressionAlgorithm
-    var acceptedEncodings: [CompressionAlgorithm]
+    var acceptedEncodings: CompressionAlgorithmSet
+
+    init(
+      methodDescriptor: MethodDescriptor,
+      scheme: Scheme,
+      outboundEncoding: CompressionAlgorithm,
+      acceptedEncodings: CompressionAlgorithmSet
+    ) {
+      self.methodDescriptor = methodDescriptor
+      self.scheme = scheme
+      self.outboundEncoding = outboundEncoding
+      self.acceptedEncodings = acceptedEncodings.union(.none)
+    }
   }
 
   struct ServerConfiguration {
     var scheme: Scheme
-    var acceptedEncodings: [CompressionAlgorithm]
+    var acceptedEncodings: CompressionAlgorithmSet
+
+    init(scheme: Scheme, acceptedEncodings: CompressionAlgorithmSet) {
+      self.scheme = scheme
+      self.acceptedEncodings = acceptedEncodings.union(.none)
+    }
   }
 }
 
@@ -490,7 +507,7 @@ extension GRPCStreamStateMachine {
     methodDescriptor: MethodDescriptor,
     scheme: Scheme,
     outboundEncoding: CompressionAlgorithm?,
-    acceptedEncodings: [CompressionAlgorithm],
+    acceptedEncodings: CompressionAlgorithmSet,
     customMetadata: Metadata
   ) -> HPACKHeaders {
     var headers = HPACKHeaders()
@@ -509,12 +526,12 @@ extension GRPCStreamStateMachine {
     headers.add(ContentType.grpc.canonicalValue, forKey: .contentType)
     headers.add("trailers", forKey: .te)  // Used to detect incompatible proxies
 
-    if let encoding = outboundEncoding, encoding != .identity {
+    if let encoding = outboundEncoding, encoding != .none {
       headers.add(encoding.name, forKey: .encoding)
     }
 
-    for acceptedEncoding in acceptedEncodings {
-      headers.add(acceptedEncoding.name, forKey: .acceptEncoding)
+    for encoding in acceptedEncodings.elements.filter({ $0 != .none }) {
+      headers.add(encoding.name, forKey: .acceptEncoding)
     }
 
     for metadataPair in customMetadata {
@@ -711,7 +728,7 @@ extension GRPCStreamStateMachine {
   ) -> ProcessInboundEncodingResult {
     let inboundEncoding: CompressionAlgorithm
     if let serverEncoding = headers.first(name: GRPCHTTP2Keys.encoding.rawValue) {
-      guard let parsedEncoding = CompressionAlgorithm(rawValue: serverEncoding),
+      guard let parsedEncoding = CompressionAlgorithm(name: serverEncoding),
         configuration.acceptedEncodings.contains(parsedEncoding)
       else {
         return .error(
@@ -727,7 +744,7 @@ extension GRPCStreamStateMachine {
       }
       inboundEncoding = parsedEncoding
     } else {
-      inboundEncoding = .identity
+      inboundEncoding = .none
     }
     return .success(inboundEncoding)
   }
@@ -997,11 +1014,11 @@ extension GRPCStreamStateMachine {
     headers.add("200", forKey: .status)
     headers.add(ContentType.grpc.canonicalValue, forKey: .contentType)
 
-    if let outboundEncoding, outboundEncoding != .identity {
+    if let outboundEncoding, outboundEncoding != .none {
       headers.add(outboundEncoding.name, forKey: .encoding)
     }
 
-    for acceptedEncoding in configuration.acceptedEncodings {
+    for acceptedEncoding in configuration.acceptedEncodings.elements.filter({ $0 != .none }) {
       headers.add(acceptedEncoding.name, forKey: .acceptEncoding)
     }
 
@@ -1241,10 +1258,6 @@ extension GRPCStreamStateMachine {
         )
       }
 
-      func isIdentityOrCompatibleEncoding(_ clientEncoding: CompressionAlgorithm) -> Bool {
-        clientEncoding == .identity || configuration.acceptedEncodings.contains(clientEncoding)
-      }
-
       // Firstly, find out if we support the client's chosen encoding, and reject
       // the RPC if we don't.
       let inboundEncoding: CompressionAlgorithm
@@ -1263,30 +1276,21 @@ extension GRPCStreamStateMachine {
           return .rejectRPC(trailers: trailers)
         }
 
-        guard let clientEncoding = CompressionAlgorithm(rawValue: String(rawEncoding)),
-          isIdentityOrCompatibleEncoding(clientEncoding)
+        guard let clientEncoding = CompressionAlgorithm(name: rawEncoding),
+          configuration.acceptedEncodings.contains(clientEncoding)
         else {
-          let statusMessage: String
-          let customMetadata: Metadata?
-          if configuration.acceptedEncodings.isEmpty {
-            statusMessage = "Compression is not supported"
-            customMetadata = nil
-          } else {
-            statusMessage = """
-              \(rawEncoding) compression is not supported; \
-              supported algorithms are listed in grpc-accept-encoding
-              """
-            customMetadata = {
-              var trailers = Metadata()
-              trailers.reserveCapacity(configuration.acceptedEncodings.count)
-              for acceptedEncoding in configuration.acceptedEncodings {
-                trailers.addString(
-                  acceptedEncoding.name,
-                  forKey: GRPCHTTP2Keys.acceptEncoding.rawValue
-                )
-              }
-              return trailers
-            }()
+          let statusMessage = """
+            \(rawEncoding) compression is not supported; \
+            supported algorithms are listed in grpc-accept-encoding
+            """
+
+          var customMetadata = Metadata()
+          customMetadata.reserveCapacity(configuration.acceptedEncodings.count)
+          for acceptedEncoding in configuration.acceptedEncodings.elements {
+            customMetadata.addString(
+              acceptedEncoding.name,
+              forKey: GRPCHTTP2Keys.acceptEncoding.rawValue
+            )
           }
 
           let trailers = self.makeTrailers(
@@ -1300,12 +1304,12 @@ extension GRPCStreamStateMachine {
         // Server supports client's encoding.
         inboundEncoding = clientEncoding
       } else {
-        inboundEncoding = .identity
+        inboundEncoding = .none
       }
 
       // Secondly, find a compatible encoding the server can use to compress outbound messages,
       // based on the encodings the client has advertised.
-      var outboundEncoding: CompressionAlgorithm = .identity
+      var outboundEncoding: CompressionAlgorithm = .none
       let clientAdvertisedEncodings = headers.values(
         forHeader: GRPCHTTP2Keys.acceptEncoding.rawValue,
         canonicalForm: true
@@ -1314,8 +1318,8 @@ extension GRPCStreamStateMachine {
       // If it's identity, just skip it altogether, since we won't be
       // compressing.
       for clientAdvertisedEncoding in clientAdvertisedEncodings {
-        if let algorithm = CompressionAlgorithm(rawValue: String(clientAdvertisedEncoding)),
-          isIdentityOrCompatibleEncoding(algorithm)
+        if let algorithm = CompressionAlgorithm(name: clientAdvertisedEncoding),
+          configuration.acceptedEncodings.contains(algorithm)
         {
           outboundEncoding = algorithm
           break
@@ -1510,7 +1514,7 @@ extension HPACKHeaders {
 extension Zlib.Method {
   init?(encoding: CompressionAlgorithm) {
     switch encoding {
-    case .identity:
+    case .none:
       return nil
     case .deflate:
       self = .deflate

+ 1 - 1
Sources/GRPCHTTP2Core/Server/GRPCServerStreamHandler.swift

@@ -39,7 +39,7 @@ final class GRPCServerStreamHandler: ChannelDuplexHandler {
 
   init(
     scheme: Scheme,
-    acceptedEncodings: [CompressionAlgorithm],
+    acceptedEncodings: CompressionAlgorithmSet,
     maximumPayloadSize: Int,
     skipStateMachineAssertions: Bool = false
   ) {

+ 61 - 0
Tests/GRPCCoreTests/Coding/CompressionAlgorithmTests.swift

@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+import GRPCCore
+import XCTest
+
+final class CompressionAlgorithmTests: XCTestCase {
+  func testCompressionAlgorithmSetContains() {
+    var algorithms = CompressionAlgorithmSet()
+    XCTAssertFalse(algorithms.contains(.gzip))
+    XCTAssertFalse(algorithms.contains(.deflate))
+    XCTAssertFalse(algorithms.contains(.none))
+
+    algorithms.formUnion(.gzip)
+    XCTAssertTrue(algorithms.contains(.gzip))
+    XCTAssertFalse(algorithms.contains(.deflate))
+    XCTAssertFalse(algorithms.contains(.none))
+
+    algorithms.formUnion(.deflate)
+    XCTAssertTrue(algorithms.contains(.gzip))
+    XCTAssertTrue(algorithms.contains(.deflate))
+    XCTAssertFalse(algorithms.contains(.none))
+
+    algorithms.formUnion(.none)
+    XCTAssertTrue(algorithms.contains(.gzip))
+    XCTAssertTrue(algorithms.contains(.deflate))
+    XCTAssertTrue(algorithms.contains(.none))
+  }
+
+  func testCompressionAlgorithmSetElements() {
+    var algorithms = CompressionAlgorithmSet.all
+    XCTAssertEqual(Array(algorithms.elements), [.none, .deflate, .gzip])
+
+    algorithms.subtract(.deflate)
+    XCTAssertEqual(Array(algorithms.elements), [.none, .gzip])
+
+    algorithms.subtract(.none)
+    XCTAssertEqual(Array(algorithms.elements), [.gzip])
+
+    algorithms.subtract(.gzip)
+    XCTAssertEqual(Array(algorithms.elements), [])
+  }
+
+  func testCompressionAlgorithmSetElementsIgnoresUnknownBits() {
+    let algorithms = CompressionAlgorithmSet(rawValue: .max)
+    XCTAssertEqual(Array(algorithms.elements), [.none, .deflate, .gzip])
+  }
+}

+ 11 - 11
Tests/GRPCHTTP2CoreTests/Client/GRPCClientStreamHandlerTests.swift

@@ -30,7 +30,7 @@ final class GRPCClientStreamHandlerTests: XCTestCase {
     let handler = GRPCClientStreamHandler(
       methodDescriptor: .init(service: "test", method: "test"),
       scheme: .http,
-      outboundEncoding: .identity,
+      outboundEncoding: .none,
       acceptedEncodings: [],
       maximumPayloadSize: 1
     )
@@ -60,7 +60,7 @@ final class GRPCClientStreamHandlerTests: XCTestCase {
     let handler = GRPCClientStreamHandler(
       methodDescriptor: .init(service: "test", method: "test"),
       scheme: .http,
-      outboundEncoding: .identity,
+      outboundEncoding: .none,
       acceptedEncodings: [],
       maximumPayloadSize: 1,
       skipStateMachineAssertions: true
@@ -96,7 +96,7 @@ final class GRPCClientStreamHandlerTests: XCTestCase {
     let handler = GRPCClientStreamHandler(
       methodDescriptor: .init(service: "test", method: "test"),
       scheme: .http,
-      outboundEncoding: .identity,
+      outboundEncoding: .none,
       acceptedEncodings: [],
       maximumPayloadSize: 1,
       skipStateMachineAssertions: true
@@ -127,7 +127,7 @@ final class GRPCClientStreamHandlerTests: XCTestCase {
     let handler = GRPCClientStreamHandler(
       methodDescriptor: .init(service: "test", method: "test"),
       scheme: .http,
-      outboundEncoding: .identity,
+      outboundEncoding: .none,
       acceptedEncodings: [],
       maximumPayloadSize: 1,
       skipStateMachineAssertions: true
@@ -164,7 +164,7 @@ final class GRPCClientStreamHandlerTests: XCTestCase {
     let handler = GRPCClientStreamHandler(
       methodDescriptor: .init(service: "test", method: "test"),
       scheme: .http,
-      outboundEncoding: .identity,
+      outboundEncoding: .none,
       acceptedEncodings: [],
       maximumPayloadSize: 1,
       skipStateMachineAssertions: true
@@ -258,7 +258,7 @@ final class GRPCClientStreamHandlerTests: XCTestCase {
     let handler = GRPCClientStreamHandler(
       methodDescriptor: .init(service: "test", method: "test"),
       scheme: .http,
-      outboundEncoding: .identity,
+      outboundEncoding: .none,
       acceptedEncodings: [],
       maximumPayloadSize: 1,
       skipStateMachineAssertions: true
@@ -328,7 +328,7 @@ final class GRPCClientStreamHandlerTests: XCTestCase {
     let handler = GRPCClientStreamHandler(
       methodDescriptor: .init(service: "test", method: "test"),
       scheme: .http,
-      outboundEncoding: .identity,
+      outboundEncoding: .none,
       acceptedEncodings: [],
       maximumPayloadSize: 100,
       skipStateMachineAssertions: true
@@ -396,7 +396,7 @@ final class GRPCClientStreamHandlerTests: XCTestCase {
     let handler = GRPCClientStreamHandler(
       methodDescriptor: .init(service: "test", method: "test"),
       scheme: .http,
-      outboundEncoding: .identity,
+      outboundEncoding: .none,
       acceptedEncodings: [],
       maximumPayloadSize: 1,
       skipStateMachineAssertions: true
@@ -462,7 +462,7 @@ final class GRPCClientStreamHandlerTests: XCTestCase {
     let handler = GRPCClientStreamHandler(
       methodDescriptor: .init(service: "test", method: "test"),
       scheme: .http,
-      outboundEncoding: .identity,
+      outboundEncoding: .none,
       acceptedEncodings: [],
       maximumPayloadSize: 100,
       skipStateMachineAssertions: true
@@ -580,7 +580,7 @@ final class GRPCClientStreamHandlerTests: XCTestCase {
     let handler = GRPCClientStreamHandler(
       methodDescriptor: .init(service: "test", method: "test"),
       scheme: .http,
-      outboundEncoding: .identity,
+      outboundEncoding: .none,
       acceptedEncodings: [],
       maximumPayloadSize: 100,
       skipStateMachineAssertions: true
@@ -685,7 +685,7 @@ final class GRPCClientStreamHandlerTests: XCTestCase {
     let handler = GRPCClientStreamHandler(
       methodDescriptor: .init(service: "test", method: "test"),
       scheme: .http,
-      outboundEncoding: .identity,
+      outboundEncoding: .none,
       acceptedEncodings: [],
       maximumPayloadSize: 100,
       skipStateMachineAssertions: true

+ 2 - 1
Tests/GRPCHTTP2CoreTests/GRPCStreamStateMachineTests.swift

@@ -139,7 +139,7 @@ final class GRPCStreamClientStateMachineTests: XCTestCase {
         .init(
           methodDescriptor: .init(service: "test", method: "test"),
           scheme: .http,
-          outboundEncoding: compressionEnabled ? .deflate : .identity,
+          outboundEncoding: compressionEnabled ? .deflate : .none,
           acceptedEncodings: [.deflate]
         )
       ),
@@ -1751,6 +1751,7 @@ final class GRPCStreamServerStateMachineTests: XCTestCase {
           "grpc-status": "12",
           "grpc-status-message":
             "gzip compression is not supported; supported algorithms are listed in grpc-accept-encoding",
+          "grpc-accept-encoding": "identity",
         ]
       )
     }

+ 3 - 1
Tests/GRPCHTTP2CoreTests/Server/GRPCServerStreamHandlerTests.swift

@@ -266,7 +266,9 @@ final class GRPCServerStreamHandlerTests: XCTestCase {
         GRPCHTTP2Keys.status.rawValue: "200",
         GRPCHTTP2Keys.contentType.rawValue: "application/grpc",
         GRPCHTTP2Keys.grpcStatus.rawValue: String(Status.Code.unimplemented.rawValue),
-        GRPCHTTP2Keys.grpcStatusMessage.rawValue: "Compression is not supported",
+        GRPCHTTP2Keys.grpcStatusMessage.rawValue:
+          "deflate compression is not supported; supported algorithms are listed in grpc-accept-encoding",
+        GRPCHTTP2Keys.acceptEncoding.rawValue: "identity",
       ]
     )
     XCTAssertTrue(writtenTrailersOnlyResponse.endStream)