Use key in dynamic table if available

pull/12946/head
yang-g 7 years ago
parent 6bf5f833ef
commit fd42ceb1ff
  1. 146
      src/core/ext/transport/chttp2/transport/hpack_encoder.cc
  2. 7
      src/core/lib/transport/metadata.cc
  3. 3
      src/core/lib/transport/metadata.h
  4. 99
      test/core/transport/chttp2/hpack_encoder_test.c
  5. 4
      test/core/transport/metadata_test.c

@ -180,14 +180,12 @@ static void evict_entry(grpc_chttp2_hpack_compressor *c) {
/* add an element to the decoder table */
static void add_elem(grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_compressor *c,
grpc_mdelem elem) {
GPR_ASSERT(GRPC_MDELEM_IS_INTERNED(elem));
grpc_mdelem elem, size_t elem_size, bool only_add_key) {
uint32_t key_hash = grpc_slice_hash(GRPC_MDKEY(elem));
uint32_t value_hash = grpc_slice_hash(GRPC_MDVALUE(elem));
uint32_t elem_hash = GRPC_MDSTR_KV_HASH(key_hash, value_hash);
uint32_t value_hash = only_add_key ? 0 : grpc_slice_hash(GRPC_MDVALUE(elem));
uint32_t elem_hash =
only_add_key ? 0 : GRPC_MDSTR_KV_HASH(key_hash, value_hash);
uint32_t new_index = c->tail_remote_index + c->table_elems + 1;
size_t elem_size = grpc_mdelem_get_size_in_hpack_table(elem);
GPR_ASSERT(elem_size < 65536);
@ -209,37 +207,7 @@ static void add_elem(grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_compressor *c,
c->table_size = (uint16_t)(c->table_size + elem_size);
c->table_elems++;
/* Store this element into {entries,indices}_elem */
if (grpc_mdelem_eq(c->entries_elems[HASH_FRAGMENT_2(elem_hash)], elem)) {
/* already there: update with new index */
c->indices_elems[HASH_FRAGMENT_2(elem_hash)] = new_index;
} else if (grpc_mdelem_eq(c->entries_elems[HASH_FRAGMENT_3(elem_hash)],
elem)) {
/* already there (cuckoo): update with new index */
c->indices_elems[HASH_FRAGMENT_3(elem_hash)] = new_index;
} else if (GRPC_MDISNULL(c->entries_elems[HASH_FRAGMENT_2(elem_hash)])) {
/* not there, but a free element: add */
c->entries_elems[HASH_FRAGMENT_2(elem_hash)] = GRPC_MDELEM_REF(elem);
c->indices_elems[HASH_FRAGMENT_2(elem_hash)] = new_index;
} else if (GRPC_MDISNULL(c->entries_elems[HASH_FRAGMENT_3(elem_hash)])) {
/* not there (cuckoo), but a free element: add */
c->entries_elems[HASH_FRAGMENT_3(elem_hash)] = GRPC_MDELEM_REF(elem);
c->indices_elems[HASH_FRAGMENT_3(elem_hash)] = new_index;
} else if (c->indices_elems[HASH_FRAGMENT_2(elem_hash)] <
c->indices_elems[HASH_FRAGMENT_3(elem_hash)]) {
/* not there: replace oldest */
GRPC_MDELEM_UNREF(exec_ctx, c->entries_elems[HASH_FRAGMENT_2(elem_hash)]);
c->entries_elems[HASH_FRAGMENT_2(elem_hash)] = GRPC_MDELEM_REF(elem);
c->indices_elems[HASH_FRAGMENT_2(elem_hash)] = new_index;
} else {
/* not there: replace oldest */
GRPC_MDELEM_UNREF(exec_ctx, c->entries_elems[HASH_FRAGMENT_3(elem_hash)]);
c->entries_elems[HASH_FRAGMENT_3(elem_hash)] = GRPC_MDELEM_REF(elem);
c->indices_elems[HASH_FRAGMENT_3(elem_hash)] = new_index;
}
/* do exactly the same for the key (so we can find by that again too) */
/* Store the key into {entries,indices}_keys */
if (grpc_slice_eq(c->entries_keys[HASH_FRAGMENT_2(key_hash)],
GRPC_MDKEY(elem))) {
c->indices_keys[HASH_FRAGMENT_2(key_hash)] = new_index;
@ -270,6 +238,37 @@ static void add_elem(grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_compressor *c,
grpc_slice_ref_internal(GRPC_MDKEY(elem));
c->indices_keys[HASH_FRAGMENT_3(key_hash)] = new_index;
}
if (!only_add_key) {
/* Store this element into {entries,indices}_elem */
if (grpc_mdelem_eq(c->entries_elems[HASH_FRAGMENT_2(elem_hash)], elem)) {
/* already there: update with new index */
c->indices_elems[HASH_FRAGMENT_2(elem_hash)] = new_index;
} else if (grpc_mdelem_eq(c->entries_elems[HASH_FRAGMENT_3(elem_hash)],
elem)) {
/* already there (cuckoo): update with new index */
c->indices_elems[HASH_FRAGMENT_3(elem_hash)] = new_index;
} else if (GRPC_MDISNULL(c->entries_elems[HASH_FRAGMENT_2(elem_hash)])) {
/* not there, but a free element: add */
c->entries_elems[HASH_FRAGMENT_2(elem_hash)] = GRPC_MDELEM_REF(elem);
c->indices_elems[HASH_FRAGMENT_2(elem_hash)] = new_index;
} else if (GRPC_MDISNULL(c->entries_elems[HASH_FRAGMENT_3(elem_hash)])) {
/* not there (cuckoo), but a free element: add */
c->entries_elems[HASH_FRAGMENT_3(elem_hash)] = GRPC_MDELEM_REF(elem);
c->indices_elems[HASH_FRAGMENT_3(elem_hash)] = new_index;
} else if (c->indices_elems[HASH_FRAGMENT_2(elem_hash)] <
c->indices_elems[HASH_FRAGMENT_3(elem_hash)]) {
/* not there: replace oldest */
GRPC_MDELEM_UNREF(exec_ctx, c->entries_elems[HASH_FRAGMENT_2(elem_hash)]);
c->entries_elems[HASH_FRAGMENT_2(elem_hash)] = GRPC_MDELEM_REF(elem);
c->indices_elems[HASH_FRAGMENT_2(elem_hash)] = new_index;
} else {
/* not there: replace oldest */
GRPC_MDELEM_UNREF(exec_ctx, c->entries_elems[HASH_FRAGMENT_3(elem_hash)]);
c->entries_elems[HASH_FRAGMENT_3(elem_hash)] = GRPC_MDELEM_REF(elem);
c->indices_elems[HASH_FRAGMENT_3(elem_hash)] = new_index;
}
}
}
static void emit_indexed(grpc_exec_ctx *exec_ctx,
@ -430,9 +429,14 @@ static void hpack_enc(grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_compressor *c,
"Reserved header (colon-prefixed) happening after regular ones.");
}
if (GRPC_TRACER_ON(grpc_http_trace) && !GRPC_MDELEM_IS_INTERNED(elem)) {
if (GRPC_TRACER_ON(grpc_http_trace)) {
char *k = grpc_slice_to_c_string(GRPC_MDKEY(elem));
char *v = grpc_slice_to_c_string(GRPC_MDVALUE(elem));
char *v = NULL;
if (grpc_is_binary_header(GRPC_MDKEY(elem))) {
v = grpc_dump_slice(GRPC_MDVALUE(elem), GPR_DUMP_HEX);
} else {
v = grpc_slice_to_c_string(GRPC_MDVALUE(elem));
}
gpr_log(
GPR_DEBUG,
"Encode: '%s: %s', elem_interned=%d [%d], k_interned=%d, v_interned=%d",
@ -442,7 +446,12 @@ static void hpack_enc(grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_compressor *c,
gpr_free(k);
gpr_free(v);
}
if (!GRPC_MDELEM_IS_INTERNED(elem)) {
bool elem_interned = GRPC_MDELEM_IS_INTERNED(elem);
bool key_interned = elem_interned || grpc_slice_is_interned(GRPC_MDKEY(elem));
// Key is not interned, emit literals.
if (!key_interned) {
emit_lithdr_noidx_v(exec_ctx, c, elem, st);
return;
}
@ -452,37 +461,46 @@ static void hpack_enc(grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_compressor *c,
uint32_t elem_hash;
size_t decoder_space_usage;
uint32_t indices_key;
int should_add_elem;
bool should_add_elem;
bool should_add_key;
key_hash = grpc_slice_hash(GRPC_MDKEY(elem));
value_hash = grpc_slice_hash(GRPC_MDVALUE(elem));
elem_hash = GRPC_MDSTR_KV_HASH(key_hash, value_hash);
inc_filter(HASH_FRAGMENT_1(elem_hash), &c->filter_elems_sum, c->filter_elems);
if (elem_interned) {
value_hash = grpc_slice_hash(GRPC_MDVALUE(elem));
elem_hash = GRPC_MDSTR_KV_HASH(key_hash, value_hash);
/* is this elem currently in the decoders table? */
inc_filter(HASH_FRAGMENT_1(elem_hash), &c->filter_elems_sum,
c->filter_elems);
if (grpc_mdelem_eq(c->entries_elems[HASH_FRAGMENT_2(elem_hash)], elem) &&
c->indices_elems[HASH_FRAGMENT_2(elem_hash)] > c->tail_remote_index) {
/* HIT: complete element (first cuckoo hash) */
emit_indexed(exec_ctx, c,
dynidx(c, c->indices_elems[HASH_FRAGMENT_2(elem_hash)]), st);
return;
}
/* is this elem currently in the decoders table? */
if (grpc_mdelem_eq(c->entries_elems[HASH_FRAGMENT_3(elem_hash)], elem) &&
c->indices_elems[HASH_FRAGMENT_3(elem_hash)] > c->tail_remote_index) {
/* HIT: complete element (second cuckoo hash) */
emit_indexed(exec_ctx, c,
dynidx(c, c->indices_elems[HASH_FRAGMENT_3(elem_hash)]), st);
return;
if (grpc_mdelem_eq(c->entries_elems[HASH_FRAGMENT_2(elem_hash)], elem) &&
c->indices_elems[HASH_FRAGMENT_2(elem_hash)] > c->tail_remote_index) {
/* HIT: complete element (first cuckoo hash) */
emit_indexed(exec_ctx, c,
dynidx(c, c->indices_elems[HASH_FRAGMENT_2(elem_hash)]), st);
return;
}
if (grpc_mdelem_eq(c->entries_elems[HASH_FRAGMENT_3(elem_hash)], elem) &&
c->indices_elems[HASH_FRAGMENT_3(elem_hash)] > c->tail_remote_index) {
/* HIT: complete element (second cuckoo hash) */
emit_indexed(exec_ctx, c,
dynidx(c, c->indices_elems[HASH_FRAGMENT_3(elem_hash)]), st);
return;
}
}
/* should this elem be in the table? */
decoder_space_usage = grpc_mdelem_get_size_in_hpack_table(elem);
should_add_elem = decoder_space_usage < MAX_DECODER_SPACE_USAGE &&
decoder_space_usage =
grpc_mdelem_get_size_in_hpack_table(elem, st->use_true_binary_metadata);
should_add_elem = elem_interned &&
decoder_space_usage < MAX_DECODER_SPACE_USAGE &&
c->filter_elems[HASH_FRAGMENT_1(elem_hash)] >=
c->filter_elems_sum / ONE_ON_ADD_PROBABILITY;
should_add_key =
!elem_interned && decoder_space_usage < MAX_DECODER_SPACE_USAGE;
/* no hits for the elem... maybe there's a key? */
@ -493,7 +511,7 @@ static void hpack_enc(grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_compressor *c,
/* HIT: key (first cuckoo hash) */
if (should_add_elem) {
emit_lithdr_incidx(exec_ctx, c, dynidx(c, indices_key), elem, st);
add_elem(exec_ctx, c, elem);
add_elem(exec_ctx, c, elem, decoder_space_usage, false);
return;
} else {
emit_lithdr_noidx(exec_ctx, c, dynidx(c, indices_key), elem, st);
@ -509,7 +527,7 @@ static void hpack_enc(grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_compressor *c,
/* HIT: key (first cuckoo hash) */
if (should_add_elem) {
emit_lithdr_incidx(exec_ctx, c, dynidx(c, indices_key), elem, st);
add_elem(exec_ctx, c, elem);
add_elem(exec_ctx, c, elem, decoder_space_usage, false);
return;
} else {
emit_lithdr_noidx(exec_ctx, c, dynidx(c, indices_key), elem, st);
@ -520,9 +538,9 @@ static void hpack_enc(grpc_exec_ctx *exec_ctx, grpc_chttp2_hpack_compressor *c,
/* no elem, key in the table... fall back to literal emission */
if (should_add_elem) {
if (should_add_elem || should_add_key) {
emit_lithdr_incidx_v(exec_ctx, c, elem, st);
add_elem(exec_ctx, c, elem);
add_elem(exec_ctx, c, elem, decoder_space_usage, should_add_key);
return;
} else {
emit_lithdr_noidx_v(exec_ctx, c, elem, st);

@ -352,11 +352,14 @@ static size_t get_base64_encoded_size(size_t raw_length) {
return raw_length / 3 * 4 + tail_xtra[raw_length % 3];
}
size_t grpc_mdelem_get_size_in_hpack_table(grpc_mdelem elem) {
size_t grpc_mdelem_get_size_in_hpack_table(grpc_mdelem elem,
bool use_true_binary_metadata) {
size_t overhead_and_key = 32 + GRPC_SLICE_LENGTH(GRPC_MDKEY(elem));
size_t value_len = GRPC_SLICE_LENGTH(GRPC_MDVALUE(elem));
if (grpc_is_binary_header(GRPC_MDKEY(elem))) {
return overhead_and_key + get_base64_encoded_size(value_len);
return overhead_and_key + (use_true_binary_metadata
? value_len + 1
: get_base64_encoded_size(value_len));
} else {
return overhead_and_key + value_len;
}

@ -132,7 +132,8 @@ grpc_mdelem grpc_mdelem_create(
bool grpc_mdelem_eq(grpc_mdelem a, grpc_mdelem b);
size_t grpc_mdelem_get_size_in_hpack_table(grpc_mdelem elem);
size_t grpc_mdelem_get_size_in_hpack_table(grpc_mdelem elem,
bool use_true_binary_metadata);
/* Mutator and accessor for grpc_mdelem user data. The destructor function
is used as a type tag and is checked during user_data fetch. */

@ -43,10 +43,15 @@ void **to_delete = NULL;
size_t num_to_delete = 0;
size_t cap_to_delete = 0;
typedef struct {
bool eof;
bool use_true_binary_metadata;
bool only_intern_key;
} verify_params;
/* verify that the output generated by encoding the stream matches the
hexstring passed in */
static void verify(grpc_exec_ctx *exec_ctx, size_t window_available, bool eof,
bool use_true_binary_metadata, size_t expect_window_used,
static void verify(grpc_exec_ctx *exec_ctx, const verify_params params,
const char *expected, size_t nheaders, ...) {
grpc_slice_buffer output;
grpc_slice merged;
@ -66,9 +71,13 @@ static void verify(grpc_exec_ctx *exec_ctx, size_t window_available, bool eof,
e[i - 1].next = &e[i];
e[i].prev = &e[i - 1];
}
grpc_slice value_slice = grpc_slice_from_static_string(value);
if (!params.only_intern_key) {
value_slice = grpc_slice_intern(value_slice);
}
e[i].md = grpc_mdelem_from_slices(
exec_ctx, grpc_slice_intern(grpc_slice_from_static_string(key)),
grpc_slice_intern(grpc_slice_from_static_string(value)));
value_slice);
}
e[0].prev = NULL;
e[nheaders - 1].next = NULL;
@ -90,8 +99,8 @@ static void verify(grpc_exec_ctx *exec_ctx, size_t window_available, bool eof,
memset(&stats, 0, sizeof(stats));
grpc_encode_header_options hopt = {
.stream_id = 0xdeadbeef,
.is_eof = eof,
.use_true_binary_metadata = use_true_binary_metadata,
.is_eof = params.eof,
.use_true_binary_metadata = params.use_true_binary_metadata,
.max_frame_size = 16384,
.stats = &stats,
};
@ -119,28 +128,27 @@ static void verify(grpc_exec_ctx *exec_ctx, size_t window_available, bool eof,
static void test_basic_headers(grpc_exec_ctx *exec_ctx) {
int i;
verify(exec_ctx, 0, false, false, 0, "000005 0104 deadbeef 40 0161 0161", 1,
"a", "a");
verify(exec_ctx, 0, false, false, 0, "000001 0104 deadbeef be", 1, "a", "a");
verify(exec_ctx, 0, false, false, 0, "000001 0104 deadbeef be", 1, "a", "a");
verify(exec_ctx, 0, false, false, 0, "000006 0104 deadbeef be 40 0162 0163",
2, "a", "a", "b", "c");
verify(exec_ctx, 0, false, false, 0, "000002 0104 deadbeef bf be", 2, "a",
"a", "b", "c");
verify(exec_ctx, 0, false, false, 0, "000004 0104 deadbeef 7f 00 0164", 1,
"a", "d");
verify_params params = {
.eof = false, .use_true_binary_metadata = false, .only_intern_key = false,
};
verify(exec_ctx, params, "000005 0104 deadbeef 40 0161 0161", 1, "a", "a");
verify(exec_ctx, params, "000001 0104 deadbeef be", 1, "a", "a");
verify(exec_ctx, params, "000001 0104 deadbeef be", 1, "a", "a");
verify(exec_ctx, params, "000006 0104 deadbeef be 40 0162 0163", 2, "a", "a",
"b", "c");
verify(exec_ctx, params, "000002 0104 deadbeef bf be", 2, "a", "a", "b", "c");
verify(exec_ctx, params, "000004 0104 deadbeef 7f 00 0164", 1, "a", "d");
/* flush out what's there to make a few values look very popular */
for (i = 0; i < 350; i++) {
verify(exec_ctx, 0, false, false, 0, "000003 0104 deadbeef c0 bf be", 3,
"a", "a", "b", "c", "a", "d");
verify(exec_ctx, params, "000003 0104 deadbeef c0 bf be", 3, "a", "a", "b",
"c", "a", "d");
}
verify(exec_ctx, 0, false, false, 0, "000006 0104 deadbeef c0 00 016b 0176",
2, "a", "a", "k", "v");
verify(exec_ctx, params, "000006 0104 deadbeef c0 00 016b 0176", 2, "a", "a",
"k", "v");
/* this could be 000004 0104 deadbeef 0f 30 0176 also */
verify(exec_ctx, 0, false, false, 0, "000004 0104 deadbeef 0f 2f 0176", 1,
"a", "v");
verify(exec_ctx, params, "000004 0104 deadbeef 0f 2f 0176", 1, "a", "v");
}
static void encode_int_to_str(int i, char *p) {
@ -156,6 +164,10 @@ static void test_decode_table_overflow(grpc_exec_ctx *exec_ctx) {
char key[3], value[3];
char *expect;
verify_params params = {
.eof = false, .use_true_binary_metadata = false, .only_intern_key = false,
};
for (i = 0; i < 114; i++) {
encode_int_to_str(i, key);
encode_int_to_str(i + 1, value);
@ -174,27 +186,28 @@ static void test_decode_table_overflow(grpc_exec_ctx *exec_ctx) {
}
if (i > 0) {
verify(exec_ctx, 0, false, false, 0, expect, 2, "aa", "ba", key, value);
verify(exec_ctx, params, expect, 2, "aa", "ba", key, value);
} else {
verify(exec_ctx, 0, false, false, 0, expect, 1, key, value);
verify(exec_ctx, params, expect, 1, key, value);
}
gpr_free(expect);
}
/* if the above passes, then we must have just knocked this pair out of the
decoder stack, and so we'll be forced to re-encode it */
verify(exec_ctx, 0, false, false, 0, "000007 0104 deadbeef 40 026161 026261",
1, "aa", "ba");
verify(exec_ctx, params, "000007 0104 deadbeef 40 026161 026261", 1, "aa",
"ba");
}
static void verify_table_size_change_match_elem_size(grpc_exec_ctx *exec_ctx,
const char *key,
const char *value) {
const char *value,
bool use_true_binary) {
grpc_slice_buffer output;
grpc_mdelem elem = grpc_mdelem_from_slices(
exec_ctx, grpc_slice_intern(grpc_slice_from_static_string(key)),
grpc_slice_intern(grpc_slice_from_static_string(value)));
size_t elem_size = grpc_mdelem_get_size_in_hpack_table(elem);
size_t elem_size = grpc_mdelem_get_size_in_hpack_table(elem, use_true_binary);
size_t initial_table_size = g_compressor.table_size;
grpc_linked_mdelem *e = gpr_malloc(sizeof(*e));
grpc_metadata_batch b;
@ -209,11 +222,12 @@ static void verify_table_size_change_match_elem_size(grpc_exec_ctx *exec_ctx,
grpc_transport_one_way_stats stats;
memset(&stats, 0, sizeof(stats));
grpc_encode_header_options hopt = {.stream_id = 0xdeadbeef,
.is_eof = false,
.use_true_binary_metadata = false,
.max_frame_size = 16384,
.stats = &stats};
grpc_encode_header_options hopt = {
.stream_id = 0xdeadbeef,
.is_eof = false,
.use_true_binary_metadata = use_true_binary,
.max_frame_size = 16384,
.stats = &stats};
grpc_chttp2_encode_header(exec_ctx, &g_compressor, NULL, 0, &b, &hopt,
&output);
grpc_slice_buffer_destroy_internal(exec_ctx, &output);
@ -224,8 +238,24 @@ static void verify_table_size_change_match_elem_size(grpc_exec_ctx *exec_ctx,
}
static void test_encode_header_size(grpc_exec_ctx *exec_ctx) {
verify_table_size_change_match_elem_size(exec_ctx, "hello", "world");
verify_table_size_change_match_elem_size(exec_ctx, "hello-bin", "world");
verify_table_size_change_match_elem_size(exec_ctx, "hello", "world", false);
verify_table_size_change_match_elem_size(exec_ctx, "hello-bin", "world",
false);
verify_table_size_change_match_elem_size(exec_ctx, "true-binary-bin",
"I_am_true_binary_value", true);
}
static void test_interned_key_indexed(grpc_exec_ctx *exec_ctx) {
int i;
verify_params params = {
.eof = false, .use_true_binary_metadata = false, .only_intern_key = true,
};
verify(exec_ctx, params, "000009 0104 deadbeef 40 0161 0162 0f2f 0163", 2,
"a", "b", "a", "c");
for (i = 0; i < 10; i++) {
verify(exec_ctx, params, "000008 0104 deadbeef 0f2f 0162 0f2f 0163", 2, "a",
"b", "a", "c");
}
}
static void run_test(void (*test)(grpc_exec_ctx *exec_ctx), const char *name) {
@ -245,6 +275,7 @@ int main(int argc, char **argv) {
TEST(test_basic_headers);
TEST(test_decode_table_overflow);
TEST(test_encode_header_size);
TEST(test_interned_key_indexed);
grpc_shutdown();
for (i = 0; i < num_to_delete; i++) {
gpr_free(to_delete[i]);

@ -302,7 +302,7 @@ static void verify_ascii_header_size(grpc_exec_ctx *exec_ctx, const char *key,
grpc_mdelem elem = grpc_mdelem_from_slices(
exec_ctx, maybe_intern(grpc_slice_from_static_string(key), intern_key),
maybe_intern(grpc_slice_from_static_string(value), intern_value));
size_t elem_size = grpc_mdelem_get_size_in_hpack_table(elem);
size_t elem_size = grpc_mdelem_get_size_in_hpack_table(elem, false);
size_t expected_size = 32 + strlen(key) + strlen(value);
GPR_ASSERT(expected_size == elem_size);
GRPC_MDELEM_UNREF(exec_ctx, elem);
@ -316,7 +316,7 @@ static void verify_binary_header_size(grpc_exec_ctx *exec_ctx, const char *key,
maybe_intern(grpc_slice_from_static_buffer(value, value_len),
intern_value));
GPR_ASSERT(grpc_is_binary_header(GRPC_MDKEY(elem)));
size_t elem_size = grpc_mdelem_get_size_in_hpack_table(elem);
size_t elem_size = grpc_mdelem_get_size_in_hpack_table(elem, false);
grpc_slice value_slice =
grpc_slice_from_copied_buffer((const char *)value, value_len);
grpc_slice base64_encoded = grpc_chttp2_base64_encode(value_slice);

Loading…
Cancel
Save