From 13c2f6e69d04804b4c1cba6380b54f6adb80cee0 Mon Sep 17 00:00:00 2001
From: David Garcia Quintas <dgq@google.com>
Date: Thu, 17 Mar 2016 22:51:52 -0700
Subject: [PATCH] Implemented compression level algorithms properly (as a
 function of the accepted algorithms by the call's peer.

---
 include/grpc/compression.h                   |   6 +-
 src/core/compression/compression_algorithm.c |  46 ++++++++-
 src/core/surface/call.c                      |   6 ++
 src/core/surface/call.h                      |   9 +-
 src/cpp/server/server_context.cc             |   3 +-
 test/core/compression/compression_test.c     | 102 +++++++++++++++++--
 6 files changed, 152 insertions(+), 20 deletions(-)

diff --git a/include/grpc/compression.h b/include/grpc/compression.h
index acc168a6ee..70ba393261 100644
--- a/include/grpc/compression.h
+++ b/include/grpc/compression.h
@@ -55,11 +55,13 @@ GRPCAPI int grpc_compression_algorithm_parse(
 GRPCAPI int grpc_compression_algorithm_name(
     grpc_compression_algorithm algorithm, char **name);
 
-/** Returns the compression algorithm corresponding to \a level.
+/** Returns the compression algorithm corresponding to \a level for the
+ * compression algorithms encoded in the \a accepted_encodings bitset.
  *
  * It abort()s for unknown levels . */
 GRPCAPI grpc_compression_algorithm
-grpc_compression_algorithm_for_level(grpc_compression_level level);
+grpc_compression_algorithm_for_level(grpc_compression_level level,
+                                     uint32_t accepted_encodings);
 
 GRPCAPI void grpc_compression_options_init(grpc_compression_options *opts);
 
diff --git a/src/core/compression/compression_algorithm.c b/src/core/compression/compression_algorithm.c
index 6f3a8eb28e..a76abc372f 100644
--- a/src/core/compression/compression_algorithm.c
+++ b/src/core/compression/compression_algorithm.c
@@ -128,20 +128,56 @@ grpc_mdelem *grpc_compression_encoding_mdelem(
 /* TODO(dgq): Add the ability to specify parameters to the individual
  * compression algorithms */
 grpc_compression_algorithm grpc_compression_algorithm_for_level(
-    grpc_compression_level level) {
+    grpc_compression_level level, uint32_t accepted_encodings) {
   GRPC_API_TRACE("grpc_compression_algorithm_for_level(level=%d)", 1,
                  ((int)level));
+  if (level > GRPC_COMPRESS_LEVEL_HIGH) {
+    gpr_log(GPR_ERROR, "Unknown compression level %d.", (int)level);
+    abort();
+  }
+
+  const size_t num_supported =
+      GPR_BITCOUNT(accepted_encodings) - 1; /* discard NONE */
+  if (level == GRPC_COMPRESS_LEVEL_NONE || num_supported == 0) {
+    return GRPC_COMPRESS_NONE;
+  }
+
+  GPR_ASSERT(level > 0);
+
+  /* Establish a "ranking" or compression algorithms in increasing order of
+   * compression.
+   * This is simplistic and we will probably want to introduce other dimensions
+   * in the future (cpu/memory cost, etc). */
+  const grpc_compression_algorithm algos_ranking[] = {GRPC_COMPRESS_GZIP,
+                                                      GRPC_COMPRESS_DEFLATE};
+
+  /* intersect algos_ranking with the supported ones keeping the ranked order */
+  grpc_compression_algorithm sorted_supported_algos[num_supported];
+  size_t algos_supported_idx = 0;
+  for (size_t i = 0; i < GPR_ARRAY_SIZE(algos_ranking); i++) {
+    const grpc_compression_algorithm alg = algos_ranking[i];
+    for (size_t j = 0; j < num_supported; j++) {
+      if (GPR_BITGET(accepted_encodings, alg) == 1) {
+        /* if \a alg in supported */
+        sorted_supported_algos[algos_supported_idx++] = alg;
+        break;
+      }
+    }
+    if (algos_supported_idx == num_supported) break;
+  }
+
   switch (level) {
     case GRPC_COMPRESS_LEVEL_NONE:
-      return GRPC_COMPRESS_NONE;
+      abort(); /* should have been handled already */
     case GRPC_COMPRESS_LEVEL_LOW:
+      return sorted_supported_algos[0];
     case GRPC_COMPRESS_LEVEL_MED:
+      return sorted_supported_algos[num_supported / 2];
     case GRPC_COMPRESS_LEVEL_HIGH:
-      return GRPC_COMPRESS_DEFLATE;
+      return sorted_supported_algos[num_supported - 1];
     default:
-      gpr_log(GPR_ERROR, "Unknown compression level %d.", (int)level);
       abort();
-  }
+  };
 }
 
 void grpc_compression_options_init(grpc_compression_options *opts) {
diff --git a/src/core/surface/call.c b/src/core/surface/call.c
index 1b117aa6b8..7dd74d86b4 100644
--- a/src/core/surface/call.c
+++ b/src/core/surface/call.c
@@ -1481,3 +1481,9 @@ void *grpc_call_context_get(grpc_call *call, grpc_context_index elem) {
 }
 
 uint8_t grpc_call_is_client(grpc_call *call) { return call->is_client; }
+
+grpc_compression_algorithm grpc_call_compression_for_level(
+    grpc_call *call, grpc_compression_level level) {
+  return grpc_compression_algorithm_for_level(level,
+                                              call->encodings_accepted_by_peer);
+}
diff --git a/src/core/surface/call.h b/src/core/surface/call.h
index 0bbffb98ae..0b3f543fe4 100644
--- a/src/core/surface/call.h
+++ b/src/core/surface/call.h
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2015, Google Inc.
+ * Copyright 2015-2016, Google Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -38,7 +38,9 @@
 #include "src/core/channel/context.h"
 #include "src/core/surface/api_trace.h"
 #include "src/core/surface/surface_trace.h"
+
 #include <grpc/grpc.h>
+#include <grpc/impl/codegen/compression_types.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -102,6 +104,11 @@ void *grpc_call_context_get(grpc_call *call, grpc_context_index elem);
 
 uint8_t grpc_call_is_client(grpc_call *call);
 
+/* Return an appropriate compression algorithm for the requested compression \a
+ * level in the context of \a call. */
+grpc_compression_algorithm grpc_call_compression_for_level(
+    grpc_call *call, grpc_compression_level level);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/cpp/server/server_context.cc b/src/cpp/server/server_context.cc
index eb49b21037..5d12ce2ecf 100644
--- a/src/cpp/server/server_context.cc
+++ b/src/cpp/server/server_context.cc
@@ -43,6 +43,7 @@
 #include <grpc/support/log.h>
 
 #include "src/core/channel/compress_filter.h"
+#include "src/core/surface/call.h"
 #include "src/cpp/common/create_auth_context.h"
 
 namespace grpc {
@@ -197,7 +198,7 @@ bool ServerContext::IsCancelled() const {
 
 void ServerContext::set_compression_level(grpc_compression_level level) {
   const grpc_compression_algorithm algorithm_for_level =
-      grpc_compression_algorithm_for_level(level);
+      grpc_call_compression_for_level(call_, level);
   set_compression_algorithm(algorithm_for_level);
 }
 
diff --git a/test/core/compression/compression_test.c b/test/core/compression/compression_test.c
index 26d7b2b6cc..5d8231fd7f 100644
--- a/test/core/compression/compression_test.c
+++ b/test/core/compression/compression_test.c
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2015, Google Inc.
+ * Copyright 2015-2016, Google Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -93,18 +93,98 @@ static void test_compression_algorithm_name(void) {
 }
 
 static void test_compression_algorithm_for_level(void) {
-  size_t i;
-  grpc_compression_level levels[] = {
-      GRPC_COMPRESS_LEVEL_NONE, GRPC_COMPRESS_LEVEL_LOW,
-      GRPC_COMPRESS_LEVEL_MED, GRPC_COMPRESS_LEVEL_HIGH};
-  grpc_compression_algorithm algorithms[] = {
-      GRPC_COMPRESS_NONE, GRPC_COMPRESS_DEFLATE, GRPC_COMPRESS_DEFLATE,
-      GRPC_COMPRESS_DEFLATE};
   gpr_log(GPR_DEBUG, "test_compression_algorithm_for_level");
 
-  for (i = 0; i < GPR_ARRAY_SIZE(levels); i++) {
-    GPR_ASSERT(algorithms[i] ==
-               grpc_compression_algorithm_for_level(levels[i]));
+  {
+    /* accept only identity (aka none) */
+    uint32_t accepted_encodings = 0;
+    GPR_BITSET(&accepted_encodings, GRPC_COMPRESS_NONE); /* always */
+
+    GPR_ASSERT(GRPC_COMPRESS_NONE ==
+               grpc_compression_algorithm_for_level(GRPC_COMPRESS_LEVEL_NONE,
+                                                    accepted_encodings));
+
+    GPR_ASSERT(GRPC_COMPRESS_NONE ==
+               grpc_compression_algorithm_for_level(GRPC_COMPRESS_LEVEL_LOW,
+                                                    accepted_encodings));
+
+    GPR_ASSERT(GRPC_COMPRESS_NONE ==
+               grpc_compression_algorithm_for_level(GRPC_COMPRESS_LEVEL_MED,
+                                                    accepted_encodings));
+
+    GPR_ASSERT(GRPC_COMPRESS_NONE ==
+               grpc_compression_algorithm_for_level(GRPC_COMPRESS_LEVEL_HIGH,
+                                                    accepted_encodings));
+  }
+
+  {
+    /* accept only gzip */
+    uint32_t accepted_encodings = 0;
+    GPR_BITSET(&accepted_encodings, GRPC_COMPRESS_NONE); /* always */
+    GPR_BITSET(&accepted_encodings, GRPC_COMPRESS_GZIP);
+
+    GPR_ASSERT(GRPC_COMPRESS_NONE ==
+               grpc_compression_algorithm_for_level(GRPC_COMPRESS_LEVEL_NONE,
+                                                    accepted_encodings));
+
+    GPR_ASSERT(GRPC_COMPRESS_GZIP ==
+               grpc_compression_algorithm_for_level(GRPC_COMPRESS_LEVEL_LOW,
+                                                    accepted_encodings));
+
+    GPR_ASSERT(GRPC_COMPRESS_GZIP ==
+               grpc_compression_algorithm_for_level(GRPC_COMPRESS_LEVEL_MED,
+                                                    accepted_encodings));
+
+    GPR_ASSERT(GRPC_COMPRESS_GZIP ==
+               grpc_compression_algorithm_for_level(GRPC_COMPRESS_LEVEL_HIGH,
+                                                    accepted_encodings));
+  }
+
+  {
+    /* accept only deflate */
+    uint32_t accepted_encodings = 0;
+    GPR_BITSET(&accepted_encodings, GRPC_COMPRESS_NONE); /* always */
+    GPR_BITSET(&accepted_encodings, GRPC_COMPRESS_DEFLATE);
+
+    GPR_ASSERT(GRPC_COMPRESS_NONE ==
+               grpc_compression_algorithm_for_level(GRPC_COMPRESS_LEVEL_NONE,
+                                                    accepted_encodings));
+
+    GPR_ASSERT(GRPC_COMPRESS_DEFLATE ==
+               grpc_compression_algorithm_for_level(GRPC_COMPRESS_LEVEL_LOW,
+                                                    accepted_encodings));
+
+    GPR_ASSERT(GRPC_COMPRESS_DEFLATE ==
+               grpc_compression_algorithm_for_level(GRPC_COMPRESS_LEVEL_MED,
+                                                    accepted_encodings));
+
+    GPR_ASSERT(GRPC_COMPRESS_DEFLATE ==
+               grpc_compression_algorithm_for_level(GRPC_COMPRESS_LEVEL_HIGH,
+                                                    accepted_encodings));
+  }
+
+  {
+    /* accept gzip and deflate */
+    uint32_t accepted_encodings = 0;
+    GPR_BITSET(&accepted_encodings, GRPC_COMPRESS_NONE); /* always */
+    GPR_BITSET(&accepted_encodings, GRPC_COMPRESS_GZIP);
+    GPR_BITSET(&accepted_encodings, GRPC_COMPRESS_DEFLATE);
+
+    GPR_ASSERT(GRPC_COMPRESS_NONE ==
+               grpc_compression_algorithm_for_level(GRPC_COMPRESS_LEVEL_NONE,
+                                                    accepted_encodings));
+
+    GPR_ASSERT(GRPC_COMPRESS_GZIP ==
+               grpc_compression_algorithm_for_level(GRPC_COMPRESS_LEVEL_LOW,
+                                                    accepted_encodings));
+
+    GPR_ASSERT(GRPC_COMPRESS_DEFLATE ==
+               grpc_compression_algorithm_for_level(GRPC_COMPRESS_LEVEL_MED,
+                                                    accepted_encodings));
+
+    GPR_ASSERT(GRPC_COMPRESS_DEFLATE ==
+               grpc_compression_algorithm_for_level(GRPC_COMPRESS_LEVEL_HIGH,
+                                                    accepted_encodings));
   }
 }
 
-- 
GitLab