Merge pull request #13865 from muxi/add-objc-compression

Add API to enable compression
diff --git a/src/objective-c/GRPCClient/GRPCCall+ChannelArg.h b/src/objective-c/GRPCClient/GRPCCall+ChannelArg.h
index 9f2361b..18d4597 100644
--- a/src/objective-c/GRPCClient/GRPCCall+ChannelArg.h
+++ b/src/objective-c/GRPCClient/GRPCCall+ChannelArg.h
@@ -19,6 +19,12 @@
 
 #include <AvailabilityMacros.h>
 
+typedef NS_ENUM(NSInteger, GRPCCompressAlgorithm) {
+  GRPCCompressNone,
+  GRPCCompressDeflate,
+  GRPCCompressGzip,
+};
+
 /**
  * Methods to configure GRPC channel options.
  */
@@ -36,4 +42,8 @@
 + (void)closeOpenConnections DEPRECATED_MSG_ATTRIBUTE("The API for this feature is experimental, "
                                                       "and might be removed or modified at any "
                                                       "time.");
+
++ (void)setDefaultCompressMethod:(GRPCCompressAlgorithm)algorithm
+                         forhost:(nonnull NSString *)host;
+
 @end
diff --git a/src/objective-c/GRPCClient/GRPCCall+ChannelArg.m b/src/objective-c/GRPCClient/GRPCCall+ChannelArg.m
index 398d98f..805e54b 100644
--- a/src/objective-c/GRPCClient/GRPCCall+ChannelArg.m
+++ b/src/objective-c/GRPCClient/GRPCCall+ChannelArg.m
@@ -20,6 +20,8 @@
 
 #import "private/GRPCHost.h"
 
+#import <grpc/impl/codegen/compression_types.h>
+
 @implementation GRPCCall (ChannelArg)
 
 + (void)setUserAgentPrefix:(nonnull NSString *)userAgentPrefix forHost:(nonnull NSString *)host {
@@ -36,4 +38,23 @@
   [GRPCHost flushChannelCache];
 }
 
++ (void)setDefaultCompressMethod:(GRPCCompressAlgorithm)algorithm
+                         forhost:(nonnull NSString *)host {
+  GRPCHost *hostConfig = [GRPCHost hostWithAddress:host];
+  switch (algorithm) {
+    case GRPCCompressNone:
+      hostConfig.compressAlgorithm = GRPC_COMPRESS_NONE;
+      break;
+    case GRPCCompressDeflate:
+      hostConfig.compressAlgorithm = GRPC_COMPRESS_DEFLATE;
+      break;
+    case GRPCCompressGzip:
+      hostConfig.compressAlgorithm = GRPC_COMPRESS_GZIP;
+      break;
+    default:
+      NSLog(@"Invalid compression algorithm");
+      abort();
+  }
+}
+
 @end
diff --git a/src/objective-c/GRPCClient/private/GRPCHost.h b/src/objective-c/GRPCClient/private/GRPCHost.h
index 5817121..0215db8 100644
--- a/src/objective-c/GRPCClient/private/GRPCHost.h
+++ b/src/objective-c/GRPCClient/private/GRPCHost.h
@@ -18,6 +18,8 @@
 
 #import <Foundation/Foundation.h>
 
+#import <grpc/impl/codegen/compression_types.h>
+
 NS_ASSUME_NONNULL_BEGIN
 
 @class GRPCCompletionQueue;
@@ -32,6 +34,7 @@
 @property(nonatomic, readonly) NSString *address;
 @property(nonatomic, copy, nullable) NSString *userAgentPrefix;
 @property(nonatomic, nullable) struct grpc_channel_credentials *channelCreds;
+@property(nonatomic) grpc_compression_algorithm compressAlgorithm;
 
 /** The following properties should only be modified for testing: */
 
diff --git a/src/objective-c/GRPCClient/private/GRPCHost.m b/src/objective-c/GRPCClient/private/GRPCHost.m
index a0f4118..665943f 100644
--- a/src/objective-c/GRPCClient/private/GRPCHost.m
+++ b/src/objective-c/GRPCClient/private/GRPCHost.m
@@ -87,6 +87,7 @@
       _address = address;
       _secure = YES;
       kHostCache[address] = self;
+      _compressAlgorithm = GRPC_COMPRESS_NONE;
     }
     // Keep a single monitor to flush the cache if the connectivity status changes
     // Thread safety guarded by @synchronized(kHostCache)
@@ -226,6 +227,12 @@
   }
   // Use 10000ms initial backoff time for correct behavior on bad/slow networks  
   args[@GRPC_ARG_INITIAL_RECONNECT_BACKOFF_MS] = @10000;
+
+  if (_compressAlgorithm != GRPC_COMPRESS_NONE) {
+    args[@GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM] =
+        [NSNumber numberWithInt:_compressAlgorithm];
+  }
+
   return args;
 }
 
diff --git a/src/objective-c/tests/InteropTests.m b/src/objective-c/tests/InteropTests.m
index 0be8669..dfa874a 100644
--- a/src/objective-c/tests/InteropTests.m
+++ b/src/objective-c/tests/InteropTests.m
@@ -68,6 +68,10 @@
 }
 @end
 
+BOOL isRemoteInteropTest(NSString *host) {
+  return [host isEqualToString:@"grpc-test.sandbox.googleapis.com"];
+}
+
 #pragma mark Tests
 
 @implementation InteropTests {
@@ -452,4 +456,34 @@
   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
 }
 
