blob: 928ed73cdad8613ac12d7cd48470d173e224bcb4 [file] [log] [blame]
/*
*
* Copyright 2015 gRPC authors.
*
* 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.
*
*/
#include <grpc/support/port_platform.h>
#include "src/core/lib/transport/metadata_batch.h"
#include <stdbool.h>
#include <string.h>
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include "src/core/lib/profiling/timers.h"
#include "src/core/lib/slice/slice_internal.h"
#include "src/core/lib/slice/slice_string_helpers.h"
static void assert_valid_list(grpc_mdelem_list* list) {
#ifndef NDEBUG
grpc_linked_mdelem* l;
GPR_ASSERT((list->head == nullptr) == (list->tail == nullptr));
if (!list->head) return;
GPR_ASSERT(list->head->prev == nullptr);
GPR_ASSERT(list->tail->next == nullptr);
GPR_ASSERT((list->head == list->tail) == (list->head->next == nullptr));
size_t verified_count = 0;
for (l = list->head; l; l = l->next) {
GPR_ASSERT(!GRPC_MDISNULL(l->md));
GPR_ASSERT((l->prev == nullptr) == (l == list->head));
GPR_ASSERT((l->next == nullptr) == (l == list->tail));
if (l->next) GPR_ASSERT(l->next->prev == l);
if (l->prev) GPR_ASSERT(l->prev->next == l);
verified_count++;
}
GPR_ASSERT(list->count == verified_count);
#endif /* NDEBUG */
}
static void assert_valid_callouts(grpc_metadata_batch* batch) {
#ifndef NDEBUG
for (grpc_linked_mdelem* l = batch->list.head; l != nullptr; l = l->next) {
grpc_slice key_interned = grpc_slice_intern(GRPC_MDKEY(l->md));
grpc_metadata_batch_callouts_index callout_idx =
GRPC_BATCH_INDEX_OF(key_interned);
if (callout_idx != GRPC_BATCH_CALLOUTS_COUNT) {
GPR_ASSERT(batch->idx.array[callout_idx] == l);
}
grpc_slice_unref_internal(key_interned);
}
#endif
}
#ifndef NDEBUG
void grpc_metadata_batch_assert_ok(grpc_metadata_batch* batch) {
assert_valid_list(&batch->list);
}
#endif /* NDEBUG */
void grpc_metadata_batch_init(grpc_metadata_batch* batch) {
memset(batch, 0, sizeof(*batch));
batch->deadline = GRPC_MILLIS_INF_FUTURE;
}
void grpc_metadata_batch_destroy(grpc_metadata_batch* batch) {
grpc_linked_mdelem* l;
for (l = batch->list.head; l; l = l->next) {
GRPC_MDELEM_UNREF(l->md);
}
}
grpc_error* grpc_attach_md_to_error(grpc_error* src, grpc_mdelem md) {
grpc_error* out = grpc_error_set_str(
grpc_error_set_str(src, GRPC_ERROR_STR_KEY,
grpc_slice_ref_internal(GRPC_MDKEY(md))),
GRPC_ERROR_STR_VALUE, grpc_slice_ref_internal(GRPC_MDVALUE(md)));
return out;
}
static grpc_error* maybe_link_callout(grpc_metadata_batch* batch,
grpc_linked_mdelem* storage)
GRPC_MUST_USE_RESULT;
static grpc_error* maybe_link_callout(grpc_metadata_batch* batch,
grpc_linked_mdelem* storage) {
grpc_metadata_batch_callouts_index idx =
GRPC_BATCH_INDEX_OF(GRPC_MDKEY(storage->md));
if (idx == GRPC_BATCH_CALLOUTS_COUNT) {
return GRPC_ERROR_NONE;
}
if (batch->idx.array[idx] == nullptr) {
++batch->list.default_count;
batch->idx.array[idx] = storage;
return GRPC_ERROR_NONE;
}
return grpc_attach_md_to_error(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("Unallowed duplicate metadata"),
storage->md);
}
static void maybe_unlink_callout(grpc_metadata_batch* batch,
grpc_linked_mdelem* storage) {
grpc_metadata_batch_callouts_index idx =
GRPC_BATCH_INDEX_OF(GRPC_MDKEY(storage->md));
if (idx == GRPC_BATCH_CALLOUTS_COUNT) {
return;
}
--batch->list.default_count;
GPR_ASSERT(batch->idx.array[idx] != nullptr);
batch->idx.array[idx] = nullptr;
}
grpc_error* grpc_metadata_batch_add_head(grpc_metadata_batch* batch,
grpc_linked_mdelem* storage,
grpc_mdelem elem_to_add) {
GPR_ASSERT(!GRPC_MDISNULL(elem_to_add));
storage->md = elem_to_add;
return grpc_metadata_batch_link_head(batch, storage);
}
static void link_head(grpc_mdelem_list* list, grpc_linked_mdelem* storage) {
assert_valid_list(list);
GPR_ASSERT(!GRPC_MDISNULL(storage->md));
storage->prev = nullptr;
storage->next = list->head;
storage->reserved = nullptr;
if (list->head != nullptr) {
list->head->prev = storage;
} else {
list->tail = storage;
}
list->head = storage;
list->count++;
assert_valid_list(list);
}
grpc_error* grpc_metadata_batch_link_head(grpc_metadata_batch* batch,
grpc_linked_mdelem* storage) {
assert_valid_callouts(batch);
grpc_error* err = maybe_link_callout(batch, storage);
if (err != GRPC_ERROR_NONE) {
assert_valid_callouts(batch);
return err;
}
link_head(&batch->list, storage);
assert_valid_callouts(batch);
return GRPC_ERROR_NONE;
}
grpc_error* grpc_metadata_batch_add_tail(grpc_metadata_batch* batch,
grpc_linked_mdelem* storage,
grpc_mdelem elem_to_add) {
GPR_ASSERT(!GRPC_MDISNULL(elem_to_add));
storage->md = elem_to_add;
return grpc_metadata_batch_link_tail(batch, storage);
}
static void link_tail(grpc_mdelem_list* list, grpc_linked_mdelem* storage) {
assert_valid_list(list);
GPR_ASSERT(!GRPC_MDISNULL(storage->md));
storage->prev = list->tail;
storage->next = nullptr;
storage->reserved = nullptr;
if (list->tail != nullptr) {
list->tail->next = storage;
} else {
list->head = storage;
}
list->tail = storage;
list->count++;
assert_valid_list(list);
}
grpc_error* grpc_metadata_batch_link_tail(grpc_metadata_batch* batch,
grpc_linked_mdelem* storage) {
assert_valid_callouts(batch);
grpc_error* err = maybe_link_callout(batch, storage);
if (err != GRPC_ERROR_NONE) {
assert_valid_callouts(batch);
return err;
}
link_tail(&batch->list, storage);
assert_valid_callouts(batch);
return GRPC_ERROR_NONE;
}
static void unlink_storage(grpc_mdelem_list* list,
grpc_linked_mdelem* storage) {
assert_valid_list(list);
if (storage->prev != nullptr) {
storage->prev->next = storage->next;
} else {
list->head = storage->next;
}
if (storage->next != nullptr) {
storage->next->prev = storage->prev;
} else {
list->tail = storage->prev;
}
list->count--;
assert_valid_list(list);
}
void grpc_metadata_batch_remove(grpc_metadata_batch* batch,
grpc_linked_mdelem* storage) {
assert_valid_callouts(batch);
maybe_unlink_callout(batch, storage);
unlink_storage(&batch->list, storage);
GRPC_MDELEM_UNREF(storage->md);
assert_valid_callouts(batch);
}
void grpc_metadata_batch_set_value(grpc_linked_mdelem* storage,
grpc_slice value) {
grpc_mdelem old_mdelem = storage->md;
grpc_mdelem new_mdelem = grpc_mdelem_from_slices(
grpc_slice_ref_internal(GRPC_MDKEY(old_mdelem)), value);
storage->md = new_mdelem;
GRPC_MDELEM_UNREF(old_mdelem);
}
grpc_error* grpc_metadata_batch_substitute(grpc_metadata_batch* batch,
grpc_linked_mdelem* storage,
grpc_mdelem new_mdelem) {
assert_valid_callouts(batch);
grpc_error* error = GRPC_ERROR_NONE;
grpc_mdelem old_mdelem = storage->md;
if (!grpc_slice_eq(GRPC_MDKEY(new_mdelem), GRPC_MDKEY(old_mdelem))) {
maybe_unlink_callout(batch, storage);
storage->md = new_mdelem;
error = maybe_link_callout(batch, storage);
if (error != GRPC_ERROR_NONE) {
unlink_storage(&batch->list, storage);
GRPC_MDELEM_UNREF(storage->md);
}
} else {
storage->md = new_mdelem;
}
GRPC_MDELEM_UNREF(old_mdelem);
assert_valid_callouts(batch);
return error;
}
void grpc_metadata_batch_clear(grpc_metadata_batch* batch) {
grpc_metadata_batch_destroy(batch);
grpc_metadata_batch_init(batch);
}
bool grpc_metadata_batch_is_empty(grpc_metadata_batch* batch) {
return batch->list.head == nullptr &&
batch->deadline == GRPC_MILLIS_INF_FUTURE;
}
size_t grpc_metadata_batch_size(grpc_metadata_batch* batch) {
size_t size = 0;
for (grpc_linked_mdelem* elem = batch->list.head; elem != nullptr;
elem = elem->next) {
size += GRPC_MDELEM_LENGTH(elem->md);
}
return size;
}
static void add_error(grpc_error** composite, grpc_error* error,
const char* composite_error_string) {
if (error == GRPC_ERROR_NONE) return;
if (*composite == GRPC_ERROR_NONE) {
*composite = GRPC_ERROR_CREATE_FROM_COPIED_STRING(composite_error_string);
}
*composite = grpc_error_add_child(*composite, error);
}
grpc_error* grpc_metadata_batch_filter(grpc_metadata_batch* batch,
grpc_metadata_batch_filter_func func,
void* user_data,
const char* composite_error_string) {
grpc_linked_mdelem* l = batch->list.head;
grpc_error* error = GRPC_ERROR_NONE;
while (l) {
grpc_linked_mdelem* next = l->next;
grpc_filtered_mdelem new_mdelem = func(user_data, l->md);
add_error(&error, new_mdelem.error, composite_error_string);
if (GRPC_MDISNULL(new_mdelem.md)) {
grpc_metadata_batch_remove(batch, l);
} else if (new_mdelem.md.payload != l->md.payload) {
grpc_metadata_batch_substitute(batch, l, new_mdelem.md);
}
l = next;
}
return error;
}
void grpc_metadata_batch_copy(grpc_metadata_batch* src,
grpc_metadata_batch* dst,
grpc_linked_mdelem* storage) {
grpc_metadata_batch_init(dst);
dst->deadline = src->deadline;
size_t i = 0;
for (grpc_linked_mdelem* elem = src->list.head; elem != nullptr;
elem = elem->next) {
grpc_error* error = grpc_metadata_batch_add_tail(dst, &storage[i++],
GRPC_MDELEM_REF(elem->md));
// The only way that grpc_metadata_batch_add_tail() can fail is if
// there's a duplicate entry for a callout. However, that can't be
// the case here, because we would not have been allowed to create
// a source batch that had that kind of conflict.
GPR_ASSERT(error == GRPC_ERROR_NONE);
}
}
void grpc_metadata_batch_move(grpc_metadata_batch* src,
grpc_metadata_batch* dst) {
*dst = *src;
grpc_metadata_batch_init(src);
}