/* * * 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 #include "src/core/lib/transport/metadata_batch.h" #include #include #include #include #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); }