+- (void)testCompressedUnaryRPC {
+  // This test needs to be disabled for remote test because interop server grpc-test
+  // does not support compression.
+  if (isRemoteInteropTest(self.class.host)) {
+    return;
+  }
+  XCTAssertNotNil(self.class.host);
+  __weak XCTestExpectation *expectation = [self expectationWithDescription:@"LargeUnary"];
+
+  RMTSimpleRequest *request = [RMTSimpleRequest message];
+  request.responseType = RMTPayloadType_Compressable;
+  request.responseSize = 314159;
+  request.payload.body = [NSMutableData dataWithLength:271828];
+  request.expectCompressed.value = YES;
+  [GRPCCall setDefaultCompressMethod:GRPCCompressGzip forhost:self.class.host];
+
+  [_service unaryCallWithRequest:request handler:^(RMTSimpleResponse *response, NSError *error) {
+    XCTAssertNil(error, @"Finished with unexpected error: %@", error);
+
+    RMTSimpleResponse *expectedResponse = [RMTSimpleResponse message];
+    expectedResponse.payload.type = RMTPayloadType_Compressable;
+    expectedResponse.payload.body = [NSMutableData dataWithLength:314159];
+    XCTAssertEqualObjects(response, expectedResponse);
+
+    [expectation fulfill];
+  }];
+
+  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
+}
+
 @end
diff --git a/src/objective-c/tests/RemoteTestClient/messages.proto b/src/objective-c/tests/RemoteTestClient/messages.proto
index 128efd9..342e91d 100644
--- a/src/objective-c/tests/RemoteTestClient/messages.proto
+++ b/src/objective-c/tests/RemoteTestClient/messages.proto
@@ -20,34 +20,45 @@
 
 option objc_class_prefix = "RMT";
 
+// TODO(dgq): Go back to using well-known types once
+// https://github.com/grpc/grpc/issues/6980 has been fixed.
+// import "google/protobuf/wrappers.proto";
+message BoolValue {
+  // The bool value.
+  bool value = 1;
+}
+
+// DEPRECATED, don't use. To be removed shortly.
 // The type of payload that should be returned.
 enum PayloadType {
   // Compressable text format.
   COMPRESSABLE = 0;
-
-  // Uncompressable binary format.
-  UNCOMPRESSABLE = 1;
-
-  // Randomly chosen from all other formats defined in this enum.
-  RANDOM = 2;
 }
 
 // A block of data, to simply increase gRPC message size.
 message Payload {
+  // DEPRECATED, don't use. To be removed shortly.
   // The type of data in body.
   PayloadType type = 1;
   // Primary contents of payload.
   bytes body = 2;
 }
 
+// A protobuf representation for grpc status. This is used by test
+// clients to specify a status that the server should attempt to return.
+message EchoStatus {
+  int32 code = 1;
+  string message = 2;
+}
+
 // Unary request.
 message SimpleRequest {
+  // DEPRECATED, don't use. To be removed shortly.
   // Desired payload type in the response from the server.
   // If response_type is RANDOM, server randomly chooses one from other formats.
   PayloadType response_type = 1;
 
   // Desired payload size in the response from the server.
-  // If response_type is COMPRESSABLE, this denotes the size before compression.
   int32 response_size = 2;
 
   // Optional input payload sent along with the request.
@@ -58,6 +69,18 @@
 
   // Whether SimpleResponse should include OAuth scope.
   bool fill_oauth_scope = 5;
+
+  // Whether to request the server to compress the response. This field is
+  // "nullable" in order to interoperate seamlessly with clients not able to
+  // implement the full compression tests by introspecting the call to verify
+  // the response's compression status.
+  BoolValue response_compressed = 6;
+
+  // Whether server should return a given status
+  EchoStatus response_status = 7;
+
+  // Whether the server should expect this request to be compressed.
+  BoolValue expect_compressed = 8;
 }
 
 // Unary response, as configured by the request.
@@ -76,6 +99,12 @@
   // Optional input payload sent along with the request.
   Payload payload = 1;
 
+  // Whether the server should expect this request to be compressed. This field
+  // is "nullable" in order to interoperate seamlessly with servers not able to
+  // implement the full compression tests by introspecting the call to verify
+  // the request's compression status.
+  BoolValue expect_compressed = 2;
+
   // Not expecting any payload from the response.
 }
 
@@ -88,16 +117,22 @@
 // Configuration for a particular response.
 message ResponseParameters {
   // Desired payload sizes in responses from the server.
-  // If response_type is COMPRESSABLE, this denotes the size before compression.
   int32 size = 1;
 
   // Desired interval between consecutive responses in the response stream in
   // microseconds.
   int32 interval_us = 2;
+
+  // Whether to request the server to compress the response. This field is
+  // "nullable" in order to interoperate seamlessly with clients not able to
+  // implement the full compression tests by introspecting the call to verify
+  // the response's compression status.
+  BoolValue compressed = 3;
 }
 
 // Server-streaming request.
 message StreamingOutputCallRequest {
+  // DEPRECATED, don't use. To be removed shortly.
   // Desired payload type in the response from the server.
   // If response_type is RANDOM, the payload from each response in the stream
   // might be of different types. This is to simulate a mixed type of payload
@@ -109,6 +144,9 @@
 
   // Optional input payload sent along with the request.
   Payload payload = 3;
+
+  // Whether server should return a given status
+  EchoStatus response_status = 7;
 }
 
 // Server-streaming response, as configured by the request and parameters.
@@ -116,3 +154,17 @@
   // Payload to increase response size.
   Payload payload = 1;
 }
+
+// For reconnect interop test only.
+// Client tells server what reconnection parameters it used.
+message ReconnectParams {
+  int32 max_reconnect_backoff_ms = 1;
+}
+
+// For reconnect interop test only.
+// Server tells client whether its reconnects are following the spec and the
+// reconnect backoffs it saw.
+message ReconnectInfo {
+  bool passed = 1;
+  repeated int32 backoff_ms = 2;
+}