diff options
author | Nicolas Noble <nicolasnoble@users.noreply.github.com> | 2016-03-28 09:21:56 -0700 |
---|---|---|
committer | Nicolas Noble <nicolasnoble@users.noreply.github.com> | 2016-03-28 09:21:56 -0700 |
commit | e5cc05b5c6cee7026a0d28d39925621451506820 (patch) | |
tree | 5a680b950edfbc64dd1edf939c572947deb1e416 /src/core/lib | |
parent | 94f908ae84ab6d280a41e09245925ecfa612dce8 (diff) | |
parent | 97e0ebc7ce45da32aca4bad799148eb3fc1b2033 (diff) |
Merge pull request #5964 from ctiller/move_the_things
Begin core componentization
Diffstat (limited to 'src/core/lib')
386 files changed, 65947 insertions, 0 deletions
diff --git a/src/core/lib/census/README.md b/src/core/lib/census/README.md new file mode 100644 index 0000000000..fb615a2194 --- /dev/null +++ b/src/core/lib/census/README.md @@ -0,0 +1,76 @@ +<!--- + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--> + +# Census - a resource measurement and tracing system + +This directory contains code for Census, which will ultimately provide the +following features for any gRPC-using system: +* A [dapper](http://research.google.com/pubs/pub36356.html)-like tracing + system, enabling tracing across a distributed infrastructure. +* RPC statistics and measurements for key metrics, such as latency, bytes + transferred, number of errors etc. +* Resource measurement framework which can be used for measuring custom + metrics. Through the use of [tags](#Tags), these can be broken down across + the entire distributed stack. +* Easy integration of the above with + [Google Cloud Trace](https://cloud.google.com/tools/cloud-trace) and + [Google Cloud Monitoring](https://cloud.google.com/monitoring/). + +## Concepts + +### Context + +### Operations + +### Tags + +### Metrics + +## API + +### Internal/RPC API + +### External/Client API + +### RPC API + +## Files in this directory + +Note that files and functions in this directory can be split into two +categories: +* Files that define core census library functions. Functions etc. in these + files are named census\_\*, and constitute the core census library + functionality. At some time in the future, these will become a standalone + library. +* Files that define functions etc. that provide a convenient interface between + grpc and the core census functionality. These files are all named + grpc\_\*.{c,h}, and define function names beginning with grpc\_census\_\*. + diff --git a/src/core/lib/census/aggregation.h b/src/core/lib/census/aggregation.h new file mode 100644 index 0000000000..f353368b97 --- /dev/null +++ b/src/core/lib/census/aggregation.h @@ -0,0 +1,66 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <stddef.h> + +#ifndef GRPC_CORE_LIB_CENSUS_AGGREGATION_H +#define GRPC_CORE_LIB_CENSUS_AGGREGATION_H + +/** Structure used to describe an aggregation type. */ +struct census_aggregation_ops { + /* Create a new aggregation. The pointer returned can be used in future calls + to clone(), free(), record(), data() and reset(). */ + void *(*create)(const void *create_arg); + /* Make a copy of an aggregation created by create() */ + void *(*clone)(const void *aggregation); + /* Destroy an aggregation created by create() */ + void (*free)(void *aggregation); + /* Record a new value against aggregation. */ + void (*record)(void *aggregation, double value); + /* Return current aggregation data. The caller must cast this object into + the correct type for the aggregation result. The object returned can be + freed by using free_data(). */ + void *(*data)(const void *aggregation); + /* free data returned by data() */ + void (*free_data)(void *data); + /* Reset an aggregation to default (zero) values. */ + void (*reset)(void *aggregation); + /* Merge 'from' aggregation into 'to'. Both aggregations must be compatible */ + void (*merge)(void *to, const void *from); + /* Fill buffer with printable string version of aggregation contents. For + debugging only. Returns the number of bytes added to buffer (a value == n + implies the buffer was of insufficient size). */ + size_t (*print)(const void *aggregation, char *buffer, size_t n); +}; + +#endif /* GRPC_CORE_LIB_CENSUS_AGGREGATION_H */ diff --git a/src/core/lib/census/context.c b/src/core/lib/census/context.c new file mode 100644 index 0000000000..5a118f46a9 --- /dev/null +++ b/src/core/lib/census/context.c @@ -0,0 +1,509 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/census.h> +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/port_platform.h> +#include <grpc/support/useful.h> +#include <stdbool.h> +#include <string.h> +#include "src/core/lib/support/string.h" + +// Functions in this file support the public context API, including +// encoding/decoding as part of context propagation across RPC's. The overall +// requirements (in approximate priority order) for the +// context representation: +// 1. Efficient conversion to/from wire format +// 2. Minimal bytes used on-wire +// 3. Efficient context creation +// 4. Efficient lookup of tag value for a key +// 5. Efficient iteration over tags +// 6. Minimal memory footprint +// +// Notes on tradeoffs/decisions: +// * tag includes 1 byte length of key, as well as nil-terminating byte. These +// are to aid in efficient parsing and the ability to directly return key +// strings. This is more important than saving a single byte/tag on the wire. +// * The wire encoding uses only single byte values. This eliminates the need +// to handle endian-ness conversions. It also means there is a hard upper +// limit of 255 for both CENSUS_MAX_TAG_KV_LEN and CENSUS_MAX_PROPAGATED_TAGS. +// * Keep all tag information (keys/values/flags) in a single memory buffer, +// that can be directly copied to the wire. + +// min and max valid chars in tag keys and values. All printable ASCII is OK. +#define MIN_VALID_TAG_CHAR 32 // ' ' +#define MAX_VALID_TAG_CHAR 126 // '~' + +// Structure representing a set of tags. Essentially a count of number of tags +// present, and pointer to a chunk of memory that contains the per-tag details. +struct tag_set { + int ntags; // number of tags. + int ntags_alloc; // ntags + number of deleted tags (total number of tags + // in all of kvm). This will always be == ntags, except during the process + // of building a new tag set. + size_t kvm_size; // number of bytes allocated for key/value storage. + size_t kvm_used; // number of bytes of used key/value memory + char *kvm; // key/value memory. Consists of repeated entries of: + // Offset Size Description + // 0 1 Key length, including trailing 0. (K) + // 1 1 Value length, including trailing 0 (V) + // 2 1 Flags + // 3 K Key bytes + // 3 + K V Value bytes + // + // We refer to the first 3 entries as the 'tag header'. If extra values are + // introduced in the header, you will need to modify the TAG_HEADER_SIZE + // constant, the raw_tag structure (and everything that uses it) and the + // encode/decode functions appropriately. +}; + +// Number of bytes in tag header. +#define TAG_HEADER_SIZE 3 // key length (1) + value length (1) + flags (1) +// Offsets to tag header entries. +#define KEY_LEN_OFFSET 0 +#define VALUE_LEN_OFFSET 1 +#define FLAG_OFFSET 2 + +// raw_tag represents the raw-storage form of a tag in the kvm of a tag_set. +struct raw_tag { + uint8_t key_len; + uint8_t value_len; + uint8_t flags; + char *key; + char *value; +}; + +// Use a reserved flag bit for indication of deleted tag. +#define CENSUS_TAG_DELETED CENSUS_TAG_RESERVED +#define CENSUS_TAG_IS_DELETED(flags) (flags & CENSUS_TAG_DELETED) + +// Primary representation of a context. Composed of 2 underlying tag_set +// structs, one each for propagated and local (non-propagated) tags. This is +// to efficiently support tag encoding/decoding. +// TODO(aveitch): need to add tracing id's/structure. +struct census_context { + struct tag_set tags[2]; + census_context_status status; +}; + +// Indices into the tags member of census_context +#define PROPAGATED_TAGS 0 +#define LOCAL_TAGS 1 + +// Validate (check all characters are in range and size is less than limit) a +// key or value string. Returns 0 if the string is invalid, or the length +// (including terminator) if valid. +static size_t validate_tag(const char *kv) { + size_t len = 1; + char ch; + while ((ch = *kv++) != 0) { + if (ch < MIN_VALID_TAG_CHAR || ch > MAX_VALID_TAG_CHAR) { + return 0; + } + len++; + } + if (len > CENSUS_MAX_TAG_KV_LEN) { + return 0; + } + return len; +} + +// Extract a raw tag given a pointer (raw) to the tag header. Allow for some +// extra bytes in the tag header (see encode/decode functions for usage: this +// allows for future expansion of the tag header). +static char *decode_tag(struct raw_tag *tag, char *header, int offset) { + tag->key_len = (uint8_t)(*header++); + tag->value_len = (uint8_t)(*header++); + tag->flags = (uint8_t)(*header++); + header += offset; + tag->key = header; + header += tag->key_len; + tag->value = header; + return header + tag->value_len; +} + +// Make a copy (in 'to') of an existing tag_set. +static void tag_set_copy(struct tag_set *to, const struct tag_set *from) { + memcpy(to, from, sizeof(struct tag_set)); + to->kvm = gpr_malloc(to->kvm_size); + memcpy(to->kvm, from->kvm, from->kvm_used); +} + +// Delete a tag from a tag_set, if it exists (returns true if it did). +static bool tag_set_delete_tag(struct tag_set *tags, const char *key, + size_t key_len) { + char *kvp = tags->kvm; + for (int i = 0; i < tags->ntags_alloc; i++) { + uint8_t *flags = (uint8_t *)(kvp + FLAG_OFFSET); + struct raw_tag tag; + kvp = decode_tag(&tag, kvp, 0); + if (CENSUS_TAG_IS_DELETED(tag.flags)) continue; + if ((key_len == tag.key_len) && (memcmp(key, tag.key, key_len) == 0)) { + *flags |= CENSUS_TAG_DELETED; + tags->ntags--; + return true; + } + } + return false; +} + +// Delete a tag from a context, return true if it existed. +static bool context_delete_tag(census_context *context, const census_tag *tag, + size_t key_len) { + return ( + tag_set_delete_tag(&context->tags[LOCAL_TAGS], tag->key, key_len) || + tag_set_delete_tag(&context->tags[PROPAGATED_TAGS], tag->key, key_len)); +} + +// Add a tag to a tag_set. Return true on success, false if the tag could +// not be added because of constraints on tag set size. This function should +// not be called if the tag may already exist (in a non-deleted state) in +// the tag_set, as that would result in two tags with the same key. +static bool tag_set_add_tag(struct tag_set *tags, const census_tag *tag, + size_t key_len, size_t value_len) { + if (tags->ntags == CENSUS_MAX_PROPAGATED_TAGS) { + return false; + } + const size_t tag_size = key_len + value_len + TAG_HEADER_SIZE; + if (tags->kvm_used + tag_size > tags->kvm_size) { + // allocate new memory if needed + tags->kvm_size += 2 * CENSUS_MAX_TAG_KV_LEN + TAG_HEADER_SIZE; + char *new_kvm = gpr_malloc(tags->kvm_size); + memcpy(new_kvm, tags->kvm, tags->kvm_used); + gpr_free(tags->kvm); + tags->kvm = new_kvm; + } + char *kvp = tags->kvm + tags->kvm_used; + *kvp++ = (char)key_len; + *kvp++ = (char)value_len; + // ensure reserved flags are not used. + *kvp++ = (char)(tag->flags & (CENSUS_TAG_PROPAGATE | CENSUS_TAG_STATS)); + memcpy(kvp, tag->key, key_len); + kvp += key_len; + memcpy(kvp, tag->value, value_len); + tags->kvm_used += tag_size; + tags->ntags++; + tags->ntags_alloc++; + return true; +} + +// Add/modify/delete a tag to/in a context. Caller must validate that tag key +// etc. are valid. +static void context_modify_tag(census_context *context, const census_tag *tag, + size_t key_len, size_t value_len) { + // First delete the tag if it is already present. + bool deleted = context_delete_tag(context, tag, key_len); + bool added = false; + if (CENSUS_TAG_IS_PROPAGATED(tag->flags)) { + added = tag_set_add_tag(&context->tags[PROPAGATED_TAGS], tag, key_len, + value_len); + } else { + added = + tag_set_add_tag(&context->tags[LOCAL_TAGS], tag, key_len, value_len); + } + + if (deleted) { + context->status.n_modified_tags++; + } else { + if (added) { + context->status.n_added_tags++; + } else { + context->status.n_ignored_tags++; + } + } +} + +// Remove memory used for deleted tags from a tag set. Basic algorithm: +// 1) Walk through tag set to find first deleted tag. Record where it is. +// 2) Find the next not-deleted tag. Copy all of kvm from there to the end +// "over" the deleted tags +// 3) repeat #1 and #2 until we have seen all tags +// 4) if we are still looking for a not-deleted tag, then all the end portion +// of the kvm is deleted. Just reduce the used amount of memory by the +// appropriate amount. +static void tag_set_flatten(struct tag_set *tags) { + if (tags->ntags == tags->ntags_alloc) return; + bool found_deleted = false; // found a deleted tag. + char *kvp = tags->kvm; + char *dbase = NULL; // record location of deleted tag + for (int i = 0; i < tags->ntags_alloc; i++) { + struct raw_tag tag; + char *next_kvp = decode_tag(&tag, kvp, 0); + if (found_deleted) { + if (!CENSUS_TAG_IS_DELETED(tag.flags)) { + ptrdiff_t reduce = kvp - dbase; // #bytes in deleted tags + GPR_ASSERT(reduce > 0); + ptrdiff_t copy_size = tags->kvm + tags->kvm_used - kvp; + GPR_ASSERT(copy_size > 0); + memmove(dbase, kvp, (size_t)copy_size); + tags->kvm_used -= (size_t)reduce; + next_kvp -= reduce; + found_deleted = false; + } + } else { + if (CENSUS_TAG_IS_DELETED(tag.flags)) { + dbase = kvp; + found_deleted = true; + } + } + kvp = next_kvp; + } + if (found_deleted) { + GPR_ASSERT(dbase > tags->kvm); + tags->kvm_used = (size_t)(dbase - tags->kvm); + } + tags->ntags_alloc = tags->ntags; +} + +census_context *census_context_create(const census_context *base, + const census_tag *tags, int ntags, + census_context_status const **status) { + census_context *context = gpr_malloc(sizeof(census_context)); + // If we are given a base, copy it into our new tag set. Otherwise set it + // to zero/NULL everything. + if (base == NULL) { + memset(context, 0, sizeof(census_context)); + } else { + tag_set_copy(&context->tags[PROPAGATED_TAGS], &base->tags[PROPAGATED_TAGS]); + tag_set_copy(&context->tags[LOCAL_TAGS], &base->tags[LOCAL_TAGS]); + memset(&context->status, 0, sizeof(context->status)); + } + // Walk over the additional tags and, for those that aren't invalid, modify + // the context to add/replace/delete as required. + for (int i = 0; i < ntags; i++) { + const census_tag *tag = &tags[i]; + size_t key_len = validate_tag(tag->key); + // ignore the tag if it is invalid or too short. + if (key_len <= 1) { + context->status.n_invalid_tags++; + } else { + if (tag->value != NULL) { + size_t value_len = validate_tag(tag->value); + if (value_len != 0) { + context_modify_tag(context, tag, key_len, value_len); + } else { + context->status.n_invalid_tags++; + } + } else { + if (context_delete_tag(context, tag, key_len)) { + context->status.n_deleted_tags++; + } + } + } + } + // Remove any deleted tags, update status if needed, and return. + tag_set_flatten(&context->tags[PROPAGATED_TAGS]); + tag_set_flatten(&context->tags[LOCAL_TAGS]); + context->status.n_propagated_tags = context->tags[PROPAGATED_TAGS].ntags; + context->status.n_local_tags = context->tags[LOCAL_TAGS].ntags; + if (status) { + *status = &context->status; + } + return context; +} + +const census_context_status *census_context_get_status( + const census_context *context) { + return &context->status; +} + +void census_context_destroy(census_context *context) { + gpr_free(context->tags[PROPAGATED_TAGS].kvm); + gpr_free(context->tags[LOCAL_TAGS].kvm); + gpr_free(context); +} + +void census_context_initialize_iterator(const census_context *context, + census_context_iterator *iterator) { + iterator->context = context; + iterator->index = 0; + if (context->tags[PROPAGATED_TAGS].ntags != 0) { + iterator->base = PROPAGATED_TAGS; + iterator->kvm = context->tags[PROPAGATED_TAGS].kvm; + } else if (context->tags[LOCAL_TAGS].ntags != 0) { + iterator->base = LOCAL_TAGS; + iterator->kvm = context->tags[LOCAL_TAGS].kvm; + } else { + iterator->base = -1; + } +} + +int census_context_next_tag(census_context_iterator *iterator, + census_tag *tag) { + if (iterator->base < 0) { + return 0; + } + struct raw_tag raw; + iterator->kvm = decode_tag(&raw, iterator->kvm, 0); + tag->key = raw.key; + tag->value = raw.value; + tag->flags = raw.flags; + if (++iterator->index == iterator->context->tags[iterator->base].ntags) { + do { + if (iterator->base == LOCAL_TAGS) { + iterator->base = -1; + return 1; + } + } while (iterator->context->tags[++iterator->base].ntags == 0); + iterator->index = 0; + iterator->kvm = iterator->context->tags[iterator->base].kvm; + } + return 1; +} + +// Find a tag in a tag_set by key. Return true if found, false otherwise. +static bool tag_set_get_tag(const struct tag_set *tags, const char *key, + size_t key_len, census_tag *tag) { + char *kvp = tags->kvm; + for (int i = 0; i < tags->ntags; i++) { + struct raw_tag raw; + kvp = decode_tag(&raw, kvp, 0); + if (key_len == raw.key_len && memcmp(raw.key, key, key_len) == 0) { + tag->key = raw.key; + tag->value = raw.value; + tag->flags = raw.flags; + return true; + } + } + return false; +} + +int census_context_get_tag(const census_context *context, const char *key, + census_tag *tag) { + size_t key_len = strlen(key) + 1; + if (key_len == 1) { + return 0; + } + if (tag_set_get_tag(&context->tags[PROPAGATED_TAGS], key, key_len, tag) || + tag_set_get_tag(&context->tags[LOCAL_TAGS], key, key_len, tag)) { + return 1; + } + return 0; +} + +// Context encoding and decoding functions. +// +// Wire format for tag_set's on the wire: +// +// First, a tag set header: +// +// offset bytes description +// 0 1 version number +// 1 1 number of bytes in this header. This allows for future +// expansion. +// 2 1 number of bytes in each tag header. +// 3 1 ntags value from tag set. +// +// This is followed by the key/value memory from struct tag_set. + +#define ENCODED_VERSION 0 // Version number +#define ENCODED_HEADER_SIZE 4 // size of tag set header + +// Encode a tag set. Returns 0 if buffer is too small. +static size_t tag_set_encode(const struct tag_set *tags, char *buffer, + size_t buf_size) { + if (buf_size < ENCODED_HEADER_SIZE + tags->kvm_used) { + return 0; + } + buf_size -= ENCODED_HEADER_SIZE; + *buffer++ = (char)ENCODED_VERSION; + *buffer++ = (char)ENCODED_HEADER_SIZE; + *buffer++ = (char)TAG_HEADER_SIZE; + *buffer++ = (char)tags->ntags; + if (tags->ntags == 0) { + return ENCODED_HEADER_SIZE; + } + memcpy(buffer, tags->kvm, tags->kvm_used); + return ENCODED_HEADER_SIZE + tags->kvm_used; +} + +size_t census_context_encode(const census_context *context, char *buffer, + size_t buf_size) { + return tag_set_encode(&context->tags[PROPAGATED_TAGS], buffer, buf_size); +} + +// Decode a tag set. +static void tag_set_decode(struct tag_set *tags, const char *buffer, + size_t size) { + uint8_t version = (uint8_t)(*buffer++); + uint8_t header_size = (uint8_t)(*buffer++); + uint8_t tag_header_size = (uint8_t)(*buffer++); + tags->ntags = tags->ntags_alloc = (int)(*buffer++); + if (tags->ntags == 0) { + tags->ntags_alloc = 0; + tags->kvm_size = 0; + tags->kvm_used = 0; + tags->kvm = NULL; + return; + } + if (header_size != ENCODED_HEADER_SIZE) { + GPR_ASSERT(version != ENCODED_VERSION); + GPR_ASSERT(ENCODED_HEADER_SIZE < header_size); + buffer += (header_size - ENCODED_HEADER_SIZE); + } + tags->kvm_used = size - header_size; + tags->kvm_size = tags->kvm_used + CENSUS_MAX_TAG_KV_LEN; + tags->kvm = gpr_malloc(tags->kvm_size); + if (tag_header_size != TAG_HEADER_SIZE) { + // something new in the tag information. I don't understand it, so + // don't copy it over. + GPR_ASSERT(version != ENCODED_VERSION); + GPR_ASSERT(tag_header_size > TAG_HEADER_SIZE); + char *kvp = tags->kvm; + for (int i = 0; i < tags->ntags; i++) { + memcpy(kvp, buffer, TAG_HEADER_SIZE); + kvp += header_size; + struct raw_tag raw; + buffer = + decode_tag(&raw, (char *)buffer, tag_header_size - TAG_HEADER_SIZE); + memcpy(kvp, raw.key, (size_t)raw.key_len + raw.value_len); + kvp += raw.key_len + raw.value_len; + } + } else { + memcpy(tags->kvm, buffer, tags->kvm_used); + } +} + +census_context *census_context_decode(const char *buffer, size_t size) { + census_context *context = gpr_malloc(sizeof(census_context)); + memset(&context->tags[LOCAL_TAGS], 0, sizeof(struct tag_set)); + if (buffer == NULL) { + memset(&context->tags[PROPAGATED_TAGS], 0, sizeof(struct tag_set)); + } else { + tag_set_decode(&context->tags[PROPAGATED_TAGS], buffer, size); + } + memset(&context->status, 0, sizeof(context->status)); + context->status.n_propagated_tags = context->tags[PROPAGATED_TAGS].ntags; + return context; +} diff --git a/src/core/lib/census/grpc_context.c b/src/core/lib/census/grpc_context.c new file mode 100644 index 0000000000..457c176355 --- /dev/null +++ b/src/core/lib/census/grpc_context.c @@ -0,0 +1,53 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/census.h> +#include <grpc/grpc.h> +#include "src/core/lib/surface/api_trace.h" +#include "src/core/lib/surface/call.h" + +void grpc_census_call_set_context(grpc_call *call, census_context *context) { + GRPC_API_TRACE("grpc_census_call_set_context(call=%p, census_context=%p)", 2, + (call, context)); + if (census_enabled() == CENSUS_FEATURE_NONE) { + return; + } + if (context != NULL) { + grpc_call_context_set(call, GRPC_CONTEXT_TRACING, context, NULL); + } +} + +census_context *grpc_census_call_get_context(grpc_call *call) { + GRPC_API_TRACE("grpc_census_call_get_context(call=%p)", 1, (call)); + return (census_context *)grpc_call_context_get(call, GRPC_CONTEXT_TRACING); +} diff --git a/src/core/lib/census/grpc_filter.c b/src/core/lib/census/grpc_filter.c new file mode 100644 index 0000000000..d27d789aa1 --- /dev/null +++ b/src/core/lib/census/grpc_filter.c @@ -0,0 +1,198 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/census/grpc_filter.h" + +#include <stdio.h> +#include <string.h> + +#include <grpc/census.h> +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/slice.h> +#include <grpc/support/time.h> + +#include "src/core/lib/channel/channel_stack.h" +#include "src/core/lib/statistics/census_interface.h" +#include "src/core/lib/statistics/census_rpc_stats.h" +#include "src/core/lib/transport/static_metadata.h" + +typedef struct call_data { + census_op_id op_id; + census_context *ctxt; + gpr_timespec start_ts; + int error; + + /* recv callback */ + grpc_metadata_batch *recv_initial_metadata; + grpc_closure *on_done_recv; + grpc_closure finish_recv; +} call_data; + +typedef struct channel_data { uint8_t unused; } channel_data; + +static void extract_and_annotate_method_tag(grpc_metadata_batch *md, + call_data *calld, + channel_data *chand) { + grpc_linked_mdelem *m; + for (m = md->list.head; m != NULL; m = m->next) { + if (m->md->key == GRPC_MDSTR_PATH) { + gpr_log(GPR_DEBUG, "%s", + (const char *)GPR_SLICE_START_PTR(m->md->value->slice)); + /* Add method tag here */ + } + } +} + +static void client_mutate_op(grpc_call_element *elem, + grpc_transport_stream_op *op) { + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + if (op->send_initial_metadata) { + extract_and_annotate_method_tag(op->send_initial_metadata, calld, chand); + } +} + +static void client_start_transport_op(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem, + grpc_transport_stream_op *op) { + client_mutate_op(elem, op); + grpc_call_next_op(exec_ctx, elem, op); +} + +static void server_on_done_recv(grpc_exec_ctx *exec_ctx, void *ptr, + bool success) { + grpc_call_element *elem = ptr; + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + if (success) { + extract_and_annotate_method_tag(calld->recv_initial_metadata, calld, chand); + } + calld->on_done_recv->cb(exec_ctx, calld->on_done_recv->cb_arg, success); +} + +static void server_mutate_op(grpc_call_element *elem, + grpc_transport_stream_op *op) { + call_data *calld = elem->call_data; + if (op->recv_initial_metadata) { + /* substitute our callback for the op callback */ + calld->recv_initial_metadata = op->recv_initial_metadata; + calld->on_done_recv = op->recv_initial_metadata_ready; + op->recv_initial_metadata_ready = &calld->finish_recv; + } +} + +static void server_start_transport_op(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem, + grpc_transport_stream_op *op) { + /* TODO(ctiller): this code fails. I don't know why. I expect it's + incomplete, and someone should look at it soon. + + call_data *calld = elem->call_data; + GPR_ASSERT((calld->op_id.upper != 0) || (calld->op_id.lower != 0)); */ + server_mutate_op(elem, op); + grpc_call_next_op(exec_ctx, elem, op); +} + +static void client_init_call_elem(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem, + grpc_call_element_args *args) { + call_data *d = elem->call_data; + GPR_ASSERT(d != NULL); + memset(d, 0, sizeof(*d)); + d->start_ts = gpr_now(GPR_CLOCK_REALTIME); +} + +static void client_destroy_call_elem(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem) { + call_data *d = elem->call_data; + GPR_ASSERT(d != NULL); + /* TODO(hongyu): record rpc client stats and census_rpc_end_op here */ +} + +static void server_init_call_elem(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem, + grpc_call_element_args *args) { + call_data *d = elem->call_data; + GPR_ASSERT(d != NULL); + memset(d, 0, sizeof(*d)); + d->start_ts = gpr_now(GPR_CLOCK_REALTIME); + /* TODO(hongyu): call census_tracing_start_op here. */ + grpc_closure_init(&d->finish_recv, server_on_done_recv, elem); +} + +static void server_destroy_call_elem(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem) { + call_data *d = elem->call_data; + GPR_ASSERT(d != NULL); + /* TODO(hongyu): record rpc server stats and census_tracing_end_op here */ +} + +static void init_channel_elem(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem, + grpc_channel_element_args *args) { + channel_data *chand = elem->channel_data; + GPR_ASSERT(chand != NULL); +} + +static void destroy_channel_elem(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem) { + channel_data *chand = elem->channel_data; + GPR_ASSERT(chand != NULL); +} + +const grpc_channel_filter grpc_client_census_filter = { + client_start_transport_op, + grpc_channel_next_op, + sizeof(call_data), + client_init_call_elem, + grpc_call_stack_ignore_set_pollset, + client_destroy_call_elem, + sizeof(channel_data), + init_channel_elem, + destroy_channel_elem, + grpc_call_next_get_peer, + "census-client"}; + +const grpc_channel_filter grpc_server_census_filter = { + server_start_transport_op, + grpc_channel_next_op, + sizeof(call_data), + server_init_call_elem, + grpc_call_stack_ignore_set_pollset, + server_destroy_call_elem, + sizeof(channel_data), + init_channel_elem, + destroy_channel_elem, + grpc_call_next_get_peer, + "census-server"}; diff --git a/src/core/lib/census/grpc_filter.h b/src/core/lib/census/grpc_filter.h new file mode 100644 index 0000000000..7ceafe56e4 --- /dev/null +++ b/src/core/lib/census/grpc_filter.h @@ -0,0 +1,44 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CENSUS_GRPC_FILTER_H +#define GRPC_CORE_LIB_CENSUS_GRPC_FILTER_H + +#include "src/core/lib/channel/channel_stack.h" + +/* Census filters: provides tracing and stats collection functionalities. It + needs to reside right below the surface filter in the channel stack. */ +extern const grpc_channel_filter grpc_client_census_filter; +extern const grpc_channel_filter grpc_server_census_filter; + +#endif /* GRPC_CORE_LIB_CENSUS_GRPC_FILTER_H */ diff --git a/src/core/lib/census/grpc_plugin.c b/src/core/lib/census/grpc_plugin.c new file mode 100644 index 0000000000..12aca76745 --- /dev/null +++ b/src/core/lib/census/grpc_plugin.c @@ -0,0 +1,70 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/census/grpc_plugin.h" + +#include <limits.h> + +#include <grpc/census.h> + +#include "src/core/lib/census/grpc_filter.h" +#include "src/core/lib/channel/channel_stack_builder.h" +#include "src/core/lib/surface/channel_init.h" + +static bool maybe_add_census_filter(grpc_channel_stack_builder *builder, + void *arg_must_be_null) { + const grpc_channel_args *args = + grpc_channel_stack_builder_get_channel_arguments(builder); + if (grpc_channel_args_is_census_enabled(args)) { + return grpc_channel_stack_builder_prepend_filter( + builder, &grpc_client_census_filter, NULL, NULL); + } + return true; +} + +void census_grpc_plugin_init(void) { + /* Only initialize census if no one else has and some features are + * available. */ + if (census_enabled() == CENSUS_FEATURE_NONE && + census_supported() != CENSUS_FEATURE_NONE) { + if (census_initialize(census_supported())) { /* enable all features. */ + gpr_log(GPR_ERROR, "Could not initialize census."); + } + } + grpc_channel_init_register_stage(GRPC_CLIENT_CHANNEL, INT_MAX, + maybe_add_census_filter, NULL); + grpc_channel_init_register_stage(GRPC_SERVER_CHANNEL, INT_MAX, + maybe_add_census_filter, NULL); +} + +void census_grpc_plugin_destroy(void) { census_shutdown(); } diff --git a/src/core/lib/census/grpc_plugin.h b/src/core/lib/census/grpc_plugin.h new file mode 100644 index 0000000000..33e5f0b701 --- /dev/null +++ b/src/core/lib/census/grpc_plugin.h @@ -0,0 +1,40 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CENSUS_GRPC_PLUGIN_H +#define GRPC_CORE_LIB_CENSUS_GRPC_PLUGIN_H + +void census_grpc_plugin_init(void); +void census_grpc_plugin_destroy(void); + +#endif /* GRPC_CORE_LIB_CENSUS_GRPC_PLUGIN_H */ diff --git a/src/core/lib/census/initialize.c b/src/core/lib/census/initialize.c new file mode 100644 index 0000000000..ce7ec09b89 --- /dev/null +++ b/src/core/lib/census/initialize.c @@ -0,0 +1,54 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/census.h> + +static int features_enabled = CENSUS_FEATURE_NONE; + +int census_initialize(int features) { + if (features_enabled != CENSUS_FEATURE_NONE) { + // Must have been a previous call to census_initialize; return error + return 1; + } + features_enabled = features; + return 0; +} + +void census_shutdown(void) { features_enabled = CENSUS_FEATURE_NONE; } + +int census_supported(void) { + /* TODO(aveitch): improve this as we implement features... */ + return CENSUS_FEATURE_NONE; +} + +int census_enabled(void) { return features_enabled; } diff --git a/src/core/lib/census/mlog.c b/src/core/lib/census/mlog.c new file mode 100644 index 0000000000..9d47e80297 --- /dev/null +++ b/src/core/lib/census/mlog.c @@ -0,0 +1,600 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +// Implements an efficient in-memory log, optimized for multiple writers and +// a single reader. Available log space is divided up in blocks of +// CENSUS_LOG_2_MAX_RECORD_SIZE bytes. A block can be in one of the following +// three data structures: +// - Free blocks (free_block_list) +// - Blocks with unread data (dirty_block_list) +// - Blocks currently attached to cores (core_local_blocks[]) +// +// census_log_start_write() moves a block from core_local_blocks[] to the end of +// dirty_block_list when block: +// - is out-of-space OR +// - has an incomplete record (an incomplete record occurs when a thread calls +// census_log_start_write() and is context-switched before calling +// census_log_end_write() +// So, blocks in dirty_block_list are ordered, from oldest to newest, by the +// time when block is detached from the core. +// +// census_log_read_next() first iterates over dirty_block_list and then +// core_local_blocks[]. It moves completely read blocks from dirty_block_list +// to free_block_list. Blocks in core_local_blocks[] are not freed, even when +// completely read. +// +// If the log is configured to discard old records and free_block_list is empty, +// census_log_start_write() iterates over dirty_block_list to allocate a +// new block. It moves the oldest available block (no pending read/write) to +// core_local_blocks[]. +// +// core_local_block_struct is used to implement a map from core id to the block +// associated with that core. This mapping is advisory. It is possible that the +// block returned by this mapping is no longer associated with that core. This +// mapping is updated, lazily, by census_log_start_write(). +// +// Locking in block struct: +// +// Exclusive g_log.lock must be held before calling any functions operating on +// block structs except census_log_start_write() and census_log_end_write(). +// +// Writes to a block are serialized via writer_lock. census_log_start_write() +// acquires this lock and census_log_end_write() releases it. On failure to +// acquire the lock, writer allocates a new block for the current core and +// updates core_local_block accordingly. +// +// Simultaneous read and write access is allowed. Readers can safely read up to +// committed bytes (bytes_committed). +// +// reader_lock protects the block, currently being read, from getting recycled. +// start_read() acquires reader_lock and end_read() releases the lock. +// +// Read/write access to a block is disabled via try_disable_access(). It returns +// with both writer_lock and reader_lock held. These locks are subsequently +// released by enable_access() to enable access to the block. +// +// A note on naming: Most function/struct names are prepended by cl_ +// (shorthand for census_log). Further, functions that manipulate structures +// include the name of the structure, which will be passed as the first +// argument. E.g. cl_block_initialize() will initialize a cl_block. + +#include "src/core/lib/census/mlog.h" +#include <grpc/support/alloc.h> +#include <grpc/support/atm.h> +#include <grpc/support/cpu.h> +#include <grpc/support/log.h> +#include <grpc/support/sync.h> +#include <grpc/support/useful.h> +#include <stdbool.h> +#include <string.h> + +// End of platform specific code + +typedef struct census_log_block_list_struct { + struct census_log_block_list_struct* next; + struct census_log_block_list_struct* prev; + struct census_log_block* block; +} cl_block_list_struct; + +typedef struct census_log_block { + // Pointer to underlying buffer. + char* buffer; + gpr_atm writer_lock; + gpr_atm reader_lock; + // Keeps completely written bytes. Declared atomic because accessed + // simultaneously by reader and writer. + gpr_atm bytes_committed; + // Bytes already read. + size_t bytes_read; + // Links for list. + cl_block_list_struct link; +// We want this structure to be cacheline aligned. We assume the following +// sizes for the various parts on 32/64bit systems: +// type 32b size 64b size +// char* 4 8 +// 3x gpr_atm 12 24 +// size_t 4 8 +// cl_block_list_struct 12 24 +// TOTAL 32 64 +// +// Depending on the size of our cacheline and the architecture, we +// selectively add char buffering to this structure. The size is checked +// via assert in census_log_initialize(). +#if defined(GPR_ARCH_64) +#define CL_BLOCK_PAD_SIZE (GPR_CACHELINE_SIZE - 64) +#else +#if defined(GPR_ARCH_32) +#define CL_BLOCK_PAD_SIZE (GPR_CACHELINE_SIZE - 32) +#else +#error "Unknown architecture" +#endif +#endif +#if CL_BLOCK_PAD_SIZE > 0 + char padding[CL_BLOCK_PAD_SIZE]; +#endif +} cl_block; + +// A list of cl_blocks, doubly-linked through cl_block::link. +typedef struct census_log_block_list { + int32_t count; // Number of items in list. + cl_block_list_struct ht; // head/tail of linked list. +} cl_block_list; + +// Cacheline aligned block pointers to avoid false sharing. Block pointer must +// be initialized via set_block(), before calling other functions +typedef struct census_log_core_local_block { + gpr_atm block; +// Ensure cachline alignment: we assume sizeof(gpr_atm) == 4 or 8 +#if defined(GPR_ARCH_64) +#define CL_CORE_LOCAL_BLOCK_PAD_SIZE (GPR_CACHELINE_SIZE - 8) +#else +#if defined(GPR_ARCH_32) +#define CL_CORE_LOCAL_BLOCK_PAD_SIZE (GPR_CACHELINE_SIZE - 4) +#else +#error "Unknown architecture" +#endif +#endif +#if CL_CORE_LOCAL_BLOCK_PAD_SIZE > 0 + char padding[CL_CORE_LOCAL_BLOCK_PAD_SIZE]; +#endif +} cl_core_local_block; + +struct census_log { + int discard_old_records; + // Number of cores (aka hardware-contexts) + unsigned num_cores; + // number of CENSUS_LOG_2_MAX_RECORD_SIZE blocks in log + uint32_t num_blocks; + cl_block* blocks; // Block metadata. + cl_core_local_block* core_local_blocks; // Keeps core to block mappings. + gpr_mu lock; + int initialized; // has log been initialized? + // Keeps the state of the reader iterator. A value of 0 indicates that + // iterator has reached the end. census_log_init_reader() resets the value + // to num_core to restart iteration. + uint32_t read_iterator_state; + // Points to the block being read. If non-NULL, the block is locked for + // reading(block_being_read_->reader_lock is held). + cl_block* block_being_read; + char* buffer; + cl_block_list free_block_list; + cl_block_list dirty_block_list; + gpr_atm out_of_space_count; +}; + +// Single internal log. +static struct census_log g_log; + +// Functions that operate on an atomic memory location used as a lock. + +// Returns non-zero if lock is acquired. +static int cl_try_lock(gpr_atm* lock) { return gpr_atm_acq_cas(lock, 0, 1); } + +static void cl_unlock(gpr_atm* lock) { gpr_atm_rel_store(lock, 0); } + +// Functions that operate on cl_core_local_block's. + +static void cl_core_local_block_set_block(cl_core_local_block* clb, + cl_block* block) { + gpr_atm_rel_store(&clb->block, (gpr_atm)block); +} + +static cl_block* cl_core_local_block_get_block(cl_core_local_block* clb) { + return (cl_block*)gpr_atm_acq_load(&clb->block); +} + +// Functions that operate on cl_block_list_struct's. + +static void cl_block_list_struct_initialize(cl_block_list_struct* bls, + cl_block* block) { + bls->next = bls->prev = bls; + bls->block = block; +} + +// Functions that operate on cl_block_list's. + +static void cl_block_list_initialize(cl_block_list* list) { + list->count = 0; + cl_block_list_struct_initialize(&list->ht, NULL); +} + +// Returns head of *this, or NULL if empty. +static cl_block* cl_block_list_head(cl_block_list* list) { + return list->ht.next->block; +} + +// Insert element *e after *pos. +static void cl_block_list_insert(cl_block_list* list, cl_block_list_struct* pos, + cl_block_list_struct* e) { + list->count++; + e->next = pos->next; + e->prev = pos; + e->next->prev = e; + e->prev->next = e; +} + +// Insert block at the head of the list +static void cl_block_list_insert_at_head(cl_block_list* list, cl_block* block) { + cl_block_list_insert(list, &list->ht, &block->link); +} + +// Insert block at the tail of the list. +static void cl_block_list_insert_at_tail(cl_block_list* list, cl_block* block) { + cl_block_list_insert(list, list->ht.prev, &block->link); +} + +// Removes block *b. Requires *b be in the list. +static void cl_block_list_remove(cl_block_list* list, cl_block* b) { + list->count--; + b->link.next->prev = b->link.prev; + b->link.prev->next = b->link.next; +} + +// Functions that operate on cl_block's + +static void cl_block_initialize(cl_block* block, char* buffer) { + block->buffer = buffer; + gpr_atm_rel_store(&block->writer_lock, 0); + gpr_atm_rel_store(&block->reader_lock, 0); + gpr_atm_rel_store(&block->bytes_committed, 0); + block->bytes_read = 0; + cl_block_list_struct_initialize(&block->link, block); +} + +// Guards against exposing partially written buffer to the reader. +static void cl_block_set_bytes_committed(cl_block* block, + size_t bytes_committed) { + gpr_atm_rel_store(&block->bytes_committed, (gpr_atm)bytes_committed); +} + +static size_t cl_block_get_bytes_committed(cl_block* block) { + return (size_t)gpr_atm_acq_load(&block->bytes_committed); +} + +// Tries to disable future read/write access to this block. Succeeds if: +// - no in-progress write AND +// - no in-progress read AND +// - 'discard_data' set to true OR no unread data +// On success, clears the block state and returns with writer_lock_ and +// reader_lock_ held. These locks are released by a subsequent +// cl_block_access_enable() call. +static bool cl_block_try_disable_access(cl_block* block, int discard_data) { + if (!cl_try_lock(&block->writer_lock)) { + return false; + } + if (!cl_try_lock(&block->reader_lock)) { + cl_unlock(&block->writer_lock); + return false; + } + if (!discard_data && + (block->bytes_read != cl_block_get_bytes_committed(block))) { + cl_unlock(&block->reader_lock); + cl_unlock(&block->writer_lock); + return false; + } + cl_block_set_bytes_committed(block, 0); + block->bytes_read = 0; + return true; +} + +static void cl_block_enable_access(cl_block* block) { + cl_unlock(&block->reader_lock); + cl_unlock(&block->writer_lock); +} + +// Returns with writer_lock held. +static void* cl_block_start_write(cl_block* block, size_t size) { + if (!cl_try_lock(&block->writer_lock)) { + return NULL; + } + size_t bytes_committed = cl_block_get_bytes_committed(block); + if (bytes_committed + size > CENSUS_LOG_MAX_RECORD_SIZE) { + cl_unlock(&block->writer_lock); + return NULL; + } + return block->buffer + bytes_committed; +} + +// Releases writer_lock and increments committed bytes by 'bytes_written'. +// 'bytes_written' must be <= 'size' specified in the corresponding +// StartWrite() call. This function is thread-safe. +static void cl_block_end_write(cl_block* block, size_t bytes_written) { + cl_block_set_bytes_committed( + block, cl_block_get_bytes_committed(block) + bytes_written); + cl_unlock(&block->writer_lock); +} + +// Returns a pointer to the first unread byte in buffer. The number of bytes +// available are returned in 'bytes_available'. Acquires reader lock that is +// released by a subsequent cl_block_end_read() call. Returns NULL if: +// - read in progress +// - no data available +static void* cl_block_start_read(cl_block* block, size_t* bytes_available) { + if (!cl_try_lock(&block->reader_lock)) { + return NULL; + } + // bytes_committed may change from under us. Use bytes_available to update + // bytes_read below. + size_t bytes_committed = cl_block_get_bytes_committed(block); + GPR_ASSERT(bytes_committed >= block->bytes_read); + *bytes_available = bytes_committed - block->bytes_read; + if (*bytes_available == 0) { + cl_unlock(&block->reader_lock); + return NULL; + } + void* record = block->buffer + block->bytes_read; + block->bytes_read += *bytes_available; + return record; +} + +static void cl_block_end_read(cl_block* block) { + cl_unlock(&block->reader_lock); +} + +// Internal functions operating on g_log + +// Allocates a new free block (or recycles an available dirty block if log is +// configured to discard old records). Returns NULL if out-of-space. +static cl_block* cl_allocate_block(void) { + cl_block* block = cl_block_list_head(&g_log.free_block_list); + if (block != NULL) { + cl_block_list_remove(&g_log.free_block_list, block); + return block; + } + if (!g_log.discard_old_records) { + // No free block and log is configured to keep old records. + return NULL; + } + // Recycle dirty block. Start from the oldest. + for (block = cl_block_list_head(&g_log.dirty_block_list); block != NULL; + block = block->link.next->block) { + if (cl_block_try_disable_access(block, 1 /* discard data */)) { + cl_block_list_remove(&g_log.dirty_block_list, block); + return block; + } + } + return NULL; +} + +// Allocates a new block and updates core id => block mapping. 'old_block' +// points to the block that the caller thinks is attached to +// 'core_id'. 'old_block' may be NULL. Returns true if: +// - allocated a new block OR +// - 'core_id' => 'old_block' mapping changed (another thread allocated a +// block before lock was acquired). +static bool cl_allocate_core_local_block(uint32_t core_id, + cl_block* old_block) { + // Now that we have the lock, check if core-local mapping has changed. + cl_core_local_block* core_local_block = &g_log.core_local_blocks[core_id]; + cl_block* block = cl_core_local_block_get_block(core_local_block); + if ((block != NULL) && (block != old_block)) { + return true; + } + if (block != NULL) { + cl_core_local_block_set_block(core_local_block, NULL); + cl_block_list_insert_at_tail(&g_log.dirty_block_list, block); + } + block = cl_allocate_block(); + if (block == NULL) { + return false; + } + cl_core_local_block_set_block(core_local_block, block); + cl_block_enable_access(block); + return true; +} + +static cl_block* cl_get_block(void* record) { + uintptr_t p = (uintptr_t)((char*)record - g_log.buffer); + uintptr_t index = p >> CENSUS_LOG_2_MAX_RECORD_SIZE; + return &g_log.blocks[index]; +} + +// Gets the next block to read and tries to free 'prev' block (if not NULL). +// Returns NULL if reached the end. +static cl_block* cl_next_block_to_read(cl_block* prev) { + cl_block* block = NULL; + if (g_log.read_iterator_state == g_log.num_cores) { + // We are traversing dirty list; find the next dirty block. + if (prev != NULL) { + // Try to free the previous block if there is no unread data. This + // block + // may have unread data if previously incomplete record completed + // between + // read_next() calls. + block = prev->link.next->block; + if (cl_block_try_disable_access(prev, 0 /* do not discard data */)) { + cl_block_list_remove(&g_log.dirty_block_list, prev); + cl_block_list_insert_at_head(&g_log.free_block_list, prev); + } + } else { + block = cl_block_list_head(&g_log.dirty_block_list); + } + if (block != NULL) { + return block; + } + // We are done with the dirty list; moving on to core-local blocks. + } + while (g_log.read_iterator_state > 0) { + g_log.read_iterator_state--; + block = cl_core_local_block_get_block( + &g_log.core_local_blocks[g_log.read_iterator_state]); + if (block != NULL) { + return block; + } + } + return NULL; +} + +#define CL_LOG_2_MB 20 // 2^20 = 1MB + +// External functions: primary stats_log interface +void census_log_initialize(size_t size_in_mb, int discard_old_records) { + // Check cacheline alignment. + GPR_ASSERT(sizeof(cl_block) % GPR_CACHELINE_SIZE == 0); + GPR_ASSERT(sizeof(cl_core_local_block) % GPR_CACHELINE_SIZE == 0); + GPR_ASSERT(!g_log.initialized); + g_log.discard_old_records = discard_old_records; + g_log.num_cores = gpr_cpu_num_cores(); + // Ensure that we will not get any overflow in calaculating num_blocks + GPR_ASSERT(CL_LOG_2_MB >= CENSUS_LOG_2_MAX_RECORD_SIZE); + GPR_ASSERT(size_in_mb < 1000); + // Ensure at least 2x as many blocks as there are cores. + g_log.num_blocks = + (uint32_t)GPR_MAX(2 * g_log.num_cores, (size_in_mb << CL_LOG_2_MB) >> + CENSUS_LOG_2_MAX_RECORD_SIZE); + gpr_mu_init(&g_log.lock); + g_log.read_iterator_state = 0; + g_log.block_being_read = NULL; + g_log.core_local_blocks = (cl_core_local_block*)gpr_malloc_aligned( + g_log.num_cores * sizeof(cl_core_local_block), GPR_CACHELINE_SIZE_LOG); + memset(g_log.core_local_blocks, 0, + g_log.num_cores * sizeof(cl_core_local_block)); + g_log.blocks = (cl_block*)gpr_malloc_aligned( + g_log.num_blocks * sizeof(cl_block), GPR_CACHELINE_SIZE_LOG); + memset(g_log.blocks, 0, g_log.num_blocks * sizeof(cl_block)); + g_log.buffer = gpr_malloc(g_log.num_blocks * CENSUS_LOG_MAX_RECORD_SIZE); + memset(g_log.buffer, 0, g_log.num_blocks * CENSUS_LOG_MAX_RECORD_SIZE); + cl_block_list_initialize(&g_log.free_block_list); + cl_block_list_initialize(&g_log.dirty_block_list); + for (uint32_t i = 0; i < g_log.num_blocks; ++i) { + cl_block* block = g_log.blocks + i; + cl_block_initialize(block, g_log.buffer + (CENSUS_LOG_MAX_RECORD_SIZE * i)); + cl_block_try_disable_access(block, 1 /* discard data */); + cl_block_list_insert_at_tail(&g_log.free_block_list, block); + } + gpr_atm_rel_store(&g_log.out_of_space_count, 0); + g_log.initialized = 1; +} + +void census_log_shutdown(void) { + GPR_ASSERT(g_log.initialized); + gpr_mu_destroy(&g_log.lock); + gpr_free_aligned(g_log.core_local_blocks); + g_log.core_local_blocks = NULL; + gpr_free_aligned(g_log.blocks); + g_log.blocks = NULL; + gpr_free(g_log.buffer); + g_log.buffer = NULL; + g_log.initialized = 0; +} + +void* census_log_start_write(size_t size) { + // Used to bound number of times block allocation is attempted. + GPR_ASSERT(size > 0); + GPR_ASSERT(g_log.initialized); + if (size > CENSUS_LOG_MAX_RECORD_SIZE) { + return NULL; + } + uint32_t attempts_remaining = g_log.num_blocks; + uint32_t core_id = gpr_cpu_current_cpu(); + do { + void* record = NULL; + cl_block* block = + cl_core_local_block_get_block(&g_log.core_local_blocks[core_id]); + if (block && (record = cl_block_start_write(block, size))) { + return record; + } + // Need to allocate a new block. We are here if: + // - No block associated with the core OR + // - Write in-progress on the block OR + // - block is out of space + gpr_mu_lock(&g_log.lock); + bool allocated = cl_allocate_core_local_block(core_id, block); + gpr_mu_unlock(&g_log.lock); + if (!allocated) { + gpr_atm_no_barrier_fetch_add(&g_log.out_of_space_count, 1); + return NULL; + } + } while (attempts_remaining--); + // Give up. + gpr_atm_no_barrier_fetch_add(&g_log.out_of_space_count, 1); + return NULL; +} + +void census_log_end_write(void* record, size_t bytes_written) { + GPR_ASSERT(g_log.initialized); + cl_block_end_write(cl_get_block(record), bytes_written); +} + +void census_log_init_reader(void) { + GPR_ASSERT(g_log.initialized); + gpr_mu_lock(&g_log.lock); + // If a block is locked for reading unlock it. + if (g_log.block_being_read != NULL) { + cl_block_end_read(g_log.block_being_read); + g_log.block_being_read = NULL; + } + g_log.read_iterator_state = g_log.num_cores; + gpr_mu_unlock(&g_log.lock); +} + +const void* census_log_read_next(size_t* bytes_available) { + GPR_ASSERT(g_log.initialized); + gpr_mu_lock(&g_log.lock); + if (g_log.block_being_read != NULL) { + cl_block_end_read(g_log.block_being_read); + } + do { + g_log.block_being_read = cl_next_block_to_read(g_log.block_being_read); + if (g_log.block_being_read != NULL) { + void* record = + cl_block_start_read(g_log.block_being_read, bytes_available); + if (record != NULL) { + gpr_mu_unlock(&g_log.lock); + return record; + } + } + } while (g_log.block_being_read != NULL); + gpr_mu_unlock(&g_log.lock); + return NULL; +} + +size_t census_log_remaining_space(void) { + GPR_ASSERT(g_log.initialized); + size_t space = 0; + gpr_mu_lock(&g_log.lock); + if (g_log.discard_old_records) { + // Remaining space is not meaningful; just return the entire log space. + space = g_log.num_blocks << CENSUS_LOG_2_MAX_RECORD_SIZE; + } else { + GPR_ASSERT(g_log.free_block_list.count >= 0); + space = (size_t)g_log.free_block_list.count * CENSUS_LOG_MAX_RECORD_SIZE; + } + gpr_mu_unlock(&g_log.lock); + return space; +} + +int64_t census_log_out_of_space_count(void) { + GPR_ASSERT(g_log.initialized); + return gpr_atm_acq_load(&g_log.out_of_space_count); +} diff --git a/src/core/lib/census/mlog.h b/src/core/lib/census/mlog.h new file mode 100644 index 0000000000..7fbdeda986 --- /dev/null +++ b/src/core/lib/census/mlog.h @@ -0,0 +1,95 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* A very fast in-memory log, optimized for multiple writers. */ + +#ifndef GRPC_CORE_LIB_CENSUS_MLOG_H +#define GRPC_CORE_LIB_CENSUS_MLOG_H + +#include <grpc/support/port_platform.h> +#include <stddef.h> + +/* Maximum record size, in bytes. */ +#define CENSUS_LOG_2_MAX_RECORD_SIZE 14 /* 2^14 = 16KB */ +#define CENSUS_LOG_MAX_RECORD_SIZE (1 << CENSUS_LOG_2_MAX_RECORD_SIZE) + +/* Initialize the statistics logging subsystem with the given log size. A log + size of 0 will result in the smallest possible log for the platform + (approximately CENSUS_LOG_MAX_RECORD_SIZE * gpr_cpu_num_cores()). If + discard_old_records is non-zero, then new records will displace older ones + when the log is full. This function must be called before any other + census_log functions. +*/ +void census_log_initialize(size_t size_in_mb, int discard_old_records); + +/* Shutdown the logging subsystem. Caller must ensure that: + - no in progress or future call to any census_log functions + - no incomplete records +*/ +void census_log_shutdown(void); + +/* Allocates and returns a 'size' bytes record and marks it in use. A + subsequent census_log_end_write() marks the record complete. The + 'bytes_written' census_log_end_write() argument must be <= + 'size'. Returns NULL if out-of-space AND: + - log is configured to keep old records OR + - all blocks are pinned by incomplete records. +*/ +void* census_log_start_write(size_t size); + +void census_log_end_write(void* record, size_t bytes_written); + +void census_log_init_reader(void); + +/* census_log_read_next() iterates over blocks with data and for each block + returns a pointer to the first unread byte. The number of bytes that can be + read are returned in 'bytes_available'. Reader is expected to read all + available data. Reading the data consumes it i.e. it cannot be read again. + census_log_read_next() returns NULL if the end is reached i.e last block + is read. census_log_init_reader() starts the iteration or aborts the + current iteration. +*/ +const void* census_log_read_next(size_t* bytes_available); + +/* Returns estimated remaining space across all blocks, in bytes. If log is + configured to discard old records, returns total log space. Otherwise, + returns space available in empty blocks (partially filled blocks are + treated as full). +*/ +size_t census_log_remaining_space(void); + +/* Returns the number of times gprc_stats_log_start_write() failed due to + out-of-space. */ +int64_t census_log_out_of_space_count(void); + +#endif /* GRPC_CORE_LIB_CENSUS_MLOG_H */ diff --git a/src/core/lib/census/operation.c b/src/core/lib/census/operation.c new file mode 100644 index 0000000000..315f9c3534 --- /dev/null +++ b/src/core/lib/census/operation.c @@ -0,0 +1,63 @@ +/* + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/census.h> + +/* TODO(aveitch): These are all placeholder implementations. */ + +census_timestamp census_start_rpc_op_timestamp(void) { + census_timestamp ct; + /* TODO(aveitch): assumes gpr_timespec implementation of census_timestamp. */ + ct.ts = gpr_now(GPR_CLOCK_MONOTONIC); + return ct; +} + +census_context *census_start_client_rpc_op( + const census_context *context, int64_t rpc_name_id, + const census_rpc_name_info *rpc_name_info, const char *peer, int trace_mask, + const census_timestamp *start_time) { + return NULL; +} + +census_context *census_start_server_rpc_op( + const char *buffer, int64_t rpc_name_id, + const census_rpc_name_info *rpc_name_info, const char *peer, int trace_mask, + census_timestamp *start_time) { + return NULL; +} + +census_context *census_start_op(census_context *context, const char *family, + const char *name, int trace_mask) { + return NULL; +} + +void census_end_op(census_context *context, int status) {} diff --git a/src/core/lib/census/placeholders.c b/src/core/lib/census/placeholders.c new file mode 100644 index 0000000000..fe23d13971 --- /dev/null +++ b/src/core/lib/census/placeholders.c @@ -0,0 +1,109 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/census.h> + +#include <grpc/support/log.h> + +/* Placeholders for the pending APIs */ + +int census_get_trace_record(census_trace_record *trace_record) { + (void)trace_record; + abort(); +} + +void census_record_values(census_context *context, census_value *values, + size_t nvalues) { + (void)context; + (void)values; + (void)nvalues; + abort(); +} + +void census_set_rpc_client_peer(census_context *context, const char *peer) { + (void)context; + (void)peer; + abort(); +} + +void census_trace_scan_end() { abort(); } + +int census_trace_scan_start(int consume) { + (void)consume; + abort(); +} + +const census_aggregation *census_view_aggregrations(const census_view *view) { + (void)view; + abort(); +} + +census_view *census_view_create(uint32_t metric_id, const census_context *tags, + const census_aggregation *aggregations, + size_t naggregations) { + (void)metric_id; + (void)tags; + (void)aggregations; + (void)naggregations; + abort(); +} + +const census_context *census_view_tags(const census_view *view) { + (void)view; + abort(); +} + +void census_view_delete(census_view *view) { + (void)view; + abort(); +} + +const census_view_data *census_view_get_data(const census_view *view) { + (void)view; + abort(); +} + +size_t census_view_metric(const census_view *view) { + (void)view; + abort(); +} + +size_t census_view_naggregations(const census_view *view) { + (void)view; + abort(); +} + +void census_view_reset(census_view *view) { + (void)view; + abort(); +} diff --git a/src/core/lib/census/rpc_metric_id.h b/src/core/lib/census/rpc_metric_id.h new file mode 100644 index 0000000000..aad0588fb3 --- /dev/null +++ b/src/core/lib/census/rpc_metric_id.h @@ -0,0 +1,51 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CENSUS_RPC_METRIC_ID_H +#define GRPC_CORE_LIB_CENSUS_RPC_METRIC_ID_H + +/* Metric ID's used for RPC measurements. */ +/* Count of client requests sent. */ +#define CENSUS_METRIC_RPC_CLIENT_REQUESTS ((uint32_t)0) +/* Count of server requests sent. */ +#define CENSUS_METRIC_RPC_SERVER_REQUESTS ((uint32_t)1) +/* Client error counts. */ +#define CENSUS_METRIC_RPC_CLIENT_ERRORS ((uint32_t)2) +/* Server error counts. */ +#define CENSUS_METRIC_RPC_SERVER_ERRORS ((uint32_t)3) +/* Client side request latency. */ +#define CENSUS_METRIC_RPC_CLIENT_LATENCY ((uint32_t)4) +/* Server side request latency. */ +#define CENSUS_METRIC_RPC_SERVER_LATENCY ((uint32_t)5) + +#endif /* GRPC_CORE_LIB_CENSUS_RPC_METRIC_ID_H */ diff --git a/src/core/lib/census/tracing.c b/src/core/lib/census/tracing.c new file mode 100644 index 0000000000..e508996af3 --- /dev/null +++ b/src/core/lib/census/tracing.c @@ -0,0 +1,45 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/census.h> + +/* TODO(aveitch): These are all placeholder implementations. */ + +int census_trace_mask(const census_context *context) { + return CENSUS_TRACE_MASK_NONE; +} + +void census_set_trace_mask(int trace_mask) {} + +void census_trace_print(census_context *context, uint32_t type, + const char *buffer, size_t n) {} diff --git a/src/core/lib/channel/channel_args.c b/src/core/lib/channel/channel_args.c new file mode 100644 index 0000000000..1a02f1f4aa --- /dev/null +++ b/src/core/lib/channel/channel_args.c @@ -0,0 +1,271 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/channel/channel_args.h" +#include <grpc/grpc.h> +#include "src/core/lib/support/string.h" + +#include <grpc/census.h> +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> +#include <grpc/support/useful.h> + +#include <string.h> + +static grpc_arg copy_arg(const grpc_arg *src) { + grpc_arg dst; + dst.type = src->type; + dst.key = gpr_strdup(src->key); + switch (dst.type) { + case GRPC_ARG_STRING: + dst.value.string = gpr_strdup(src->value.string); + break; + case GRPC_ARG_INTEGER: + dst.value.integer = src->value.integer; + break; + case GRPC_ARG_POINTER: + dst.value.pointer = src->value.pointer; + dst.value.pointer.p = + src->value.pointer.vtable->copy(src->value.pointer.p); + break; + } + return dst; +} + +grpc_channel_args *grpc_channel_args_copy_and_add(const grpc_channel_args *src, + const grpc_arg *to_add, + size_t num_to_add) { + grpc_channel_args *dst = gpr_malloc(sizeof(grpc_channel_args)); + size_t i; + size_t src_num_args = (src == NULL) ? 0 : src->num_args; + if (!src && !to_add) { + dst->num_args = 0; + dst->args = NULL; + return dst; + } + dst->num_args = src_num_args + num_to_add; + dst->args = gpr_malloc(sizeof(grpc_arg) * dst->num_args); + for (i = 0; i < src_num_args; i++) { + dst->args[i] = copy_arg(&src->args[i]); + } + for (i = 0; i < num_to_add; i++) { + dst->args[i + src_num_args] = copy_arg(&to_add[i]); + } + return dst; +} + +grpc_channel_args *grpc_channel_args_copy(const grpc_channel_args *src) { + return grpc_channel_args_copy_and_add(src, NULL, 0); +} + +grpc_channel_args *grpc_channel_args_merge(const grpc_channel_args *a, + const grpc_channel_args *b) { + return grpc_channel_args_copy_and_add(a, b->args, b->num_args); +} + +static int cmp_arg(const grpc_arg *a, const grpc_arg *b) { + int c = GPR_ICMP(a->type, b->type); + if (c != 0) return c; + c = strcmp(a->key, b->key); + if (c != 0) return c; + switch (a->type) { + case GRPC_ARG_STRING: + return strcmp(a->value.string, b->value.string); + case GRPC_ARG_INTEGER: + return GPR_ICMP(a->value.integer, b->value.integer); + case GRPC_ARG_POINTER: + c = GPR_ICMP(a->value.pointer.p, b->value.pointer.p); + if (c != 0) { + c = GPR_ICMP(a->value.pointer.vtable, b->value.pointer.vtable); + if (c == 0) { + c = a->value.pointer.vtable->cmp(a->value.pointer.p, + b->value.pointer.p); + } + } + return c; + } + GPR_UNREACHABLE_CODE(return 0); +} + +/* stabilizing comparison function: since channel_args ordering matters for + * keys with the same name, we need to preserve that ordering */ +static int cmp_key_stable(const void *ap, const void *bp) { + const grpc_arg *const *a = ap; + const grpc_arg *const *b = bp; + int c = strcmp((*a)->key, (*b)->key); + if (c == 0) c = GPR_ICMP(*a, *b); + return c; +} + +grpc_channel_args *grpc_channel_args_normalize(const grpc_channel_args *a) { + grpc_arg **args = gpr_malloc(sizeof(grpc_arg *) * a->num_args); + for (size_t i = 0; i < a->num_args; i++) { + args[i] = &a->args[i]; + } + qsort(args, a->num_args, sizeof(grpc_arg *), cmp_key_stable); + + grpc_channel_args *b = gpr_malloc(sizeof(grpc_channel_args)); + b->num_args = a->num_args; + b->args = gpr_malloc(sizeof(grpc_arg) * b->num_args); + for (size_t i = 0; i < a->num_args; i++) { + b->args[i] = copy_arg(args[i]); + } + + gpr_free(args); + return b; +} + +void grpc_channel_args_destroy(grpc_channel_args *a) { + size_t i; + for (i = 0; i < a->num_args; i++) { + switch (a->args[i].type) { + case GRPC_ARG_STRING: + gpr_free(a->args[i].value.string); + break; + case GRPC_ARG_INTEGER: + break; + case GRPC_ARG_POINTER: + a->args[i].value.pointer.vtable->destroy(a->args[i].value.pointer.p); + break; + } + gpr_free(a->args[i].key); + } + gpr_free(a->args); + gpr_free(a); +} + +int grpc_channel_args_is_census_enabled(const grpc_channel_args *a) { + size_t i; + if (a == NULL) return 0; + for (i = 0; i < a->num_args; i++) { + if (0 == strcmp(a->args[i].key, GRPC_ARG_ENABLE_CENSUS)) { + return a->args[i].value.integer != 0 && census_enabled(); + } + } + return census_enabled(); +} + +grpc_compression_algorithm grpc_channel_args_get_compression_algorithm( + const grpc_channel_args *a) { + size_t i; + if (a == NULL) return 0; + for (i = 0; i < a->num_args; ++i) { + if (a->args[i].type == GRPC_ARG_INTEGER && + !strcmp(GRPC_COMPRESSION_ALGORITHM_ARG, a->args[i].key)) { + return (grpc_compression_algorithm)a->args[i].value.integer; + break; + } + } + return GRPC_COMPRESS_NONE; +} + +grpc_channel_args *grpc_channel_args_set_compression_algorithm( + grpc_channel_args *a, grpc_compression_algorithm algorithm) { + grpc_arg tmp; + tmp.type = GRPC_ARG_INTEGER; + tmp.key = GRPC_COMPRESSION_ALGORITHM_ARG; + tmp.value.integer = algorithm; + return grpc_channel_args_copy_and_add(a, &tmp, 1); +} + +/** Returns 1 if the argument for compression algorithm's enabled states bitset + * was found in \a a, returning the arg's value in \a states. Otherwise, returns + * 0. */ +static int find_compression_algorithm_states_bitset(const grpc_channel_args *a, + int **states_arg) { + if (a != NULL) { + size_t i; + for (i = 0; i < a->num_args; ++i) { + if (a->args[i].type == GRPC_ARG_INTEGER && + !strcmp(GRPC_COMPRESSION_ALGORITHM_STATE_ARG, a->args[i].key)) { + *states_arg = &a->args[i].value.integer; + return 1; /* GPR_TRUE */ + } + } + } + return 0; /* GPR_FALSE */ +} + +grpc_channel_args *grpc_channel_args_compression_algorithm_set_state( + grpc_channel_args **a, grpc_compression_algorithm algorithm, int state) { + int *states_arg; + grpc_channel_args *result = *a; + const int states_arg_found = + find_compression_algorithm_states_bitset(*a, &states_arg); + + if (states_arg_found) { + if (state != 0) { + GPR_BITSET((unsigned *)states_arg, algorithm); + } else { + GPR_BITCLEAR((unsigned *)states_arg, algorithm); + } + } else { + /* create a new arg */ + grpc_arg tmp; + tmp.type = GRPC_ARG_INTEGER; + tmp.key = GRPC_COMPRESSION_ALGORITHM_STATE_ARG; + /* all enabled by default */ + tmp.value.integer = (1u << GRPC_COMPRESS_ALGORITHMS_COUNT) - 1; + if (state != 0) { + GPR_BITSET((unsigned *)&tmp.value.integer, algorithm); + } else { + GPR_BITCLEAR((unsigned *)&tmp.value.integer, algorithm); + } + result = grpc_channel_args_copy_and_add(*a, &tmp, 1); + grpc_channel_args_destroy(*a); + *a = result; + } + return result; +} + +int grpc_channel_args_compression_algorithm_get_states( + const grpc_channel_args *a) { + int *states_arg; + if (find_compression_algorithm_states_bitset(a, &states_arg)) { + return *states_arg; + } else { + return (1u << GRPC_COMPRESS_ALGORITHMS_COUNT) - 1; /* All algs. enabled */ + } +} + +int grpc_channel_args_compare(const grpc_channel_args *a, + const grpc_channel_args *b) { + int c = GPR_ICMP(a->num_args, b->num_args); + if (c != 0) return c; + for (size_t i = 0; i < a->num_args; i++) { + c = cmp_arg(&a->args[i], &b->args[i]); + if (c != 0) return c; + } + return 0; +} diff --git a/src/core/lib/channel/channel_args.h b/src/core/lib/channel/channel_args.h new file mode 100644 index 0000000000..67d287ec6b --- /dev/null +++ b/src/core/lib/channel/channel_args.h @@ -0,0 +1,94 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CHANNEL_CHANNEL_ARGS_H +#define GRPC_CORE_LIB_CHANNEL_CHANNEL_ARGS_H + +#include <grpc/compression.h> +#include <grpc/grpc.h> + +/* Copy some arguments */ +grpc_channel_args *grpc_channel_args_copy(const grpc_channel_args *src); + +/* Copy some arguments, stably sorting keys */ +grpc_channel_args *grpc_channel_args_normalize(const grpc_channel_args *a); + +/** Copy some arguments and add the to_add parameter in the end. + If to_add is NULL, it is equivalent to call grpc_channel_args_copy. */ +grpc_channel_args *grpc_channel_args_copy_and_add(const grpc_channel_args *src, + const grpc_arg *to_add, + size_t num_to_add); + +/** Copy args from a then args from b into a new channel args */ +grpc_channel_args *grpc_channel_args_merge(const grpc_channel_args *a, + const grpc_channel_args *b); + +/** Destroy arguments created by grpc_channel_args_copy */ +void grpc_channel_args_destroy(grpc_channel_args *a); + +/** Reads census_enabled settings from channel args. Returns 1 if census_enabled + * is specified in channel args, otherwise returns 0. */ +int grpc_channel_args_is_census_enabled(const grpc_channel_args *a); + +/** Returns the compression algorithm set in \a a. */ +grpc_compression_algorithm grpc_channel_args_get_compression_algorithm( + const grpc_channel_args *a); + +/** Returns a channel arg instance with compression enabled. If \a a is + * non-NULL, its args are copied. N.B. GRPC_COMPRESS_NONE disables compression + * for the channel. */ +grpc_channel_args *grpc_channel_args_set_compression_algorithm( + grpc_channel_args *a, grpc_compression_algorithm algorithm); + +/** Sets the support for the given compression algorithm. By default, all + * compression algorithms are enabled. It's an error to disable an algorithm set + * by grpc_channel_args_set_compression_algorithm. + * + * Returns an instance with the updated algorithm states. The \a a pointer is + * modified to point to the returned instance (which may be different from the + * input value of \a a). */ +grpc_channel_args *grpc_channel_args_compression_algorithm_set_state( + grpc_channel_args **a, grpc_compression_algorithm algorithm, int enabled); + +/** Returns the bitset representing the support state (true for enabled, false + * for disabled) for compression algorithms. + * + * The i-th bit of the returned bitset corresponds to the i-th entry in the + * grpc_compression_algorithm enum. */ +int grpc_channel_args_compression_algorithm_get_states( + const grpc_channel_args *a); + +int grpc_channel_args_compare(const grpc_channel_args *a, + const grpc_channel_args *b); + +#endif /* GRPC_CORE_LIB_CHANNEL_CHANNEL_ARGS_H */ diff --git a/src/core/lib/channel/channel_stack.c b/src/core/lib/channel/channel_stack.c new file mode 100644 index 0000000000..52283e35fa --- /dev/null +++ b/src/core/lib/channel/channel_stack.c @@ -0,0 +1,262 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/channel/channel_stack.h" +#include <grpc/support/log.h> + +#include <stdlib.h> +#include <string.h> + +int grpc_trace_channel = 0; + +/* Memory layouts. + + Channel stack is laid out as: { + grpc_channel_stack stk; + padding to GPR_MAX_ALIGNMENT + grpc_channel_element[stk.count]; + per-filter memory, aligned to GPR_MAX_ALIGNMENT + } + + Call stack is laid out as: { + grpc_call_stack stk; + padding to GPR_MAX_ALIGNMENT + grpc_call_element[stk.count]; + per-filter memory, aligned to GPR_MAX_ALIGNMENT + } */ + +/* Given a size, round up to the next multiple of sizeof(void*) */ +#define ROUND_UP_TO_ALIGNMENT_SIZE(x) \ + (((x) + GPR_MAX_ALIGNMENT - 1u) & ~(GPR_MAX_ALIGNMENT - 1u)) + +size_t grpc_channel_stack_size(const grpc_channel_filter **filters, + size_t filter_count) { + /* always need the header, and size for the channel elements */ + size_t size = + ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_channel_stack)) + + ROUND_UP_TO_ALIGNMENT_SIZE(filter_count * sizeof(grpc_channel_element)); + size_t i; + + GPR_ASSERT((GPR_MAX_ALIGNMENT & (GPR_MAX_ALIGNMENT - 1)) == 0 && + "GPR_MAX_ALIGNMENT must be a power of two"); + + /* add the size for each filter */ + for (i = 0; i < filter_count; i++) { + size += ROUND_UP_TO_ALIGNMENT_SIZE(filters[i]->sizeof_channel_data); + } + + return size; +} + +#define CHANNEL_ELEMS_FROM_STACK(stk) \ + ((grpc_channel_element *)((char *)(stk) + ROUND_UP_TO_ALIGNMENT_SIZE( \ + sizeof(grpc_channel_stack)))) + +#define CALL_ELEMS_FROM_STACK(stk) \ + ((grpc_call_element *)((char *)(stk) + \ + ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_call_stack)))) + +grpc_channel_element *grpc_channel_stack_element( + grpc_channel_stack *channel_stack, size_t index) { + return CHANNEL_ELEMS_FROM_STACK(channel_stack) + index; +} + +grpc_channel_element *grpc_channel_stack_last_element( + grpc_channel_stack *channel_stack) { + return grpc_channel_stack_element(channel_stack, channel_stack->count - 1); +} + +grpc_call_element *grpc_call_stack_element(grpc_call_stack *call_stack, + size_t index) { + return CALL_ELEMS_FROM_STACK(call_stack) + index; +} + +void grpc_channel_stack_init(grpc_exec_ctx *exec_ctx, int initial_refs, + grpc_iomgr_cb_func destroy, void *destroy_arg, + const grpc_channel_filter **filters, + size_t filter_count, + const grpc_channel_args *channel_args, + const char *name, grpc_channel_stack *stack) { + size_t call_size = + ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_call_stack)) + + ROUND_UP_TO_ALIGNMENT_SIZE(filter_count * sizeof(grpc_call_element)); + grpc_channel_element *elems; + grpc_channel_element_args args; + char *user_data; + size_t i; + + stack->count = filter_count; + GRPC_STREAM_REF_INIT(&stack->refcount, initial_refs, destroy, destroy_arg, + name); + elems = CHANNEL_ELEMS_FROM_STACK(stack); + user_data = + ((char *)elems) + + ROUND_UP_TO_ALIGNMENT_SIZE(filter_count * sizeof(grpc_channel_element)); + + /* init per-filter data */ + for (i = 0; i < filter_count; i++) { + args.channel_stack = stack; + args.channel_args = channel_args; + args.is_first = i == 0; + args.is_last = i == (filter_count - 1); + elems[i].filter = filters[i]; + elems[i].channel_data = user_data; + elems[i].filter->init_channel_elem(exec_ctx, &elems[i], &args); + user_data += ROUND_UP_TO_ALIGNMENT_SIZE(filters[i]->sizeof_channel_data); + call_size += ROUND_UP_TO_ALIGNMENT_SIZE(filters[i]->sizeof_call_data); + } + + GPR_ASSERT(user_data > (char *)stack); + GPR_ASSERT((uintptr_t)(user_data - (char *)stack) == + grpc_channel_stack_size(filters, filter_count)); + + stack->call_stack_size = call_size; +} + +void grpc_channel_stack_destroy(grpc_exec_ctx *exec_ctx, + grpc_channel_stack *stack) { + grpc_channel_element *channel_elems = CHANNEL_ELEMS_FROM_STACK(stack); + size_t count = stack->count; + size_t i; + + /* destroy per-filter data */ + for (i = 0; i < count; i++) { + channel_elems[i].filter->destroy_channel_elem(exec_ctx, &channel_elems[i]); + } +} + +void grpc_call_stack_init(grpc_exec_ctx *exec_ctx, + grpc_channel_stack *channel_stack, int initial_refs, + grpc_iomgr_cb_func destroy, void *destroy_arg, + grpc_call_context_element *context, + const void *transport_server_data, + grpc_call_stack *call_stack) { + grpc_channel_element *channel_elems = CHANNEL_ELEMS_FROM_STACK(channel_stack); + grpc_call_element_args args; + size_t count = channel_stack->count; + grpc_call_element *call_elems; + char *user_data; + size_t i; + + call_stack->count = count; + GRPC_STREAM_REF_INIT(&call_stack->refcount, initial_refs, destroy, + destroy_arg, "CALL_STACK"); + call_elems = CALL_ELEMS_FROM_STACK(call_stack); + user_data = ((char *)call_elems) + + ROUND_UP_TO_ALIGNMENT_SIZE(count * sizeof(grpc_call_element)); + + /* init per-filter data */ + for (i = 0; i < count; i++) { + args.call_stack = call_stack; + args.server_transport_data = transport_server_data; + args.context = context; + call_elems[i].filter = channel_elems[i].filter; + call_elems[i].channel_data = channel_elems[i].channel_data; + call_elems[i].call_data = user_data; + call_elems[i].filter->init_call_elem(exec_ctx, &call_elems[i], &args); + user_data += + ROUND_UP_TO_ALIGNMENT_SIZE(call_elems[i].filter->sizeof_call_data); + } +} + +void grpc_call_stack_set_pollset(grpc_exec_ctx *exec_ctx, + grpc_call_stack *call_stack, + grpc_pollset *pollset) { + size_t count = call_stack->count; + grpc_call_element *call_elems; + char *user_data; + size_t i; + + call_elems = CALL_ELEMS_FROM_STACK(call_stack); + user_data = ((char *)call_elems) + + ROUND_UP_TO_ALIGNMENT_SIZE(count * sizeof(grpc_call_element)); + + /* init per-filter data */ + for (i = 0; i < count; i++) { + call_elems[i].filter->set_pollset(exec_ctx, &call_elems[i], pollset); + user_data += + ROUND_UP_TO_ALIGNMENT_SIZE(call_elems[i].filter->sizeof_call_data); + } +} + +void grpc_call_stack_ignore_set_pollset(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem, + grpc_pollset *pollset) {} + +void grpc_call_stack_destroy(grpc_exec_ctx *exec_ctx, grpc_call_stack *stack) { + grpc_call_element *elems = CALL_ELEMS_FROM_STACK(stack); + size_t count = stack->count; + size_t i; + + /* destroy per-filter data */ + for (i = 0; i < count; i++) { + elems[i].filter->destroy_call_elem(exec_ctx, &elems[i]); + } +} + +void grpc_call_next_op(grpc_exec_ctx *exec_ctx, grpc_call_element *elem, + grpc_transport_stream_op *op) { + grpc_call_element *next_elem = elem + 1; + next_elem->filter->start_transport_stream_op(exec_ctx, next_elem, op); +} + +char *grpc_call_next_get_peer(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem) { + grpc_call_element *next_elem = elem + 1; + return next_elem->filter->get_peer(exec_ctx, next_elem); +} + +void grpc_channel_next_op(grpc_exec_ctx *exec_ctx, grpc_channel_element *elem, + grpc_transport_op *op) { + grpc_channel_element *next_elem = elem + 1; + next_elem->filter->start_transport_op(exec_ctx, next_elem, op); +} + +grpc_channel_stack *grpc_channel_stack_from_top_element( + grpc_channel_element *elem) { + return (grpc_channel_stack *)((char *)(elem)-ROUND_UP_TO_ALIGNMENT_SIZE( + sizeof(grpc_channel_stack))); +} + +grpc_call_stack *grpc_call_stack_from_top_element(grpc_call_element *elem) { + return (grpc_call_stack *)((char *)(elem)-ROUND_UP_TO_ALIGNMENT_SIZE( + sizeof(grpc_call_stack))); +} + +void grpc_call_element_send_cancel(grpc_exec_ctx *exec_ctx, + grpc_call_element *cur_elem) { + grpc_transport_stream_op op; + memset(&op, 0, sizeof(op)); + op.cancel_with_status = GRPC_STATUS_CANCELLED; + grpc_call_next_op(exec_ctx, cur_elem, &op); +} diff --git a/src/core/lib/channel/channel_stack.h b/src/core/lib/channel/channel_stack.h new file mode 100644 index 0000000000..b29bee411d --- /dev/null +++ b/src/core/lib/channel/channel_stack.h @@ -0,0 +1,260 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CHANNEL_CHANNEL_STACK_H +#define GRPC_CORE_LIB_CHANNEL_CHANNEL_STACK_H + +/* A channel filter defines how operations on a channel are implemented. + Channel filters are chained together to create full channels, and if those + chains are linear, then channel stacks provide a mechanism to minimize + allocations for that chain. + Call stacks are created by channel stacks and represent the per-call data + for that stack. */ + +#include <stddef.h> + +#include <grpc/grpc.h> +#include <grpc/support/log.h> +#include "src/core/lib/debug/trace.h" +#include "src/core/lib/transport/transport.h" + +typedef struct grpc_channel_element grpc_channel_element; +typedef struct grpc_call_element grpc_call_element; + +typedef struct grpc_channel_stack grpc_channel_stack; +typedef struct grpc_call_stack grpc_call_stack; + +typedef struct { + grpc_channel_stack *channel_stack; + const grpc_channel_args *channel_args; + int is_first; + int is_last; +} grpc_channel_element_args; + +typedef struct { + grpc_call_stack *call_stack; + const void *server_transport_data; + grpc_call_context_element *context; +} grpc_call_element_args; + +/* Channel filters specify: + 1. the amount of memory needed in the channel & call (via the sizeof_XXX + members) + 2. functions to initialize and destroy channel & call data + (init_XXX, destroy_XXX) + 3. functions to implement call operations and channel operations (call_op, + channel_op) + 4. a name, which is useful when debugging + + Members are laid out in approximate frequency of use order. */ +typedef struct { + /* Called to eg. send/receive data on a call. + See grpc_call_next_op on how to call the next element in the stack */ + void (*start_transport_stream_op)(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem, + grpc_transport_stream_op *op); + /* Called to handle channel level operations - e.g. new calls, or transport + closure. + See grpc_channel_next_op on how to call the next element in the stack */ + void (*start_transport_op)(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem, grpc_transport_op *op); + + /* sizeof(per call data) */ + size_t sizeof_call_data; + /* Initialize per call data. + elem is initialized at the start of the call, and elem->call_data is what + needs initializing. + The filter does not need to do any chaining. + server_transport_data is an opaque pointer. If it is NULL, this call is + on a client; if it is non-NULL, then it points to memory owned by the + transport and is on the server. Most filters want to ignore this + argument. */ + void (*init_call_elem)(grpc_exec_ctx *exec_ctx, grpc_call_element *elem, + grpc_call_element_args *args); + void (*set_pollset)(grpc_exec_ctx *exec_ctx, grpc_call_element *elem, + grpc_pollset *pollset); + /* Destroy per call data. + The filter does not need to do any chaining */ + void (*destroy_call_elem)(grpc_exec_ctx *exec_ctx, grpc_call_element *elem); + + /* sizeof(per channel data) */ + size_t sizeof_channel_data; + /* Initialize per-channel data. + elem is initialized at the start of the call, and elem->channel_data is + what needs initializing. + is_first, is_last designate this elements position in the stack, and are + useful for asserting correct configuration by upper layer code. + The filter does not need to do any chaining */ + void (*init_channel_elem)(grpc_exec_ctx *exec_ctx, grpc_channel_element *elem, + grpc_channel_element_args *args); + /* Destroy per channel data. + The filter does not need to do any chaining */ + void (*destroy_channel_elem)(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem); + + /* Implement grpc_call_get_peer() */ + char *(*get_peer)(grpc_exec_ctx *exec_ctx, grpc_call_element *elem); + + /* The name of this filter */ + const char *name; +} grpc_channel_filter; + +/* A channel_element tracks its filter and the filter requested memory within + a channel allocation */ +struct grpc_channel_element { + const grpc_channel_filter *filter; + void *channel_data; +}; + +/* A call_element tracks its filter, the filter requested memory within + a channel allocation, and the filter requested memory within a call + allocation */ +struct grpc_call_element { + const grpc_channel_filter *filter; + void *channel_data; + void *call_data; +}; + +/* A channel stack tracks a set of related filters for one channel, and + guarantees they live within a single malloc() allocation */ +struct grpc_channel_stack { + grpc_stream_refcount refcount; + size_t count; + /* Memory required for a call stack (computed at channel stack + initialization) */ + size_t call_stack_size; +}; + +/* A call stack tracks a set of related filters for one call, and guarantees + they live within a single malloc() allocation */ +struct grpc_call_stack { + /* shared refcount for this channel stack. + MUST be the first element: the underlying code calls destroy + with the address of the refcount, but higher layers prefer to think + about the address of the call stack itself. */ + grpc_stream_refcount refcount; + size_t count; +}; + +/* Get a channel element given a channel stack and its index */ +grpc_channel_element *grpc_channel_stack_element(grpc_channel_stack *stack, + size_t i); +/* Get the last channel element in a channel stack */ +grpc_channel_element *grpc_channel_stack_last_element( + grpc_channel_stack *stack); +/* Get a call stack element given a call stack and an index */ +grpc_call_element *grpc_call_stack_element(grpc_call_stack *stack, size_t i); + +/* Determine memory required for a channel stack containing a set of filters */ +size_t grpc_channel_stack_size(const grpc_channel_filter **filters, + size_t filter_count); +/* Initialize a channel stack given some filters */ +void grpc_channel_stack_init(grpc_exec_ctx *exec_ctx, int initial_refs, + grpc_iomgr_cb_func destroy, void *destroy_arg, + const grpc_channel_filter **filters, + size_t filter_count, const grpc_channel_args *args, + const char *name, grpc_channel_stack *stack); +/* Destroy a channel stack */ +void grpc_channel_stack_destroy(grpc_exec_ctx *exec_ctx, + grpc_channel_stack *stack); + +/* Initialize a call stack given a channel stack. transport_server_data is + expected to be NULL on a client, or an opaque transport owned pointer on the + server. */ +void grpc_call_stack_init(grpc_exec_ctx *exec_ctx, + grpc_channel_stack *channel_stack, int initial_refs, + grpc_iomgr_cb_func destroy, void *destroy_arg, + grpc_call_context_element *context, + const void *transport_server_data, + grpc_call_stack *call_stack); +/* Set a pollset for a call stack: must occur before the first op is started */ +void grpc_call_stack_set_pollset(grpc_exec_ctx *exec_ctx, + grpc_call_stack *call_stack, + grpc_pollset *pollset); + +#ifdef GRPC_STREAM_REFCOUNT_DEBUG +#define GRPC_CALL_STACK_REF(call_stack, reason) \ + grpc_stream_ref(&(call_stack)->refcount, reason) +#define GRPC_CALL_STACK_UNREF(exec_ctx, call_stack, reason) \ + grpc_stream_unref(exec_ctx, &(call_stack)->refcount, reason) +#define GRPC_CHANNEL_STACK_REF(channel_stack, reason) \ + grpc_stream_ref(&(channel_stack)->refcount, reason) +#define GRPC_CHANNEL_STACK_UNREF(exec_ctx, channel_stack, reason) \ + grpc_stream_unref(exec_ctx, &(channel_stack)->refcount, reason) +#else +#define GRPC_CALL_STACK_REF(call_stack, reason) \ + grpc_stream_ref(&(call_stack)->refcount) +#define GRPC_CALL_STACK_UNREF(exec_ctx, call_stack, reason) \ + grpc_stream_unref(exec_ctx, &(call_stack)->refcount) +#define GRPC_CHANNEL_STACK_REF(channel_stack, reason) \ + grpc_stream_ref(&(channel_stack)->refcount) +#define GRPC_CHANNEL_STACK_UNREF(exec_ctx, channel_stack, reason) \ + grpc_stream_unref(exec_ctx, &(channel_stack)->refcount) +#endif + +/* Destroy a call stack */ +void grpc_call_stack_destroy(grpc_exec_ctx *exec_ctx, grpc_call_stack *stack); + +/* Ignore set pollset - used by filters to implement the set_pollset method + if they don't care about pollsets at all. Does nothing. */ +void grpc_call_stack_ignore_set_pollset(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem, + grpc_pollset *pollset); +/* Call the next operation in a call stack */ +void grpc_call_next_op(grpc_exec_ctx *exec_ctx, grpc_call_element *elem, + grpc_transport_stream_op *op); +/* Call the next operation (depending on call directionality) in a channel + stack */ +void grpc_channel_next_op(grpc_exec_ctx *exec_ctx, grpc_channel_element *elem, + grpc_transport_op *op); +/* Pass through a request to get_peer to the next child element */ +char *grpc_call_next_get_peer(grpc_exec_ctx *exec_ctx, grpc_call_element *elem); + +/* Given the top element of a channel stack, get the channel stack itself */ +grpc_channel_stack *grpc_channel_stack_from_top_element( + grpc_channel_element *elem); +/* Given the top element of a call stack, get the call stack itself */ +grpc_call_stack *grpc_call_stack_from_top_element(grpc_call_element *elem); + +void grpc_call_log_op(char *file, int line, gpr_log_severity severity, + grpc_call_element *elem, grpc_transport_stream_op *op); + +void grpc_call_element_send_cancel(grpc_exec_ctx *exec_ctx, + grpc_call_element *cur_elem); + +extern int grpc_trace_channel; + +#define GRPC_CALL_LOG_OP(sev, elem, op) \ + if (grpc_trace_channel) grpc_call_log_op(sev, elem, op) + +#endif /* GRPC_CORE_LIB_CHANNEL_CHANNEL_STACK_H */ diff --git a/src/core/lib/channel/channel_stack_builder.c b/src/core/lib/channel/channel_stack_builder.c new file mode 100644 index 0000000000..1ce0c4e07f --- /dev/null +++ b/src/core/lib/channel/channel_stack_builder.c @@ -0,0 +1,258 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/channel/channel_stack_builder.h" + +#include <string.h> + +#include <grpc/support/alloc.h> + +int grpc_trace_channel_stack_builder = 0; + +typedef struct filter_node { + struct filter_node *next; + struct filter_node *prev; + const grpc_channel_filter *filter; + grpc_post_filter_create_init_func init; + void *init_arg; +} filter_node; + +struct grpc_channel_stack_builder { + // sentinel nodes for filters that have been added + filter_node begin; + filter_node end; + // various set/get-able parameters + const grpc_channel_args *args; + grpc_transport *transport; + const char *name; +}; + +struct grpc_channel_stack_builder_iterator { + grpc_channel_stack_builder *builder; + filter_node *node; +}; + +grpc_channel_stack_builder *grpc_channel_stack_builder_create(void) { + grpc_channel_stack_builder *b = gpr_malloc(sizeof(*b)); + memset(b, 0, sizeof(*b)); + + b->begin.filter = NULL; + b->end.filter = NULL; + b->begin.next = &b->end; + b->begin.prev = &b->end; + b->end.next = &b->begin; + b->end.prev = &b->begin; + + return b; +} + +static grpc_channel_stack_builder_iterator *create_iterator_at_filter_node( + grpc_channel_stack_builder *builder, filter_node *node) { + grpc_channel_stack_builder_iterator *it = gpr_malloc(sizeof(*it)); + it->builder = builder; + it->node = node; + return it; +} + +void grpc_channel_stack_builder_iterator_destroy( + grpc_channel_stack_builder_iterator *it) { + gpr_free(it); +} + +grpc_channel_stack_builder_iterator * +grpc_channel_stack_builder_create_iterator_at_first( + grpc_channel_stack_builder *builder) { + return create_iterator_at_filter_node(builder, &builder->begin); +} + +grpc_channel_stack_builder_iterator * +grpc_channel_stack_builder_create_iterator_at_last( + grpc_channel_stack_builder *builder) { + return create_iterator_at_filter_node(builder, &builder->end); +} + +bool grpc_channel_stack_builder_move_next( + grpc_channel_stack_builder_iterator *iterator) { + if (iterator->node == &iterator->builder->end) return false; + iterator->node = iterator->node->next; + return true; +} + +bool grpc_channel_stack_builder_move_prev( + grpc_channel_stack_builder_iterator *iterator) { + if (iterator->node == &iterator->builder->begin) return false; + iterator->node = iterator->node->prev; + return true; +} + +bool grpc_channel_stack_builder_move_prev( + grpc_channel_stack_builder_iterator *iterator); + +void grpc_channel_stack_builder_set_name(grpc_channel_stack_builder *builder, + const char *name) { + GPR_ASSERT(builder->name == NULL); + builder->name = name; +} + +void grpc_channel_stack_builder_set_channel_arguments( + grpc_channel_stack_builder *builder, const grpc_channel_args *args) { + GPR_ASSERT(builder->args == NULL); + builder->args = args; +} + +void grpc_channel_stack_builder_set_transport( + grpc_channel_stack_builder *builder, grpc_transport *transport) { + GPR_ASSERT(builder->transport == NULL); + builder->transport = transport; +} + +grpc_transport *grpc_channel_stack_builder_get_transport( + grpc_channel_stack_builder *builder) { + return builder->transport; +} + +const grpc_channel_args *grpc_channel_stack_builder_get_channel_arguments( + grpc_channel_stack_builder *builder) { + return builder->args; +} + +bool grpc_channel_stack_builder_append_filter( + grpc_channel_stack_builder *builder, const grpc_channel_filter *filter, + grpc_post_filter_create_init_func post_init_func, void *user_data) { + grpc_channel_stack_builder_iterator *it = + grpc_channel_stack_builder_create_iterator_at_last(builder); + bool ok = grpc_channel_stack_builder_add_filter_before( + it, filter, post_init_func, user_data); + grpc_channel_stack_builder_iterator_destroy(it); + return ok; +} + +bool grpc_channel_stack_builder_prepend_filter( + grpc_channel_stack_builder *builder, const grpc_channel_filter *filter, + grpc_post_filter_create_init_func post_init_func, void *user_data) { + grpc_channel_stack_builder_iterator *it = + grpc_channel_stack_builder_create_iterator_at_first(builder); + bool ok = grpc_channel_stack_builder_add_filter_after( + it, filter, post_init_func, user_data); + grpc_channel_stack_builder_iterator_destroy(it); + return ok; +} + +static void add_after(filter_node *before, const grpc_channel_filter *filter, + grpc_post_filter_create_init_func post_init_func, + void *user_data) { + filter_node *new = gpr_malloc(sizeof(*new)); + new->next = before->next; + new->prev = before; + new->next->prev = new->prev->next = new; + new->filter = filter; + new->init = post_init_func; + new->init_arg = user_data; +} + +bool grpc_channel_stack_builder_add_filter_before( + grpc_channel_stack_builder_iterator *iterator, + const grpc_channel_filter *filter, + grpc_post_filter_create_init_func post_init_func, void *user_data) { + if (iterator->node == &iterator->builder->begin) return false; + add_after(iterator->node->prev, filter, post_init_func, user_data); + return true; +} + +bool grpc_channel_stack_builder_add_filter_after( + grpc_channel_stack_builder_iterator *iterator, + const grpc_channel_filter *filter, + grpc_post_filter_create_init_func post_init_func, void *user_data) { + if (iterator->node == &iterator->builder->end) return false; + add_after(iterator->node, filter, post_init_func, user_data); + return true; +} + +void grpc_channel_stack_builder_destroy(grpc_channel_stack_builder *builder) { + filter_node *p = builder->begin.next; + while (p != &builder->end) { + filter_node *next = p->next; + gpr_free(p); + p = next; + } + gpr_free(builder); +} + +void *grpc_channel_stack_builder_finish(grpc_exec_ctx *exec_ctx, + grpc_channel_stack_builder *builder, + size_t prefix_bytes, int initial_refs, + grpc_iomgr_cb_func destroy, + void *destroy_arg) { + // count the number of filters + size_t num_filters = 0; + for (filter_node *p = builder->begin.next; p != &builder->end; p = p->next) { + num_filters++; + } + + // create an array of filters + const grpc_channel_filter **filters = + gpr_malloc(sizeof(*filters) * num_filters); + size_t i = 0; + for (filter_node *p = builder->begin.next; p != &builder->end; p = p->next) { + filters[i++] = p->filter; + } + + // calculate the size of the channel stack + size_t channel_stack_size = grpc_channel_stack_size(filters, num_filters); + + // allocate memory, with prefix_bytes followed by channel_stack_size + char *result = gpr_malloc(prefix_bytes + channel_stack_size); + // fetch a pointer to the channel stack + grpc_channel_stack *channel_stack = + (grpc_channel_stack *)(result + prefix_bytes); + // and initialize it + grpc_channel_stack_init(exec_ctx, initial_refs, destroy, + destroy_arg == NULL ? result : destroy_arg, filters, + num_filters, builder->args, builder->name, + channel_stack); + + // run post-initialization functions + i = 0; + for (filter_node *p = builder->begin.next; p != &builder->end; p = p->next) { + if (p->init != NULL) { + p->init(channel_stack, grpc_channel_stack_element(channel_stack, i), + p->init_arg); + } + i++; + } + + grpc_channel_stack_builder_destroy(builder); + gpr_free((grpc_channel_filter **)filters); + + return result; +} diff --git a/src/core/lib/channel/channel_stack_builder.h b/src/core/lib/channel/channel_stack_builder.h new file mode 100644 index 0000000000..8532c4462a --- /dev/null +++ b/src/core/lib/channel/channel_stack_builder.h @@ -0,0 +1,155 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CHANNEL_CHANNEL_STACK_BUILDER_H +#define GRPC_CORE_LIB_CHANNEL_CHANNEL_STACK_BUILDER_H + +#include <stdbool.h> + +#include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/channel/channel_stack.h" + +/// grpc_channel_stack_builder offers a programmatic interface to selected +/// and order channel filters +typedef struct grpc_channel_stack_builder grpc_channel_stack_builder; +typedef struct grpc_channel_stack_builder_iterator + grpc_channel_stack_builder_iterator; + +/// Create a new channel stack builder +grpc_channel_stack_builder *grpc_channel_stack_builder_create(void); + +/// Assign a name to the channel stack: \a name must be statically allocated +void grpc_channel_stack_builder_set_name(grpc_channel_stack_builder *builder, + const char *name); + +/// Attach \a transport to the builder (does not take ownership) +void grpc_channel_stack_builder_set_transport( + grpc_channel_stack_builder *builder, grpc_transport *transport); + +/// Fetch attached transport +grpc_transport *grpc_channel_stack_builder_get_transport( + grpc_channel_stack_builder *builder); + +/// Set channel arguments: \a args must continue to exist until after +/// grpc_channel_stack_builder_finish returns +void grpc_channel_stack_builder_set_channel_arguments( + grpc_channel_stack_builder *builder, const grpc_channel_args *args); + +/// Return a borrowed pointer to the channel arguments +const grpc_channel_args *grpc_channel_stack_builder_get_channel_arguments( + grpc_channel_stack_builder *builder); + +/// Begin iterating over already defined filters in the builder at the beginning +grpc_channel_stack_builder_iterator * +grpc_channel_stack_builder_create_iterator_at_first( + grpc_channel_stack_builder *builder); + +/// Begin iterating over already defined filters in the builder at the end +grpc_channel_stack_builder_iterator * +grpc_channel_stack_builder_create_iterator_at_last( + grpc_channel_stack_builder *builder); + +/// Is an iterator at the first element? +bool grpc_channel_stack_builder_iterator_is_first( + grpc_channel_stack_builder_iterator *iterator); + +/// Is an iterator at the end? +bool grpc_channel_stack_builder_iterator_is_end( + grpc_channel_stack_builder_iterator *iterator); + +/// Move an iterator to the next item +bool grpc_channel_stack_builder_move_next( + grpc_channel_stack_builder_iterator *iterator); + +/// Move an iterator to the previous item +bool grpc_channel_stack_builder_move_prev( + grpc_channel_stack_builder_iterator *iterator); + +typedef void (*grpc_post_filter_create_init_func)( + grpc_channel_stack *channel_stack, grpc_channel_element *elem, void *arg); + +/// Add \a filter to the stack, after \a iterator. +/// Call \a post_init_func(..., \a user_data) once the channel stack is +/// created. +bool grpc_channel_stack_builder_add_filter_after( + grpc_channel_stack_builder_iterator *iterator, + const grpc_channel_filter *filter, + grpc_post_filter_create_init_func post_init_func, + void *user_data) GRPC_MUST_USE_RESULT; + +/// Add \a filter to the stack, before \a iterator. +/// Call \a post_init_func(..., \a user_data) once the channel stack is +/// created. +bool grpc_channel_stack_builder_add_filter_before( + grpc_channel_stack_builder_iterator *iterator, + const grpc_channel_filter *filter, + grpc_post_filter_create_init_func post_init_func, + void *user_data) GRPC_MUST_USE_RESULT; + +/// Add \a filter to the beginning of the filter list. +/// Call \a post_init_func(..., \a user_data) once the channel stack is +/// created. +bool grpc_channel_stack_builder_prepend_filter( + grpc_channel_stack_builder *builder, const grpc_channel_filter *filter, + grpc_post_filter_create_init_func post_init_func, + void *user_data) GRPC_MUST_USE_RESULT; + +/// Add \a filter to the end of the filter list. +/// Call \a post_init_func(..., \a user_data) once the channel stack is +/// created. +bool grpc_channel_stack_builder_append_filter( + grpc_channel_stack_builder *builder, const grpc_channel_filter *filter, + grpc_post_filter_create_init_func post_init_func, + void *user_data) GRPC_MUST_USE_RESULT; + +/// Terminate iteration and destroy \a iterator +void grpc_channel_stack_builder_iterator_destroy( + grpc_channel_stack_builder_iterator *iterator); + +/// Destroy the builder, return the freshly minted channel stack +/// Allocates \a prefix_bytes bytes before the channel stack +/// Returns the base pointer of the allocated block +/// \a initial_refs, \a destroy, \a destroy_arg are as per +/// grpc_channel_stack_init +void *grpc_channel_stack_builder_finish(grpc_exec_ctx *exec_ctx, + grpc_channel_stack_builder *builder, + size_t prefix_bytes, int initial_refs, + grpc_iomgr_cb_func destroy, + void *destroy_arg); + +/// Destroy the builder without creating a channel stack +void grpc_channel_stack_builder_destroy(grpc_channel_stack_builder *builder); + +extern int grpc_trace_channel_stack_builder; + +#endif /* GRPC_CORE_LIB_CHANNEL_CHANNEL_STACK_BUILDER_H */ diff --git a/src/core/lib/channel/client_channel.c b/src/core/lib/channel/client_channel.c new file mode 100644 index 0000000000..9fdf803ecf --- /dev/null +++ b/src/core/lib/channel/client_channel.c @@ -0,0 +1,526 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/channel/client_channel.h" + +#include <stdio.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/sync.h> +#include <grpc/support/useful.h> + +#include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/channel/connected_channel.h" +#include "src/core/lib/channel/subchannel_call_holder.h" +#include "src/core/lib/iomgr/iomgr.h" +#include "src/core/lib/profiling/timers.h" +#include "src/core/lib/support/string.h" +#include "src/core/lib/surface/channel.h" +#include "src/core/lib/transport/connectivity_state.h" + +/* Client channel implementation */ + +typedef grpc_subchannel_call_holder call_data; + +typedef struct client_channel_channel_data { + /** resolver for this channel */ + grpc_resolver *resolver; + /** have we started resolving this channel */ + int started_resolving; + + /** mutex protecting client configuration, including all + variables below in this data structure */ + gpr_mu mu_config; + /** currently active load balancer - guarded by mu_config */ + grpc_lb_policy *lb_policy; + /** incoming configuration - set by resolver.next + guarded by mu_config */ + grpc_client_config *incoming_configuration; + /** a list of closures that are all waiting for config to come in */ + grpc_closure_list waiting_for_config_closures; + /** resolver callback */ + grpc_closure on_config_changed; + /** connectivity state being tracked */ + grpc_connectivity_state_tracker state_tracker; + /** when an lb_policy arrives, should we try to exit idle */ + int exit_idle_when_lb_policy_arrives; + /** owning stack */ + grpc_channel_stack *owning_stack; + /** interested parties (owned) */ + grpc_pollset_set *interested_parties; +} channel_data; + +/** We create one watcher for each new lb_policy that is returned from a + resolver, + to watch for state changes from the lb_policy. When a state change is seen, + we + update the channel, and create a new watcher */ +typedef struct { + channel_data *chand; + grpc_closure on_changed; + grpc_connectivity_state state; + grpc_lb_policy *lb_policy; +} lb_policy_connectivity_watcher; + +typedef struct { + grpc_closure closure; + grpc_call_element *elem; +} waiting_call; + +static char *cc_get_peer(grpc_exec_ctx *exec_ctx, grpc_call_element *elem) { + return grpc_subchannel_call_holder_get_peer(exec_ctx, elem->call_data); +} + +static void cc_start_transport_stream_op(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem, + grpc_transport_stream_op *op) { + GRPC_CALL_LOG_OP(GPR_INFO, elem, op); + grpc_subchannel_call_holder_perform_op(exec_ctx, elem->call_data, op); +} + +static void watch_lb_policy(grpc_exec_ctx *exec_ctx, channel_data *chand, + grpc_lb_policy *lb_policy, + grpc_connectivity_state current_state); + +static void on_lb_policy_state_changed_locked( + grpc_exec_ctx *exec_ctx, lb_policy_connectivity_watcher *w) { + grpc_connectivity_state publish_state = w->state; + /* check if the notification is for a stale policy */ + if (w->lb_policy != w->chand->lb_policy) return; + + if (publish_state == GRPC_CHANNEL_FATAL_FAILURE && + w->chand->resolver != NULL) { + publish_state = GRPC_CHANNEL_TRANSIENT_FAILURE; + grpc_resolver_channel_saw_error(exec_ctx, w->chand->resolver); + GRPC_LB_POLICY_UNREF(exec_ctx, w->chand->lb_policy, "channel"); + w->chand->lb_policy = NULL; + } + grpc_connectivity_state_set(exec_ctx, &w->chand->state_tracker, publish_state, + "lb_changed"); + if (w->state != GRPC_CHANNEL_FATAL_FAILURE) { + watch_lb_policy(exec_ctx, w->chand, w->lb_policy, w->state); + } +} + +static void on_lb_policy_state_changed(grpc_exec_ctx *exec_ctx, void *arg, + bool iomgr_success) { + lb_policy_connectivity_watcher *w = arg; + + gpr_mu_lock(&w->chand->mu_config); + on_lb_policy_state_changed_locked(exec_ctx, w); + gpr_mu_unlock(&w->chand->mu_config); + + GRPC_CHANNEL_STACK_UNREF(exec_ctx, w->chand->owning_stack, "watch_lb_policy"); + gpr_free(w); +} + +static void watch_lb_policy(grpc_exec_ctx *exec_ctx, channel_data *chand, + grpc_lb_policy *lb_policy, + grpc_connectivity_state current_state) { + lb_policy_connectivity_watcher *w = gpr_malloc(sizeof(*w)); + GRPC_CHANNEL_STACK_REF(chand->owning_stack, "watch_lb_policy"); + + w->chand = chand; + grpc_closure_init(&w->on_changed, on_lb_policy_state_changed, w); + w->state = current_state; + w->lb_policy = lb_policy; + grpc_lb_policy_notify_on_state_change(exec_ctx, lb_policy, &w->state, + &w->on_changed); +} + +static void cc_on_config_changed(grpc_exec_ctx *exec_ctx, void *arg, + bool iomgr_success) { + channel_data *chand = arg; + grpc_lb_policy *lb_policy = NULL; + grpc_lb_policy *old_lb_policy; + grpc_connectivity_state state = GRPC_CHANNEL_TRANSIENT_FAILURE; + int exit_idle = 0; + + if (chand->incoming_configuration != NULL) { + lb_policy = grpc_client_config_get_lb_policy(chand->incoming_configuration); + if (lb_policy != NULL) { + GRPC_LB_POLICY_REF(lb_policy, "channel"); + GRPC_LB_POLICY_REF(lb_policy, "config_change"); + state = grpc_lb_policy_check_connectivity(exec_ctx, lb_policy); + } + + grpc_client_config_unref(exec_ctx, chand->incoming_configuration); + } + + chand->incoming_configuration = NULL; + + if (lb_policy != NULL) { + grpc_pollset_set_add_pollset_set(exec_ctx, lb_policy->interested_parties, + chand->interested_parties); + } + + gpr_mu_lock(&chand->mu_config); + old_lb_policy = chand->lb_policy; + chand->lb_policy = lb_policy; + if (lb_policy != NULL || chand->resolver == NULL /* disconnected */) { + grpc_exec_ctx_enqueue_list(exec_ctx, &chand->waiting_for_config_closures, + NULL); + } + if (lb_policy != NULL && chand->exit_idle_when_lb_policy_arrives) { + GRPC_LB_POLICY_REF(lb_policy, "exit_idle"); + exit_idle = 1; + chand->exit_idle_when_lb_policy_arrives = 0; + } + + if (iomgr_success && chand->resolver) { + grpc_connectivity_state_set(exec_ctx, &chand->state_tracker, state, + "new_lb+resolver"); + if (lb_policy != NULL) { + watch_lb_policy(exec_ctx, chand, lb_policy, state); + } + GRPC_CHANNEL_STACK_REF(chand->owning_stack, "resolver"); + grpc_resolver_next(exec_ctx, chand->resolver, + &chand->incoming_configuration, + &chand->on_config_changed); + gpr_mu_unlock(&chand->mu_config); + } else { + if (chand->resolver != NULL) { + grpc_resolver_shutdown(exec_ctx, chand->resolver); + GRPC_RESOLVER_UNREF(exec_ctx, chand->resolver, "channel"); + chand->resolver = NULL; + } + grpc_connectivity_state_set(exec_ctx, &chand->state_tracker, + GRPC_CHANNEL_FATAL_FAILURE, "resolver_gone"); + gpr_mu_unlock(&chand->mu_config); + } + + if (exit_idle) { + grpc_lb_policy_exit_idle(exec_ctx, lb_policy); + GRPC_LB_POLICY_UNREF(exec_ctx, lb_policy, "exit_idle"); + } + + if (old_lb_policy != NULL) { + grpc_pollset_set_del_pollset_set( + exec_ctx, old_lb_policy->interested_parties, chand->interested_parties); + GRPC_LB_POLICY_UNREF(exec_ctx, old_lb_policy, "channel"); + } + + if (lb_policy != NULL) { + GRPC_LB_POLICY_UNREF(exec_ctx, lb_policy, "config_change"); + } + + GRPC_CHANNEL_STACK_UNREF(exec_ctx, chand->owning_stack, "resolver"); +} + +static void cc_start_transport_op(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem, + grpc_transport_op *op) { + channel_data *chand = elem->channel_data; + + grpc_exec_ctx_enqueue(exec_ctx, op->on_consumed, true, NULL); + + GPR_ASSERT(op->set_accept_stream == false); + if (op->bind_pollset != NULL) { + grpc_pollset_set_add_pollset(exec_ctx, chand->interested_parties, + op->bind_pollset); + } + + gpr_mu_lock(&chand->mu_config); + if (op->on_connectivity_state_change != NULL) { + grpc_connectivity_state_notify_on_state_change( + exec_ctx, &chand->state_tracker, op->connectivity_state, + op->on_connectivity_state_change); + op->on_connectivity_state_change = NULL; + op->connectivity_state = NULL; + } + + if (op->send_ping != NULL) { + if (chand->lb_policy == NULL) { + grpc_exec_ctx_enqueue(exec_ctx, op->send_ping, false, NULL); + } else { + grpc_lb_policy_ping_one(exec_ctx, chand->lb_policy, op->send_ping); + op->bind_pollset = NULL; + } + op->send_ping = NULL; + } + + if (op->disconnect && chand->resolver != NULL) { + grpc_connectivity_state_set(exec_ctx, &chand->state_tracker, + GRPC_CHANNEL_FATAL_FAILURE, "disconnect"); + grpc_resolver_shutdown(exec_ctx, chand->resolver); + GRPC_RESOLVER_UNREF(exec_ctx, chand->resolver, "channel"); + chand->resolver = NULL; + if (chand->lb_policy != NULL) { + grpc_pollset_set_del_pollset_set(exec_ctx, + chand->lb_policy->interested_parties, + chand->interested_parties); + GRPC_LB_POLICY_UNREF(exec_ctx, chand->lb_policy, "channel"); + chand->lb_policy = NULL; + } + } + gpr_mu_unlock(&chand->mu_config); +} + +typedef struct { + grpc_metadata_batch *initial_metadata; + grpc_connected_subchannel **connected_subchannel; + grpc_closure *on_ready; + grpc_call_element *elem; + grpc_closure closure; +} continue_picking_args; + +static int cc_pick_subchannel(grpc_exec_ctx *exec_ctx, void *arg, + grpc_metadata_batch *initial_metadata, + grpc_connected_subchannel **connected_subchannel, + grpc_closure *on_ready); + +static void continue_picking(grpc_exec_ctx *exec_ctx, void *arg, bool success) { + continue_picking_args *cpa = arg; + if (!success) { + grpc_exec_ctx_enqueue(exec_ctx, cpa->on_ready, false, NULL); + } else if (cpa->connected_subchannel == NULL) { + /* cancelled, do nothing */ + } else if (cc_pick_subchannel(exec_ctx, cpa->elem, cpa->initial_metadata, + cpa->connected_subchannel, cpa->on_ready)) { + grpc_exec_ctx_enqueue(exec_ctx, cpa->on_ready, true, NULL); + } + gpr_free(cpa); +} + +static int cc_pick_subchannel(grpc_exec_ctx *exec_ctx, void *elemp, + grpc_metadata_batch *initial_metadata, + grpc_connected_subchannel **connected_subchannel, + grpc_closure *on_ready) { + grpc_call_element *elem = elemp; + channel_data *chand = elem->channel_data; + call_data *calld = elem->call_data; + continue_picking_args *cpa; + grpc_closure *closure; + + GPR_ASSERT(connected_subchannel); + + gpr_mu_lock(&chand->mu_config); + if (initial_metadata == NULL) { + if (chand->lb_policy != NULL) { + grpc_lb_policy_cancel_pick(exec_ctx, chand->lb_policy, + connected_subchannel); + } + for (closure = chand->waiting_for_config_closures.head; closure != NULL; + closure = grpc_closure_next(closure)) { + cpa = closure->cb_arg; + if (cpa->connected_subchannel == connected_subchannel) { + cpa->connected_subchannel = NULL; + grpc_exec_ctx_enqueue(exec_ctx, cpa->on_ready, false, NULL); + } + } + gpr_mu_unlock(&chand->mu_config); + return 1; + } + if (chand->lb_policy != NULL) { + grpc_lb_policy *lb_policy = chand->lb_policy; + int r; + GRPC_LB_POLICY_REF(lb_policy, "cc_pick_subchannel"); + gpr_mu_unlock(&chand->mu_config); + r = grpc_lb_policy_pick(exec_ctx, lb_policy, calld->pollset, + initial_metadata, connected_subchannel, on_ready); + GRPC_LB_POLICY_UNREF(exec_ctx, lb_policy, "cc_pick_subchannel"); + return r; + } + if (chand->resolver != NULL && !chand->started_resolving) { + chand->started_resolving = 1; + GRPC_CHANNEL_STACK_REF(chand->owning_stack, "resolver"); + grpc_resolver_next(exec_ctx, chand->resolver, + &chand->incoming_configuration, + &chand->on_config_changed); + } + cpa = gpr_malloc(sizeof(*cpa)); + cpa->initial_metadata = initial_metadata; + cpa->connected_subchannel = connected_subchannel; + cpa->on_ready = on_ready; + cpa->elem = elem; + grpc_closure_init(&cpa->closure, continue_picking, cpa); + grpc_closure_list_add(&chand->waiting_for_config_closures, &cpa->closure, 1); + gpr_mu_unlock(&chand->mu_config); + return 0; +} + +/* Constructor for call_data */ +static void init_call_elem(grpc_exec_ctx *exec_ctx, grpc_call_element *elem, + grpc_call_element_args *args) { + grpc_subchannel_call_holder_init(elem->call_data, cc_pick_subchannel, elem, + args->call_stack); +} + +/* Destructor for call_data */ +static void destroy_call_elem(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem) { + grpc_subchannel_call_holder_destroy(exec_ctx, elem->call_data); +} + +/* Constructor for channel_data */ +static void init_channel_elem(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem, + grpc_channel_element_args *args) { + channel_data *chand = elem->channel_data; + + memset(chand, 0, sizeof(*chand)); + + GPR_ASSERT(args->is_last); + GPR_ASSERT(elem->filter == &grpc_client_channel_filter); + + gpr_mu_init(&chand->mu_config); + grpc_closure_init(&chand->on_config_changed, cc_on_config_changed, chand); + chand->owning_stack = args->channel_stack; + + grpc_connectivity_state_init(&chand->state_tracker, GRPC_CHANNEL_IDLE, + "client_channel"); + chand->interested_parties = grpc_pollset_set_create(); +} + +/* Destructor for channel_data */ +static void destroy_channel_elem(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem) { + channel_data *chand = elem->channel_data; + + if (chand->resolver != NULL) { + grpc_resolver_shutdown(exec_ctx, chand->resolver); + GRPC_RESOLVER_UNREF(exec_ctx, chand->resolver, "channel"); + } + if (chand->lb_policy != NULL) { + grpc_pollset_set_del_pollset_set(exec_ctx, + chand->lb_policy->interested_parties, + chand->interested_parties); + GRPC_LB_POLICY_UNREF(exec_ctx, chand->lb_policy, "channel"); + } + grpc_connectivity_state_destroy(exec_ctx, &chand->state_tracker); + grpc_pollset_set_destroy(chand->interested_parties); + gpr_mu_destroy(&chand->mu_config); +} + +static void cc_set_pollset(grpc_exec_ctx *exec_ctx, grpc_call_element *elem, + grpc_pollset *pollset) { + call_data *calld = elem->call_data; + calld->pollset = pollset; +} + +const grpc_channel_filter grpc_client_channel_filter = { + cc_start_transport_stream_op, + cc_start_transport_op, + sizeof(call_data), + init_call_elem, + cc_set_pollset, + destroy_call_elem, + sizeof(channel_data), + init_channel_elem, + destroy_channel_elem, + cc_get_peer, + "client-channel", +}; + +void grpc_client_channel_set_resolver(grpc_exec_ctx *exec_ctx, + grpc_channel_stack *channel_stack, + grpc_resolver *resolver) { + /* post construction initialization: set the transport setup pointer */ + grpc_channel_element *elem = grpc_channel_stack_last_element(channel_stack); + channel_data *chand = elem->channel_data; + gpr_mu_lock(&chand->mu_config); + GPR_ASSERT(!chand->resolver); + chand->resolver = resolver; + GRPC_RESOLVER_REF(resolver, "channel"); + if (!grpc_closure_list_empty(chand->waiting_for_config_closures) || + chand->exit_idle_when_lb_policy_arrives) { + chand->started_resolving = 1; + GRPC_CHANNEL_STACK_REF(chand->owning_stack, "resolver"); + grpc_resolver_next(exec_ctx, resolver, &chand->incoming_configuration, + &chand->on_config_changed); + } + gpr_mu_unlock(&chand->mu_config); +} + +grpc_connectivity_state grpc_client_channel_check_connectivity_state( + grpc_exec_ctx *exec_ctx, grpc_channel_element *elem, int try_to_connect) { + channel_data *chand = elem->channel_data; + grpc_connectivity_state out; + gpr_mu_lock(&chand->mu_config); + out = grpc_connectivity_state_check(&chand->state_tracker); + if (out == GRPC_CHANNEL_IDLE && try_to_connect) { + if (chand->lb_policy != NULL) { + grpc_lb_policy_exit_idle(exec_ctx, chand->lb_policy); + } else { + chand->exit_idle_when_lb_policy_arrives = 1; + if (!chand->started_resolving && chand->resolver != NULL) { + GRPC_CHANNEL_STACK_REF(chand->owning_stack, "resolver"); + chand->started_resolving = 1; + grpc_resolver_next(exec_ctx, chand->resolver, + &chand->incoming_configuration, + &chand->on_config_changed); + } + } + } + gpr_mu_unlock(&chand->mu_config); + return out; +} + +typedef struct { + channel_data *chand; + grpc_pollset *pollset; + grpc_closure *on_complete; + grpc_closure my_closure; +} external_connectivity_watcher; + +static void on_external_watch_complete(grpc_exec_ctx *exec_ctx, void *arg, + bool iomgr_success) { + external_connectivity_watcher *w = arg; + grpc_closure *follow_up = w->on_complete; + grpc_pollset_set_del_pollset(exec_ctx, w->chand->interested_parties, + w->pollset); + GRPC_CHANNEL_STACK_UNREF(exec_ctx, w->chand->owning_stack, + "external_connectivity_watcher"); + gpr_free(w); + follow_up->cb(exec_ctx, follow_up->cb_arg, iomgr_success); +} + +void grpc_client_channel_watch_connectivity_state( + grpc_exec_ctx *exec_ctx, grpc_channel_element *elem, grpc_pollset *pollset, + grpc_connectivity_state *state, grpc_closure *on_complete) { + channel_data *chand = elem->channel_data; + external_connectivity_watcher *w = gpr_malloc(sizeof(*w)); + w->chand = chand; + w->pollset = pollset; + w->on_complete = on_complete; + grpc_pollset_set_add_pollset(exec_ctx, chand->interested_parties, pollset); + grpc_closure_init(&w->my_closure, on_external_watch_complete, w); + GRPC_CHANNEL_STACK_REF(w->chand->owning_stack, + "external_connectivity_watcher"); + gpr_mu_lock(&chand->mu_config); + grpc_connectivity_state_notify_on_state_change( + exec_ctx, &chand->state_tracker, state, &w->my_closure); + gpr_mu_unlock(&chand->mu_config); +} diff --git a/src/core/lib/channel/client_channel.h b/src/core/lib/channel/client_channel.h new file mode 100644 index 0000000000..8777796fb6 --- /dev/null +++ b/src/core/lib/channel/client_channel.h @@ -0,0 +1,63 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CHANNEL_CLIENT_CHANNEL_H +#define GRPC_CORE_LIB_CHANNEL_CLIENT_CHANNEL_H + +#include "src/core/lib/channel/channel_stack.h" +#include "src/core/lib/client_config/resolver.h" + +/* A client channel is a channel that begins disconnected, and can connect + to some endpoint on demand. If that endpoint disconnects, it will be + connected to again later. + + Calls on a disconnected client channel are queued until a connection is + established. */ + +extern const grpc_channel_filter grpc_client_channel_filter; + +/* post-construction initializer to let the client channel know which + transport setup it should cancel upon destruction, or initiate when it needs + a connection */ +void grpc_client_channel_set_resolver(grpc_exec_ctx *exec_ctx, + grpc_channel_stack *channel_stack, + grpc_resolver *resolver); + +grpc_connectivity_state grpc_client_channel_check_connectivity_state( + grpc_exec_ctx *exec_ctx, grpc_channel_element *elem, int try_to_connect); + +void grpc_client_channel_watch_connectivity_state( + grpc_exec_ctx *exec_ctx, grpc_channel_element *elem, grpc_pollset *pollset, + grpc_connectivity_state *state, grpc_closure *on_complete); + +#endif /* GRPC_CORE_LIB_CHANNEL_CLIENT_CHANNEL_H */ diff --git a/src/core/lib/channel/compress_filter.c b/src/core/lib/channel/compress_filter.c new file mode 100644 index 0000000000..04bb7cc76f --- /dev/null +++ b/src/core/lib/channel/compress_filter.c @@ -0,0 +1,304 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <assert.h> +#include <string.h> + +#include <grpc/compression.h> +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/slice_buffer.h> + +#include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/channel/compress_filter.h" +#include "src/core/lib/compression/algorithm_metadata.h" +#include "src/core/lib/compression/message_compress.h" +#include "src/core/lib/profiling/timers.h" +#include "src/core/lib/support/string.h" +#include "src/core/lib/transport/static_metadata.h" + +typedef struct call_data { + gpr_slice_buffer slices; /**< Buffers up input slices to be compressed */ + grpc_linked_mdelem compression_algorithm_storage; + grpc_linked_mdelem accept_encoding_storage; + uint32_t remaining_slice_bytes; + /** Compression algorithm we'll try to use. It may be given by incoming + * metadata, or by the channel's default compression settings. */ + grpc_compression_algorithm compression_algorithm; + /** If true, contents of \a compression_algorithm are authoritative */ + int has_compression_algorithm; + + grpc_transport_stream_op send_op; + uint32_t send_length; + uint32_t send_flags; + gpr_slice incoming_slice; + grpc_slice_buffer_stream replacement_stream; + grpc_closure *post_send; + grpc_closure send_done; + grpc_closure got_slice; +} call_data; + +typedef struct channel_data { + /** The default, channel-level, compression algorithm */ + grpc_compression_algorithm default_compression_algorithm; + /** Compression options for the channel */ + grpc_compression_options compression_options; + /** Supported compression algorithms */ + uint32_t supported_compression_algorithms; +} channel_data; + +/** For each \a md element from the incoming metadata, filter out the entry for + * "grpc-encoding", using its value to populate the call data's + * compression_algorithm field. */ +static grpc_mdelem *compression_md_filter(void *user_data, grpc_mdelem *md) { + grpc_call_element *elem = user_data; + call_data *calld = elem->call_data; + channel_data *channeld = elem->channel_data; + + if (md->key == GRPC_MDSTR_GRPC_INTERNAL_ENCODING_REQUEST) { + const char *md_c_str = grpc_mdstr_as_c_string(md->value); + if (!grpc_compression_algorithm_parse(md_c_str, strlen(md_c_str), + &calld->compression_algorithm)) { + gpr_log(GPR_ERROR, + "Invalid compression algorithm: '%s' (unknown). Ignoring.", + md_c_str); + calld->compression_algorithm = GRPC_COMPRESS_NONE; + } + if (grpc_compression_options_is_algorithm_enabled( + &channeld->compression_options, calld->compression_algorithm) == + 0) { + gpr_log(GPR_ERROR, + "Invalid compression algorithm: '%s' (previously disabled). " + "Ignoring.", + md_c_str); + calld->compression_algorithm = GRPC_COMPRESS_NONE; + } + calld->has_compression_algorithm = 1; + return NULL; + } + + return md; +} + +static int skip_compression(grpc_call_element *elem) { + call_data *calld = elem->call_data; + channel_data *channeld = elem->channel_data; + if (calld->has_compression_algorithm) { + if (calld->compression_algorithm == GRPC_COMPRESS_NONE) { + return 1; + } + return 0; /* we have an actual call-specific algorithm */ + } + /* no per-call compression override */ + return channeld->default_compression_algorithm == GRPC_COMPRESS_NONE; +} + +/** Filter initial metadata */ +static void process_send_initial_metadata( + grpc_call_element *elem, grpc_metadata_batch *initial_metadata) { + call_data *calld = elem->call_data; + channel_data *channeld = elem->channel_data; + /* Parse incoming request for compression. If any, it'll be available + * at calld->compression_algorithm */ + grpc_metadata_batch_filter(initial_metadata, compression_md_filter, elem); + if (!calld->has_compression_algorithm) { + /* If no algorithm was found in the metadata and we aren't + * exceptionally skipping compression, fall back to the channel + * default */ + calld->compression_algorithm = channeld->default_compression_algorithm; + calld->has_compression_algorithm = 1; /* GPR_TRUE */ + } + /* hint compression algorithm */ + grpc_metadata_batch_add_tail( + initial_metadata, &calld->compression_algorithm_storage, + grpc_compression_encoding_mdelem(calld->compression_algorithm)); + + /* convey supported compression algorithms */ + grpc_metadata_batch_add_tail(initial_metadata, + &calld->accept_encoding_storage, + GRPC_MDELEM_ACCEPT_ENCODING_FOR_ALGORITHMS( + channeld->supported_compression_algorithms)); +} + +static void continue_send_message(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem); + +static void send_done(grpc_exec_ctx *exec_ctx, void *elemp, bool success) { + grpc_call_element *elem = elemp; + call_data *calld = elem->call_data; + gpr_slice_buffer_reset_and_unref(&calld->slices); + calld->post_send->cb(exec_ctx, calld->post_send->cb_arg, success); +} + +static void finish_send_message(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem) { + call_data *calld = elem->call_data; + int did_compress; + gpr_slice_buffer tmp; + gpr_slice_buffer_init(&tmp); + did_compress = + grpc_msg_compress(calld->compression_algorithm, &calld->slices, &tmp); + if (did_compress) { + gpr_slice_buffer_swap(&calld->slices, &tmp); + calld->send_flags |= GRPC_WRITE_INTERNAL_COMPRESS; + } + gpr_slice_buffer_destroy(&tmp); + + grpc_slice_buffer_stream_init(&calld->replacement_stream, &calld->slices, + calld->send_flags); + calld->send_op.send_message = &calld->replacement_stream.base; + calld->post_send = calld->send_op.on_complete; + calld->send_op.on_complete = &calld->send_done; + + grpc_call_next_op(exec_ctx, elem, &calld->send_op); +} + +static void got_slice(grpc_exec_ctx *exec_ctx, void *elemp, bool success) { + grpc_call_element *elem = elemp; + call_data *calld = elem->call_data; + gpr_slice_buffer_add(&calld->slices, calld->incoming_slice); + if (calld->send_length == calld->slices.length) { + finish_send_message(exec_ctx, elem); + } else { + continue_send_message(exec_ctx, elem); + } +} + +static void continue_send_message(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem) { + call_data *calld = elem->call_data; + while (grpc_byte_stream_next(exec_ctx, calld->send_op.send_message, + &calld->incoming_slice, ~(size_t)0, + &calld->got_slice)) { + gpr_slice_buffer_add(&calld->slices, calld->incoming_slice); + if (calld->send_length == calld->slices.length) { + finish_send_message(exec_ctx, elem); + break; + } + } +} + +static void compress_start_transport_stream_op(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem, + grpc_transport_stream_op *op) { + call_data *calld = elem->call_data; + + GPR_TIMER_BEGIN("compress_start_transport_stream_op", 0); + + if (op->send_initial_metadata) { + process_send_initial_metadata(elem, op->send_initial_metadata); + } + if (op->send_message != NULL && !skip_compression(elem) && + 0 == (op->send_message->flags & GRPC_WRITE_NO_COMPRESS)) { + calld->send_op = *op; + calld->send_length = op->send_message->length; + calld->send_flags = op->send_message->flags; + continue_send_message(exec_ctx, elem); + } else { + /* pass control down the stack */ + grpc_call_next_op(exec_ctx, elem, op); + } + + GPR_TIMER_END("compress_start_transport_stream_op", 0); +} + +/* Constructor for call_data */ +static void init_call_elem(grpc_exec_ctx *exec_ctx, grpc_call_element *elem, + grpc_call_element_args *args) { + /* grab pointers to our data from the call element */ + call_data *calld = elem->call_data; + + /* initialize members */ + gpr_slice_buffer_init(&calld->slices); + calld->has_compression_algorithm = 0; + grpc_closure_init(&calld->got_slice, got_slice, elem); + grpc_closure_init(&calld->send_done, send_done, elem); +} + +/* Destructor for call_data */ +static void destroy_call_elem(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem) { + /* grab pointers to our data from the call element */ + call_data *calld = elem->call_data; + gpr_slice_buffer_destroy(&calld->slices); +} + +/* Constructor for channel_data */ +static void init_channel_elem(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem, + grpc_channel_element_args *args) { + channel_data *channeld = elem->channel_data; + grpc_compression_algorithm algo_idx; + + grpc_compression_options_init(&channeld->compression_options); + channeld->compression_options.enabled_algorithms_bitset = + (uint32_t)grpc_channel_args_compression_algorithm_get_states( + args->channel_args); + + channeld->default_compression_algorithm = + grpc_channel_args_get_compression_algorithm(args->channel_args); + /* Make sure the default isn't disabled. */ + GPR_ASSERT(grpc_compression_options_is_algorithm_enabled( + &channeld->compression_options, channeld->default_compression_algorithm)); + channeld->compression_options.default_compression_algorithm = + channeld->default_compression_algorithm; + + channeld->supported_compression_algorithms = 0; + for (algo_idx = 0; algo_idx < GRPC_COMPRESS_ALGORITHMS_COUNT; ++algo_idx) { + /* skip disabled algorithms */ + if (grpc_compression_options_is_algorithm_enabled( + &channeld->compression_options, algo_idx) == 0) { + continue; + } + channeld->supported_compression_algorithms |= 1u << algo_idx; + } + + GPR_ASSERT(!args->is_last); +} + +/* Destructor for channel data */ +static void destroy_channel_elem(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem) {} + +const grpc_channel_filter grpc_compress_filter = { + compress_start_transport_stream_op, + grpc_channel_next_op, + sizeof(call_data), + init_call_elem, + grpc_call_stack_ignore_set_pollset, + destroy_call_elem, + sizeof(channel_data), + init_channel_elem, + destroy_channel_elem, + grpc_call_next_get_peer, + "compress"}; diff --git a/src/core/lib/channel/compress_filter.h b/src/core/lib/channel/compress_filter.h new file mode 100644 index 0000000000..9010074335 --- /dev/null +++ b/src/core/lib/channel/compress_filter.h @@ -0,0 +1,65 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CHANNEL_COMPRESS_FILTER_H +#define GRPC_CORE_LIB_CHANNEL_COMPRESS_FILTER_H + +#include "src/core/lib/channel/channel_stack.h" + +#define GRPC_COMPRESS_REQUEST_ALGORITHM_KEY "grpc-internal-encoding-request" + +/** Compression filter for outgoing data. + * + * See <grpc/compression.h> for the available compression settings. + * + * Compression settings may come from: + * - Channel configuration, as established at channel creation time. + * - The metadata accompanying the outgoing data to be compressed. This is + * taken as a request only. We may choose not to honor it. The metadata key + * is given by \a GRPC_COMPRESS_REQUEST_ALGORITHM_KEY. + * + * Compression can be disabled for concrete messages (for instance in order to + * prevent CRIME/BEAST type attacks) by having the GRPC_WRITE_NO_COMPRESS set in + * the BEGIN_MESSAGE flags. + * + * The attempted compression mechanism is added to the resulting initial + * metadata under the'grpc-encoding' key. + * + * If compression is actually performed, BEGIN_MESSAGE's flag is modified to + * incorporate GRPC_WRITE_INTERNAL_COMPRESS. Otherwise, and regardless of the + * aforementioned 'grpc-encoding' metadata value, data will pass through + * uncompressed. */ + +extern const grpc_channel_filter grpc_compress_filter; + +#endif /* GRPC_CORE_LIB_CHANNEL_COMPRESS_FILTER_H */ diff --git a/src/core/lib/channel/connected_channel.c b/src/core/lib/channel/connected_channel.c new file mode 100644 index 0000000000..5e3a8974ce --- /dev/null +++ b/src/core/lib/channel/connected_channel.c @@ -0,0 +1,176 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/channel/connected_channel.h" + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +#include <grpc/byte_buffer.h> +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/slice_buffer.h> +#include "src/core/lib/profiling/timers.h" +#include "src/core/lib/support/string.h" +#include "src/core/lib/transport/transport.h" + +#define MAX_BUFFER_LENGTH 8192 + +typedef struct connected_channel_channel_data { + grpc_transport *transport; +} channel_data; + +typedef struct connected_channel_call_data { void *unused; } call_data; + +/* We perform a small hack to locate transport data alongside the connected + channel data in call allocations, to allow everything to be pulled in minimal + cache line requests */ +#define TRANSPORT_STREAM_FROM_CALL_DATA(calld) ((grpc_stream *)((calld) + 1)) +#define CALL_DATA_FROM_TRANSPORT_STREAM(transport_stream) \ + (((call_data *)(transport_stream)) - 1) + +/* Intercept a call operation and either push it directly up or translate it + into transport stream operations */ +static void con_start_transport_stream_op(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem, + grpc_transport_stream_op *op) { + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + GRPC_CALL_LOG_OP(GPR_INFO, elem, op); + + grpc_transport_perform_stream_op(exec_ctx, chand->transport, + TRANSPORT_STREAM_FROM_CALL_DATA(calld), op); +} + +static void con_start_transport_op(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem, + grpc_transport_op *op) { + channel_data *chand = elem->channel_data; + grpc_transport_perform_op(exec_ctx, chand->transport, op); +} + +/* Constructor for call_data */ +static void init_call_elem(grpc_exec_ctx *exec_ctx, grpc_call_element *elem, + grpc_call_element_args *args) { + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + int r; + + r = grpc_transport_init_stream( + exec_ctx, chand->transport, TRANSPORT_STREAM_FROM_CALL_DATA(calld), + &args->call_stack->refcount, args->server_transport_data); + GPR_ASSERT(r == 0); +} + +static void set_pollset(grpc_exec_ctx *exec_ctx, grpc_call_element *elem, + grpc_pollset *pollset) { + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + grpc_transport_set_pollset(exec_ctx, chand->transport, + TRANSPORT_STREAM_FROM_CALL_DATA(calld), pollset); +} + +/* Destructor for call_data */ +static void destroy_call_elem(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem) { + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + grpc_transport_destroy_stream(exec_ctx, chand->transport, + TRANSPORT_STREAM_FROM_CALL_DATA(calld)); +} + +/* Constructor for channel_data */ +static void init_channel_elem(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem, + grpc_channel_element_args *args) { + channel_data *cd = (channel_data *)elem->channel_data; + GPR_ASSERT(args->is_last); + cd->transport = NULL; +} + +/* Destructor for channel_data */ +static void destroy_channel_elem(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem) { + channel_data *cd = (channel_data *)elem->channel_data; + grpc_transport_destroy(exec_ctx, cd->transport); +} + +static char *con_get_peer(grpc_exec_ctx *exec_ctx, grpc_call_element *elem) { + channel_data *chand = elem->channel_data; + return grpc_transport_get_peer(exec_ctx, chand->transport); +} + +static const grpc_channel_filter connected_channel_filter = { + con_start_transport_stream_op, + con_start_transport_op, + sizeof(call_data), + init_call_elem, + set_pollset, + destroy_call_elem, + sizeof(channel_data), + init_channel_elem, + destroy_channel_elem, + con_get_peer, + "connected", +}; + +static void bind_transport(grpc_channel_stack *channel_stack, + grpc_channel_element *elem, void *t) { + channel_data *cd = (channel_data *)elem->channel_data; + GPR_ASSERT(elem->filter == &connected_channel_filter); + GPR_ASSERT(cd->transport == NULL); + cd->transport = t; + + /* HACK(ctiller): increase call stack size for the channel to make space + for channel data. We need a cleaner (but performant) way to do this, + and I'm not sure what that is yet. + This is only "safe" because call stacks place no additional data after + the last call element, and the last call element MUST be the connected + channel. */ + channel_stack->call_stack_size += grpc_transport_stream_size(t); +} + +bool grpc_add_connected_filter(grpc_channel_stack_builder *builder, + void *arg_must_be_null) { + GPR_ASSERT(arg_must_be_null == NULL); + grpc_transport *t = grpc_channel_stack_builder_get_transport(builder); + GPR_ASSERT(t != NULL); + return grpc_channel_stack_builder_append_filter( + builder, &connected_channel_filter, bind_transport, t); +} + +grpc_stream *grpc_connected_channel_get_stream(grpc_call_element *elem) { + call_data *calld = elem->call_data; + return TRANSPORT_STREAM_FROM_CALL_DATA(calld); +} diff --git a/src/core/lib/channel/connected_channel.h b/src/core/lib/channel/connected_channel.h new file mode 100644 index 0000000000..4f20b751cc --- /dev/null +++ b/src/core/lib/channel/connected_channel.h @@ -0,0 +1,42 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CHANNEL_CONNECTED_CHANNEL_H +#define GRPC_CORE_LIB_CHANNEL_CONNECTED_CHANNEL_H + +#include "src/core/lib/channel/channel_stack_builder.h" + +bool grpc_add_connected_filter(grpc_channel_stack_builder *builder, + void *arg_must_be_null); + +#endif /* GRPC_CORE_LIB_CHANNEL_CONNECTED_CHANNEL_H */ diff --git a/src/core/lib/channel/context.h b/src/core/lib/channel/context.h new file mode 100644 index 0000000000..bca102da9a --- /dev/null +++ b/src/core/lib/channel/context.h @@ -0,0 +1,49 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CHANNEL_CONTEXT_H +#define GRPC_CORE_LIB_CHANNEL_CONTEXT_H + +/* Call object context pointers */ +typedef enum { + GRPC_CONTEXT_SECURITY = 0, + GRPC_CONTEXT_TRACING, + GRPC_CONTEXT_COUNT +} grpc_context_index; + +typedef struct { + void *value; + void (*destroy)(void *); +} grpc_call_context_element; + +#endif /* GRPC_CORE_LIB_CHANNEL_CONTEXT_H */ diff --git a/src/core/lib/channel/http_client_filter.c b/src/core/lib/channel/http_client_filter.c new file mode 100644 index 0000000000..7dbac38414 --- /dev/null +++ b/src/core/lib/channel/http_client_filter.c @@ -0,0 +1,255 @@ +/* + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/channel/http_client_filter.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> +#include <string.h> +#include "src/core/lib/profiling/timers.h" +#include "src/core/lib/support/string.h" +#include "src/core/lib/transport/static_metadata.h" + +typedef struct call_data { + grpc_linked_mdelem method; + grpc_linked_mdelem scheme; + grpc_linked_mdelem authority; + grpc_linked_mdelem te_trailers; + grpc_linked_mdelem content_type; + grpc_linked_mdelem user_agent; + + grpc_metadata_batch *recv_initial_metadata; + + /** Closure to call when finished with the hc_on_recv hook */ + grpc_closure *on_done_recv; + /** Receive closures are chained: we inject this closure as the on_done_recv + up-call on transport_op, and remember to call our on_done_recv member + after handling it. */ + grpc_closure hc_on_recv; +} call_data; + +typedef struct channel_data { + grpc_mdelem *static_scheme; + grpc_mdelem *user_agent; +} channel_data; + +typedef struct { + grpc_call_element *elem; + grpc_exec_ctx *exec_ctx; +} client_recv_filter_args; + +static grpc_mdelem *client_recv_filter(void *user_data, grpc_mdelem *md) { + client_recv_filter_args *a = user_data; + if (md == GRPC_MDELEM_STATUS_200) { + return NULL; + } else if (md->key == GRPC_MDSTR_STATUS) { + grpc_call_element_send_cancel(a->exec_ctx, a->elem); + return NULL; + } else if (md->key == GRPC_MDSTR_CONTENT_TYPE) { + return NULL; + } + return md; +} + +static void hc_on_recv(grpc_exec_ctx *exec_ctx, void *user_data, bool success) { + grpc_call_element *elem = user_data; + call_data *calld = elem->call_data; + client_recv_filter_args a; + a.elem = elem; + a.exec_ctx = exec_ctx; + grpc_metadata_batch_filter(calld->recv_initial_metadata, client_recv_filter, + &a); + calld->on_done_recv->cb(exec_ctx, calld->on_done_recv->cb_arg, success); +} + +static grpc_mdelem *client_strip_filter(void *user_data, grpc_mdelem *md) { + /* eat the things we'd like to set ourselves */ + if (md->key == GRPC_MDSTR_METHOD) return NULL; + if (md->key == GRPC_MDSTR_SCHEME) return NULL; + if (md->key == GRPC_MDSTR_TE) return NULL; + if (md->key == GRPC_MDSTR_CONTENT_TYPE) return NULL; + if (md->key == GRPC_MDSTR_USER_AGENT) return NULL; + return md; +} + +static void hc_mutate_op(grpc_call_element *elem, + grpc_transport_stream_op *op) { + /* grab pointers to our data from the call element */ + call_data *calld = elem->call_data; + channel_data *channeld = elem->channel_data; + if (op->send_initial_metadata != NULL) { + grpc_metadata_batch_filter(op->send_initial_metadata, client_strip_filter, + elem); + /* Send : prefixed headers, which have to be before any application + layer headers. */ + grpc_metadata_batch_add_head(op->send_initial_metadata, &calld->method, + GRPC_MDELEM_METHOD_POST); + grpc_metadata_batch_add_head(op->send_initial_metadata, &calld->scheme, + channeld->static_scheme); + grpc_metadata_batch_add_tail(op->send_initial_metadata, &calld->te_trailers, + GRPC_MDELEM_TE_TRAILERS); + grpc_metadata_batch_add_tail( + op->send_initial_metadata, &calld->content_type, + GRPC_MDELEM_CONTENT_TYPE_APPLICATION_SLASH_GRPC); + grpc_metadata_batch_add_tail(op->send_initial_metadata, &calld->user_agent, + GRPC_MDELEM_REF(channeld->user_agent)); + } + + if (op->recv_initial_metadata != NULL) { + /* substitute our callback for the higher callback */ + calld->recv_initial_metadata = op->recv_initial_metadata; + calld->on_done_recv = op->recv_initial_metadata_ready; + op->recv_initial_metadata_ready = &calld->hc_on_recv; + } +} + +static void hc_start_transport_op(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem, + grpc_transport_stream_op *op) { + GPR_TIMER_BEGIN("hc_start_transport_op", 0); + GRPC_CALL_LOG_OP(GPR_INFO, elem, op); + hc_mutate_op(elem, op); + GPR_TIMER_END("hc_start_transport_op", 0); + grpc_call_next_op(exec_ctx, elem, op); +} + +/* Constructor for call_data */ +static void init_call_elem(grpc_exec_ctx *exec_ctx, grpc_call_element *elem, + grpc_call_element_args *args) { + call_data *calld = elem->call_data; + calld->on_done_recv = NULL; + grpc_closure_init(&calld->hc_on_recv, hc_on_recv, elem); +} + +/* Destructor for call_data */ +static void destroy_call_elem(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem) {} + +static grpc_mdelem *scheme_from_args(const grpc_channel_args *args) { + unsigned i; + size_t j; + grpc_mdelem *valid_schemes[] = {GRPC_MDELEM_SCHEME_HTTP, + GRPC_MDELEM_SCHEME_HTTPS}; + if (args != NULL) { + for (i = 0; i < args->num_args; ++i) { + if (args->args[i].type == GRPC_ARG_STRING && + strcmp(args->args[i].key, GRPC_ARG_HTTP2_SCHEME) == 0) { + for (j = 0; j < GPR_ARRAY_SIZE(valid_schemes); j++) { + if (0 == strcmp(grpc_mdstr_as_c_string(valid_schemes[j]->value), + args->args[i].value.string)) { + return valid_schemes[j]; + } + } + } + } + } + return GRPC_MDELEM_SCHEME_HTTP; +} + +static grpc_mdstr *user_agent_from_args(const grpc_channel_args *args) { + gpr_strvec v; + size_t i; + int is_first = 1; + char *tmp; + grpc_mdstr *result; + + gpr_strvec_init(&v); + + for (i = 0; args && i < args->num_args; i++) { + if (0 == strcmp(args->args[i].key, GRPC_ARG_PRIMARY_USER_AGENT_STRING)) { + if (args->args[i].type != GRPC_ARG_STRING) { + gpr_log(GPR_ERROR, "Channel argument '%s' should be a string", + GRPC_ARG_PRIMARY_USER_AGENT_STRING); + } else { + if (!is_first) gpr_strvec_add(&v, gpr_strdup(" ")); + is_first = 0; + gpr_strvec_add(&v, gpr_strdup(args->args[i].value.string)); + } + } + } + + gpr_asprintf(&tmp, "%sgrpc-c/%s (%s)", is_first ? "" : " ", + grpc_version_string(), GPR_PLATFORM_STRING); + is_first = 0; + gpr_strvec_add(&v, tmp); + + for (i = 0; args && i < args->num_args; i++) { + if (0 == strcmp(args->args[i].key, GRPC_ARG_SECONDARY_USER_AGENT_STRING)) { + if (args->args[i].type != GRPC_ARG_STRING) { + gpr_log(GPR_ERROR, "Channel argument '%s' should be a string", + GRPC_ARG_SECONDARY_USER_AGENT_STRING); + } else { + if (!is_first) gpr_strvec_add(&v, gpr_strdup(" ")); + is_first = 0; + gpr_strvec_add(&v, gpr_strdup(args->args[i].value.string)); + } + } + } + + tmp = gpr_strvec_flatten(&v, NULL); + gpr_strvec_destroy(&v); + result = grpc_mdstr_from_string(tmp); + gpr_free(tmp); + + return result; +} + +/* Constructor for channel_data */ +static void init_channel_elem(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem, + grpc_channel_element_args *args) { + channel_data *chand = elem->channel_data; + GPR_ASSERT(!args->is_last); + chand->static_scheme = scheme_from_args(args->channel_args); + chand->user_agent = grpc_mdelem_from_metadata_strings( + GRPC_MDSTR_USER_AGENT, user_agent_from_args(args->channel_args)); +} + +/* Destructor for channel data */ +static void destroy_channel_elem(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem) { + channel_data *chand = elem->channel_data; + GRPC_MDELEM_UNREF(chand->user_agent); +} + +const grpc_channel_filter grpc_http_client_filter = { + hc_start_transport_op, + grpc_channel_next_op, + sizeof(call_data), + init_call_elem, + grpc_call_stack_ignore_set_pollset, + destroy_call_elem, + sizeof(channel_data), + init_channel_elem, + destroy_channel_elem, + grpc_call_next_get_peer, + "http-client"}; diff --git a/src/core/lib/channel/http_client_filter.h b/src/core/lib/channel/http_client_filter.h new file mode 100644 index 0000000000..418426e9cc --- /dev/null +++ b/src/core/lib/channel/http_client_filter.h @@ -0,0 +1,44 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CHANNEL_HTTP_CLIENT_FILTER_H +#define GRPC_CORE_LIB_CHANNEL_HTTP_CLIENT_FILTER_H + +#include "src/core/lib/channel/channel_stack.h" + +/* Processes metadata on the client side for HTTP2 transports */ +extern const grpc_channel_filter grpc_http_client_filter; + +#define GRPC_ARG_HTTP2_SCHEME "grpc.http2_scheme" + +#endif /* GRPC_CORE_LIB_CHANNEL_HTTP_CLIENT_FILTER_H */ diff --git a/src/core/lib/channel/http_server_filter.c b/src/core/lib/channel/http_server_filter.c new file mode 100644 index 0000000000..df99b77ab3 --- /dev/null +++ b/src/core/lib/channel/http_server_filter.c @@ -0,0 +1,240 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/channel/http_server_filter.h" + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <string.h> +#include "src/core/lib/profiling/timers.h" +#include "src/core/lib/transport/static_metadata.h" + +typedef struct call_data { + uint8_t seen_path; + uint8_t seen_post; + uint8_t sent_status; + uint8_t seen_scheme; + uint8_t seen_te_trailers; + uint8_t seen_authority; + grpc_linked_mdelem status; + grpc_linked_mdelem content_type; + + grpc_metadata_batch *recv_initial_metadata; + /** Closure to call when finished with the hs_on_recv hook */ + grpc_closure *on_done_recv; + /** Receive closures are chained: we inject this closure as the on_done_recv + up-call on transport_op, and remember to call our on_done_recv member + after handling it. */ + grpc_closure hs_on_recv; +} call_data; + +typedef struct channel_data { uint8_t unused; } channel_data; + +typedef struct { + grpc_call_element *elem; + grpc_exec_ctx *exec_ctx; +} server_filter_args; + +static grpc_mdelem *server_filter(void *user_data, grpc_mdelem *md) { + server_filter_args *a = user_data; + grpc_call_element *elem = a->elem; + call_data *calld = elem->call_data; + + /* Check if it is one of the headers we care about. */ + if (md == GRPC_MDELEM_TE_TRAILERS || md == GRPC_MDELEM_METHOD_POST || + md == GRPC_MDELEM_SCHEME_HTTP || md == GRPC_MDELEM_SCHEME_HTTPS || + md == GRPC_MDELEM_CONTENT_TYPE_APPLICATION_SLASH_GRPC) { + /* swallow it */ + if (md == GRPC_MDELEM_METHOD_POST) { + calld->seen_post = 1; + } else if (md->key == GRPC_MDSTR_SCHEME) { + calld->seen_scheme = 1; + } else if (md == GRPC_MDELEM_TE_TRAILERS) { + calld->seen_te_trailers = 1; + } + /* TODO(klempner): Track that we've seen all the headers we should + require */ + return NULL; + } else if (md->key == GRPC_MDSTR_CONTENT_TYPE) { + if (strncmp(grpc_mdstr_as_c_string(md->value), "application/grpc+", 17) == + 0) { + /* Although the C implementation doesn't (currently) generate them, + any custom +-suffix is explicitly valid. */ + /* TODO(klempner): We should consider preallocating common values such + as +proto or +json, or at least stashing them if we see them. */ + /* TODO(klempner): Should we be surfacing this to application code? */ + } else { + /* TODO(klempner): We're currently allowing this, but we shouldn't + see it without a proxy so log for now. */ + gpr_log(GPR_INFO, "Unexpected content-type %s", + grpc_mdstr_as_c_string(md->value)); + } + return NULL; + } else if (md->key == GRPC_MDSTR_TE || md->key == GRPC_MDSTR_METHOD || + md->key == GRPC_MDSTR_SCHEME) { + gpr_log(GPR_ERROR, "Invalid %s: header: '%s'", + grpc_mdstr_as_c_string(md->key), grpc_mdstr_as_c_string(md->value)); + /* swallow it and error everything out. */ + /* TODO(klempner): We ought to generate more descriptive error messages + on the wire here. */ + grpc_call_element_send_cancel(a->exec_ctx, elem); + return NULL; + } else if (md->key == GRPC_MDSTR_PATH) { + if (calld->seen_path) { + gpr_log(GPR_ERROR, "Received :path twice"); + return NULL; + } + calld->seen_path = 1; + return md; + } else if (md->key == GRPC_MDSTR_AUTHORITY) { + calld->seen_authority = 1; + return md; + } else if (md->key == GRPC_MDSTR_HOST) { + /* translate host to :authority since :authority may be + omitted */ + grpc_mdelem *authority = grpc_mdelem_from_metadata_strings( + GRPC_MDSTR_AUTHORITY, GRPC_MDSTR_REF(md->value)); + calld->seen_authority = 1; + return authority; + } else { + return md; + } +} + +static void hs_on_recv(grpc_exec_ctx *exec_ctx, void *user_data, bool success) { + grpc_call_element *elem = user_data; + call_data *calld = elem->call_data; + if (success) { + server_filter_args a; + a.elem = elem; + a.exec_ctx = exec_ctx; + grpc_metadata_batch_filter(calld->recv_initial_metadata, server_filter, &a); + /* Have we seen the required http2 transport headers? + (:method, :scheme, content-type, with :path and :authority covered + at the channel level right now) */ + if (calld->seen_post && calld->seen_scheme && calld->seen_te_trailers && + calld->seen_path && calld->seen_authority) { + /* do nothing */ + } else { + if (!calld->seen_path) { + gpr_log(GPR_ERROR, "Missing :path header"); + } + if (!calld->seen_authority) { + gpr_log(GPR_ERROR, "Missing :authority header"); + } + if (!calld->seen_post) { + gpr_log(GPR_ERROR, "Missing :method header"); + } + if (!calld->seen_scheme) { + gpr_log(GPR_ERROR, "Missing :scheme header"); + } + if (!calld->seen_te_trailers) { + gpr_log(GPR_ERROR, "Missing te trailers header"); + } + /* Error this call out */ + success = 0; + grpc_call_element_send_cancel(exec_ctx, elem); + } + } + calld->on_done_recv->cb(exec_ctx, calld->on_done_recv->cb_arg, success); +} + +static void hs_mutate_op(grpc_call_element *elem, + grpc_transport_stream_op *op) { + /* grab pointers to our data from the call element */ + call_data *calld = elem->call_data; + + if (op->send_initial_metadata != NULL && !calld->sent_status) { + calld->sent_status = 1; + grpc_metadata_batch_add_head(op->send_initial_metadata, &calld->status, + GRPC_MDELEM_STATUS_200); + grpc_metadata_batch_add_tail( + op->send_initial_metadata, &calld->content_type, + GRPC_MDELEM_CONTENT_TYPE_APPLICATION_SLASH_GRPC); + } + + if (op->recv_initial_metadata) { + /* substitute our callback for the higher callback */ + calld->recv_initial_metadata = op->recv_initial_metadata; + calld->on_done_recv = op->recv_initial_metadata_ready; + op->recv_initial_metadata_ready = &calld->hs_on_recv; + } +} + +static void hs_start_transport_op(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem, + grpc_transport_stream_op *op) { + GRPC_CALL_LOG_OP(GPR_INFO, elem, op); + GPR_TIMER_BEGIN("hs_start_transport_op", 0); + hs_mutate_op(elem, op); + grpc_call_next_op(exec_ctx, elem, op); + GPR_TIMER_END("hs_start_transport_op", 0); +} + +/* Constructor for call_data */ +static void init_call_elem(grpc_exec_ctx *exec_ctx, grpc_call_element *elem, + grpc_call_element_args *args) { + /* grab pointers to our data from the call element */ + call_data *calld = elem->call_data; + /* initialize members */ + memset(calld, 0, sizeof(*calld)); + grpc_closure_init(&calld->hs_on_recv, hs_on_recv, elem); +} + +/* Destructor for call_data */ +static void destroy_call_elem(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem) {} + +/* Constructor for channel_data */ +static void init_channel_elem(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem, + grpc_channel_element_args *args) { + GPR_ASSERT(!args->is_last); +} + +/* Destructor for channel data */ +static void destroy_channel_elem(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem) {} + +const grpc_channel_filter grpc_http_server_filter = { + hs_start_transport_op, + grpc_channel_next_op, + sizeof(call_data), + init_call_elem, + grpc_call_stack_ignore_set_pollset, + destroy_call_elem, + sizeof(channel_data), + init_channel_elem, + destroy_channel_elem, + grpc_call_next_get_peer, + "http-server"}; diff --git a/src/core/lib/channel/http_server_filter.h b/src/core/lib/channel/http_server_filter.h new file mode 100644 index 0000000000..c8cf920ded --- /dev/null +++ b/src/core/lib/channel/http_server_filter.h @@ -0,0 +1,42 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CHANNEL_HTTP_SERVER_FILTER_H +#define GRPC_CORE_LIB_CHANNEL_HTTP_SERVER_FILTER_H + +#include "src/core/lib/channel/channel_stack.h" + +/* Processes metadata on the client side for HTTP2 transports */ +extern const grpc_channel_filter grpc_http_server_filter; + +#endif /* GRPC_CORE_LIB_CHANNEL_HTTP_SERVER_FILTER_H */ diff --git a/src/core/lib/channel/subchannel_call_holder.c b/src/core/lib/channel/subchannel_call_holder.c new file mode 100644 index 0000000000..6c6d42dd73 --- /dev/null +++ b/src/core/lib/channel/subchannel_call_holder.c @@ -0,0 +1,259 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/channel/subchannel_call_holder.h" + +#include <grpc/support/alloc.h> + +#include "src/core/lib/profiling/timers.h" + +#define GET_CALL(holder) \ + ((grpc_subchannel_call *)(gpr_atm_acq_load(&(holder)->subchannel_call))) + +#define CANCELLED_CALL ((grpc_subchannel_call *)1) + +static void subchannel_ready(grpc_exec_ctx *exec_ctx, void *holder, + bool success); +static void retry_ops(grpc_exec_ctx *exec_ctx, void *retry_ops_args, + bool success); + +static void add_waiting_locked(grpc_subchannel_call_holder *holder, + grpc_transport_stream_op *op); +static void fail_locked(grpc_exec_ctx *exec_ctx, + grpc_subchannel_call_holder *holder); +static void retry_waiting_locked(grpc_exec_ctx *exec_ctx, + grpc_subchannel_call_holder *holder); + +void grpc_subchannel_call_holder_init( + grpc_subchannel_call_holder *holder, + grpc_subchannel_call_holder_pick_subchannel pick_subchannel, + void *pick_subchannel_arg, grpc_call_stack *owning_call) { + gpr_atm_rel_store(&holder->subchannel_call, 0); + holder->pick_subchannel = pick_subchannel; + holder->pick_subchannel_arg = pick_subchannel_arg; + gpr_mu_init(&holder->mu); + holder->connected_subchannel = NULL; + holder->waiting_ops = NULL; + holder->waiting_ops_count = 0; + holder->waiting_ops_capacity = 0; + holder->creation_phase = GRPC_SUBCHANNEL_CALL_HOLDER_NOT_CREATING; + holder->owning_call = owning_call; +} + +void grpc_subchannel_call_holder_destroy(grpc_exec_ctx *exec_ctx, + grpc_subchannel_call_holder *holder) { + grpc_subchannel_call *call = GET_CALL(holder); + if (call != NULL && call != CANCELLED_CALL) { + GRPC_SUBCHANNEL_CALL_UNREF(exec_ctx, call, "holder"); + } + GPR_ASSERT(holder->creation_phase == + GRPC_SUBCHANNEL_CALL_HOLDER_NOT_CREATING); + gpr_mu_destroy(&holder->mu); + GPR_ASSERT(holder->waiting_ops_count == 0); + gpr_free(holder->waiting_ops); +} + +void grpc_subchannel_call_holder_perform_op(grpc_exec_ctx *exec_ctx, + grpc_subchannel_call_holder *holder, + grpc_transport_stream_op *op) { + /* try to (atomically) get the call */ + grpc_subchannel_call *call = GET_CALL(holder); + GPR_TIMER_BEGIN("grpc_subchannel_call_holder_perform_op", 0); + if (call == CANCELLED_CALL) { + grpc_transport_stream_op_finish_with_failure(exec_ctx, op); + GPR_TIMER_END("grpc_subchannel_call_holder_perform_op", 0); + return; + } + if (call != NULL) { + grpc_subchannel_call_process_op(exec_ctx, call, op); + GPR_TIMER_END("grpc_subchannel_call_holder_perform_op", 0); + return; + } + /* we failed; lock and figure out what to do */ + gpr_mu_lock(&holder->mu); +retry: + /* need to recheck that another thread hasn't set the call */ + call = GET_CALL(holder); + if (call == CANCELLED_CALL) { + gpr_mu_unlock(&holder->mu); + grpc_transport_stream_op_finish_with_failure(exec_ctx, op); + GPR_TIMER_END("grpc_subchannel_call_holder_perform_op", 0); + return; + } + if (call != NULL) { + gpr_mu_unlock(&holder->mu); + grpc_subchannel_call_process_op(exec_ctx, call, op); + GPR_TIMER_END("grpc_subchannel_call_holder_perform_op", 0); + return; + } + /* if this is a cancellation, then we can raise our cancelled flag */ + if (op->cancel_with_status != GRPC_STATUS_OK) { + if (!gpr_atm_rel_cas(&holder->subchannel_call, 0, 1)) { + goto retry; + } else { + switch (holder->creation_phase) { + case GRPC_SUBCHANNEL_CALL_HOLDER_NOT_CREATING: + fail_locked(exec_ctx, holder); + break; + case GRPC_SUBCHANNEL_CALL_HOLDER_PICKING_SUBCHANNEL: + holder->pick_subchannel(exec_ctx, holder->pick_subchannel_arg, NULL, + &holder->connected_subchannel, NULL); + break; + } + gpr_mu_unlock(&holder->mu); + grpc_transport_stream_op_finish_with_failure(exec_ctx, op); + GPR_TIMER_END("grpc_subchannel_call_holder_perform_op", 0); + return; + } + } + /* if we don't have a subchannel, try to get one */ + if (holder->creation_phase == GRPC_SUBCHANNEL_CALL_HOLDER_NOT_CREATING && + holder->connected_subchannel == NULL && + op->send_initial_metadata != NULL) { + holder->creation_phase = GRPC_SUBCHANNEL_CALL_HOLDER_PICKING_SUBCHANNEL; + grpc_closure_init(&holder->next_step, subchannel_ready, holder); + GRPC_CALL_STACK_REF(holder->owning_call, "pick_subchannel"); + if (holder->pick_subchannel( + exec_ctx, holder->pick_subchannel_arg, op->send_initial_metadata, + &holder->connected_subchannel, &holder->next_step)) { + holder->creation_phase = GRPC_SUBCHANNEL_CALL_HOLDER_NOT_CREATING; + GRPC_CALL_STACK_UNREF(exec_ctx, holder->owning_call, "pick_subchannel"); + } + } + /* if we've got a subchannel, then let's ask it to create a call */ + if (holder->creation_phase == GRPC_SUBCHANNEL_CALL_HOLDER_NOT_CREATING && + holder->connected_subchannel != NULL) { + gpr_atm_rel_store( + &holder->subchannel_call, + (gpr_atm)(uintptr_t)grpc_connected_subchannel_create_call( + exec_ctx, holder->connected_subchannel, holder->pollset)); + retry_waiting_locked(exec_ctx, holder); + goto retry; + } + /* nothing to be done but wait */ + add_waiting_locked(holder, op); + gpr_mu_unlock(&holder->mu); + GPR_TIMER_END("grpc_subchannel_call_holder_perform_op", 0); +} + +static void subchannel_ready(grpc_exec_ctx *exec_ctx, void *arg, bool success) { + grpc_subchannel_call_holder *holder = arg; + gpr_mu_lock(&holder->mu); + GPR_ASSERT(holder->creation_phase == + GRPC_SUBCHANNEL_CALL_HOLDER_PICKING_SUBCHANNEL); + holder->creation_phase = GRPC_SUBCHANNEL_CALL_HOLDER_NOT_CREATING; + if (holder->connected_subchannel == NULL) { + fail_locked(exec_ctx, holder); + } else if (1 == gpr_atm_acq_load(&holder->subchannel_call)) { + /* already cancelled before subchannel became ready */ + fail_locked(exec_ctx, holder); + } else { + gpr_atm_rel_store( + &holder->subchannel_call, + (gpr_atm)(uintptr_t)grpc_connected_subchannel_create_call( + exec_ctx, holder->connected_subchannel, holder->pollset)); + retry_waiting_locked(exec_ctx, holder); + } + gpr_mu_unlock(&holder->mu); + GRPC_CALL_STACK_UNREF(exec_ctx, holder->owning_call, "pick_subchannel"); +} + +typedef struct { + grpc_transport_stream_op *ops; + size_t nops; + grpc_subchannel_call *call; +} retry_ops_args; + +static void retry_waiting_locked(grpc_exec_ctx *exec_ctx, + grpc_subchannel_call_holder *holder) { + retry_ops_args *a = gpr_malloc(sizeof(*a)); + a->ops = holder->waiting_ops; + a->nops = holder->waiting_ops_count; + a->call = GET_CALL(holder); + if (a->call == CANCELLED_CALL) { + gpr_free(a); + fail_locked(exec_ctx, holder); + return; + } + holder->waiting_ops = NULL; + holder->waiting_ops_count = 0; + holder->waiting_ops_capacity = 0; + GRPC_SUBCHANNEL_CALL_REF(a->call, "retry_ops"); + grpc_exec_ctx_enqueue(exec_ctx, grpc_closure_create(retry_ops, a), true, + NULL); +} + +static void retry_ops(grpc_exec_ctx *exec_ctx, void *args, bool success) { + retry_ops_args *a = args; + size_t i; + for (i = 0; i < a->nops; i++) { + grpc_subchannel_call_process_op(exec_ctx, a->call, &a->ops[i]); + } + GRPC_SUBCHANNEL_CALL_UNREF(exec_ctx, a->call, "retry_ops"); + gpr_free(a->ops); + gpr_free(a); +} + +static void add_waiting_locked(grpc_subchannel_call_holder *holder, + grpc_transport_stream_op *op) { + GPR_TIMER_BEGIN("add_waiting_locked", 0); + if (holder->waiting_ops_count == holder->waiting_ops_capacity) { + holder->waiting_ops_capacity = GPR_MAX(3, 2 * holder->waiting_ops_capacity); + holder->waiting_ops = + gpr_realloc(holder->waiting_ops, holder->waiting_ops_capacity * + sizeof(*holder->waiting_ops)); + } + holder->waiting_ops[holder->waiting_ops_count++] = *op; + GPR_TIMER_END("add_waiting_locked", 0); +} + +static void fail_locked(grpc_exec_ctx *exec_ctx, + grpc_subchannel_call_holder *holder) { + size_t i; + for (i = 0; i < holder->waiting_ops_count; i++) { + grpc_transport_stream_op_finish_with_failure(exec_ctx, + &holder->waiting_ops[i]); + } + holder->waiting_ops_count = 0; +} + +char *grpc_subchannel_call_holder_get_peer( + grpc_exec_ctx *exec_ctx, grpc_subchannel_call_holder *holder) { + grpc_subchannel_call *subchannel_call = GET_CALL(holder); + + if (subchannel_call) { + return grpc_subchannel_call_get_peer(exec_ctx, subchannel_call); + } else { + return NULL; + } +} diff --git a/src/core/lib/channel/subchannel_call_holder.h b/src/core/lib/channel/subchannel_call_holder.h new file mode 100644 index 0000000000..882f366792 --- /dev/null +++ b/src/core/lib/channel/subchannel_call_holder.h @@ -0,0 +1,97 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CHANNEL_SUBCHANNEL_CALL_HOLDER_H +#define GRPC_CORE_LIB_CHANNEL_SUBCHANNEL_CALL_HOLDER_H + +#include "src/core/lib/client_config/subchannel.h" + +/** Pick a subchannel for grpc_subchannel_call_holder; + Return 1 if subchannel is available immediately (in which case on_ready + should not be called), or 0 otherwise (in which case on_ready should be + called when the subchannel is available) */ +typedef int (*grpc_subchannel_call_holder_pick_subchannel)( + grpc_exec_ctx *exec_ctx, void *arg, grpc_metadata_batch *initial_metadata, + grpc_connected_subchannel **connected_subchannel, grpc_closure *on_ready); + +typedef enum { + GRPC_SUBCHANNEL_CALL_HOLDER_NOT_CREATING, + GRPC_SUBCHANNEL_CALL_HOLDER_PICKING_SUBCHANNEL +} grpc_subchannel_call_holder_creation_phase; + +/** Wrapper for holding a pointer to grpc_subchannel_call, and the + associated machinery to create such a pointer. + Handles queueing of stream ops until a call object is ready, waiting + for initial metadata before trying to create a call object, + and handling cancellation gracefully. + + The channel filter uses this as their call_data. */ +typedef struct grpc_subchannel_call_holder { + /** either 0 for no call, 1 for cancelled, or a pointer to a + grpc_subchannel_call */ + gpr_atm subchannel_call; + /** Helper function to choose the subchannel on which to create + the call object. Channel filter delegates to the load + balancing policy (once it's ready). */ + grpc_subchannel_call_holder_pick_subchannel pick_subchannel; + void *pick_subchannel_arg; + + gpr_mu mu; + + grpc_subchannel_call_holder_creation_phase creation_phase; + grpc_connected_subchannel *connected_subchannel; + grpc_pollset *pollset; + + grpc_transport_stream_op *waiting_ops; + size_t waiting_ops_count; + size_t waiting_ops_capacity; + + grpc_closure next_step; + + grpc_call_stack *owning_call; +} grpc_subchannel_call_holder; + +void grpc_subchannel_call_holder_init( + grpc_subchannel_call_holder *holder, + grpc_subchannel_call_holder_pick_subchannel pick_subchannel, + void *pick_subchannel_arg, grpc_call_stack *owning_call); +void grpc_subchannel_call_holder_destroy(grpc_exec_ctx *exec_ctx, + grpc_subchannel_call_holder *holder); + +void grpc_subchannel_call_holder_perform_op(grpc_exec_ctx *exec_ctx, + grpc_subchannel_call_holder *holder, + grpc_transport_stream_op *op); +char *grpc_subchannel_call_holder_get_peer(grpc_exec_ctx *exec_ctx, + grpc_subchannel_call_holder *holder); + +#endif /* GRPC_CORE_LIB_CHANNEL_SUBCHANNEL_CALL_HOLDER_H */ diff --git a/src/core/lib/client_config/README.md b/src/core/lib/client_config/README.md new file mode 100644 index 0000000000..fff7a5af5b --- /dev/null +++ b/src/core/lib/client_config/README.md @@ -0,0 +1,66 @@ +Client Configuration Support for GRPC +===================================== + +This library provides high level configuration machinery to construct client +channels and load balance between them. + +Each grpc_channel is created with a grpc_resolver. It is the resolver's duty +to resolve a name into configuration data for the channel. Such configuration +data might include: + +- a list of (ip, port) addresses to connect to +- a load balancing policy to decide which server to send a request to +- a set of filters to mutate outgoing requests (say, by adding metadata) + +The resolver provides this data as a stream of grpc_client_config objects to +the channel. We represent configuration as a stream so that it can be changed +by the resolver during execution, by reacting to external events (such as a +new configuration file being pushed to some store). + + +Load Balancing +-------------- + +Load balancing configuration is provided by a grpc_lb_policy object, stored as +part of grpc_client_config. + +The primary job of the load balancing policies is to pick a target server given only the +initial metadata for a request. It does this by providing a grpc_subchannel +object to the owning channel. + + +Sub-Channels +------------ + +A sub-channel provides a connection to a server for a client channel. It has a +connectivity state like a regular channel, and so can be connected or +disconnected. This connectivity state can be used to inform load balancing +decisions (for example, by avoiding disconnected backends). + +Configured sub-channels are fully setup to participate in the grpc data plane. +Their behavior is specified by a set of grpc channel filters defined at their +construction. To customize this behavior, resolvers build +grpc_subchannel_factory objects, which use the decorator pattern to customize +construction arguments for concrete grpc_subchannel instances. + + +Naming for GRPC +=============== + +Names in GRPC are represented by a URI (as defined in +[RFC 3986](https://tools.ietf.org/html/rfc3986)). + +The following schemes are currently supported: + +dns:///host:port - dns schemes are currently supported so long as authority is + empty (authority based dns resolution is expected in a future + release) + +unix:path - the unix scheme is used to create and connect to unix domain + sockets - the authority must be empty, and the path + represents the absolute or relative path to the desired + socket + +ipv4:host:port - a pre-resolved ipv4 dotted decimal address/port combination + +ipv6:[host]:port - a pre-resolved ipv6 address/port combination diff --git a/src/core/lib/client_config/client_config.c b/src/core/lib/client_config/client_config.c new file mode 100644 index 0000000000..82c8d68099 --- /dev/null +++ b/src/core/lib/client_config/client_config.c @@ -0,0 +1,74 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/client_config/client_config.h" + +#include <string.h> + +#include <grpc/support/alloc.h> + +struct grpc_client_config { + gpr_refcount refs; + grpc_lb_policy *lb_policy; +}; + +grpc_client_config *grpc_client_config_create() { + grpc_client_config *c = gpr_malloc(sizeof(*c)); + memset(c, 0, sizeof(*c)); + gpr_ref_init(&c->refs, 1); + return c; +} + +void grpc_client_config_ref(grpc_client_config *c) { gpr_ref(&c->refs); } + +void grpc_client_config_unref(grpc_exec_ctx *exec_ctx, grpc_client_config *c) { + if (gpr_unref(&c->refs)) { + if (c->lb_policy != NULL) { + GRPC_LB_POLICY_UNREF(exec_ctx, c->lb_policy, "client_config"); + } + gpr_free(c); + } +} + +void grpc_client_config_set_lb_policy(grpc_client_config *c, + grpc_lb_policy *lb_policy) { + GPR_ASSERT(c->lb_policy == NULL); + if (lb_policy) { + GRPC_LB_POLICY_REF(lb_policy, "client_config"); + } + c->lb_policy = lb_policy; +} + +grpc_lb_policy *grpc_client_config_get_lb_policy(grpc_client_config *c) { + return c->lb_policy; +} diff --git a/src/core/lib/client_config/client_config.h b/src/core/lib/client_config/client_config.h new file mode 100644 index 0000000000..404ec0d3a5 --- /dev/null +++ b/src/core/lib/client_config/client_config.h @@ -0,0 +1,53 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CLIENT_CONFIG_CLIENT_CONFIG_H +#define GRPC_CORE_LIB_CLIENT_CONFIG_CLIENT_CONFIG_H + +#include "src/core/lib/client_config/lb_policy.h" + +/** Total configuration for a client. Provided, and updated, by + grpc_resolver */ +typedef struct grpc_client_config grpc_client_config; + +grpc_client_config *grpc_client_config_create(); +void grpc_client_config_ref(grpc_client_config *client_config); +void grpc_client_config_unref(grpc_exec_ctx *exec_ctx, + grpc_client_config *client_config); + +void grpc_client_config_set_lb_policy(grpc_client_config *client_config, + grpc_lb_policy *lb_policy); +grpc_lb_policy *grpc_client_config_get_lb_policy( + grpc_client_config *client_config); + +#endif /* GRPC_CORE_LIB_CLIENT_CONFIG_CLIENT_CONFIG_H */ diff --git a/src/core/lib/client_config/connector.c b/src/core/lib/client_config/connector.c new file mode 100644 index 0000000000..f51d862c6d --- /dev/null +++ b/src/core/lib/client_config/connector.c @@ -0,0 +1,55 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/client_config/connector.h" + +grpc_connector* grpc_connector_ref(grpc_connector* connector) { + connector->vtable->ref(connector); + return connector; +} + +void grpc_connector_unref(grpc_exec_ctx* exec_ctx, grpc_connector* connector) { + connector->vtable->unref(exec_ctx, connector); +} + +void grpc_connector_connect(grpc_exec_ctx* exec_ctx, grpc_connector* connector, + const grpc_connect_in_args* in_args, + grpc_connect_out_args* out_args, + grpc_closure* notify) { + connector->vtable->connect(exec_ctx, connector, in_args, out_args, notify); +} + +void grpc_connector_shutdown(grpc_exec_ctx* exec_ctx, + grpc_connector* connector) { + connector->vtable->shutdown(exec_ctx, connector); +} diff --git a/src/core/lib/client_config/connector.h b/src/core/lib/client_config/connector.h new file mode 100644 index 0000000000..21b925aade --- /dev/null +++ b/src/core/lib/client_config/connector.h @@ -0,0 +1,92 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CLIENT_CONFIG_CONNECTOR_H +#define GRPC_CORE_LIB_CLIENT_CONFIG_CONNECTOR_H + +#include "src/core/lib/channel/channel_stack.h" +#include "src/core/lib/iomgr/sockaddr.h" +#include "src/core/lib/transport/transport.h" + +typedef struct grpc_connector grpc_connector; +typedef struct grpc_connector_vtable grpc_connector_vtable; + +struct grpc_connector { + const grpc_connector_vtable *vtable; +}; + +typedef struct { + /** set of pollsets interested in this connection */ + grpc_pollset_set *interested_parties; + /** address to connect to */ + const struct sockaddr *addr; + size_t addr_len; + /** initial connect string to send */ + gpr_slice initial_connect_string; + /** deadline for connection */ + gpr_timespec deadline; + /** channel arguments (to be passed to transport) */ + const grpc_channel_args *channel_args; +} grpc_connect_in_args; + +typedef struct { + /** the connected transport */ + grpc_transport *transport; + + /** channel arguments (to be passed to the filters) */ + const grpc_channel_args *channel_args; +} grpc_connect_out_args; + +struct grpc_connector_vtable { + void (*ref)(grpc_connector *connector); + void (*unref)(grpc_exec_ctx *exec_ctx, grpc_connector *connector); + /** Implementation of grpc_connector_shutdown */ + void (*shutdown)(grpc_exec_ctx *exec_ctx, grpc_connector *connector); + /** Implementation of grpc_connector_connect */ + void (*connect)(grpc_exec_ctx *exec_ctx, grpc_connector *connector, + const grpc_connect_in_args *in_args, + grpc_connect_out_args *out_args, grpc_closure *notify); +}; + +grpc_connector *grpc_connector_ref(grpc_connector *connector); +void grpc_connector_unref(grpc_exec_ctx *exec_ctx, grpc_connector *connector); +/** Connect using the connector: max one outstanding call at a time */ +void grpc_connector_connect(grpc_exec_ctx *exec_ctx, grpc_connector *connector, + const grpc_connect_in_args *in_args, + grpc_connect_out_args *out_args, + grpc_closure *notify); +/** Cancel any pending connection */ +void grpc_connector_shutdown(grpc_exec_ctx *exec_ctx, + grpc_connector *connector); + +#endif /* GRPC_CORE_LIB_CLIENT_CONFIG_CONNECTOR_H */ diff --git a/src/core/lib/client_config/default_initial_connect_string.c b/src/core/lib/client_config/default_initial_connect_string.c new file mode 100644 index 0000000000..86eb37de77 --- /dev/null +++ b/src/core/lib/client_config/default_initial_connect_string.c @@ -0,0 +1,39 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/slice.h> +#include "src/core/lib/iomgr/sockaddr.h" + +void grpc_set_default_initial_connect_string(struct sockaddr **addr, + size_t *addr_len, + gpr_slice *initial_str) {} diff --git a/src/core/lib/client_config/initial_connect_string.c b/src/core/lib/client_config/initial_connect_string.c new file mode 100644 index 0000000000..95ae728316 --- /dev/null +++ b/src/core/lib/client_config/initial_connect_string.c @@ -0,0 +1,53 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/client_config/initial_connect_string.h" + +#include <stddef.h> + +extern void grpc_set_default_initial_connect_string(struct sockaddr **addr, + size_t *addr_len, + gpr_slice *initial_str); + +static grpc_set_initial_connect_string_func g_set_initial_connect_string_func = + grpc_set_default_initial_connect_string; + +void grpc_test_set_initial_connect_string_function( + grpc_set_initial_connect_string_func func) { + g_set_initial_connect_string_func = func; +} + +void grpc_set_initial_connect_string(struct sockaddr **addr, size_t *addr_len, + gpr_slice *initial_str) { + g_set_initial_connect_string_func(addr, addr_len, initial_str); +} diff --git a/src/core/lib/client_config/initial_connect_string.h b/src/core/lib/client_config/initial_connect_string.h new file mode 100644 index 0000000000..eec42fa240 --- /dev/null +++ b/src/core/lib/client_config/initial_connect_string.h @@ -0,0 +1,50 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CLIENT_CONFIG_INITIAL_CONNECT_STRING_H +#define GRPC_CORE_LIB_CLIENT_CONFIG_INITIAL_CONNECT_STRING_H + +#include <grpc/support/slice.h> +#include "src/core/lib/iomgr/sockaddr.h" + +typedef void (*grpc_set_initial_connect_string_func)(struct sockaddr **addr, + size_t *addr_len, + gpr_slice *initial_str); +void grpc_test_set_initial_connect_string_function( + grpc_set_initial_connect_string_func func); + +/** Set a string to be sent once connected. Optionally reset addr. */ +void grpc_set_initial_connect_string(struct sockaddr **addr, size_t *addr_len, + gpr_slice *connect_string); + +#endif /* GRPC_CORE_LIB_CLIENT_CONFIG_INITIAL_CONNECT_STRING_H */ diff --git a/src/core/lib/client_config/lb_policies/load_balancer_api.c b/src/core/lib/client_config/lb_policies/load_balancer_api.c new file mode 100644 index 0000000000..4cbed200df --- /dev/null +++ b/src/core/lib/client_config/lb_policies/load_balancer_api.c @@ -0,0 +1,163 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/client_config/lb_policies/load_balancer_api.h" +#include "third_party/nanopb/pb_decode.h" +#include "third_party/nanopb/pb_encode.h" + +#include <grpc/support/alloc.h> + +typedef struct decode_serverlist_arg { + int first_pass; + int i; + size_t num_servers; + grpc_grpclb_server **servers; +} decode_serverlist_arg; + +/* invoked once for every Server in ServerList */ +static bool decode_serverlist(pb_istream_t *stream, const pb_field_t *field, + void **arg) { + decode_serverlist_arg *dec_arg = *arg; + if (dec_arg->first_pass != 0) { /* first pass */ + grpc_grpclb_server server; + if (!pb_decode(stream, grpc_lb_v0_Server_fields, &server)) { + return false; + } + dec_arg->num_servers++; + } else { /* second pass */ + grpc_grpclb_server *server = gpr_malloc(sizeof(grpc_grpclb_server)); + GPR_ASSERT(dec_arg->num_servers > 0); + if (dec_arg->i == 0) { /* first iteration of second pass */ + dec_arg->servers = + gpr_malloc(sizeof(grpc_grpclb_server *) * dec_arg->num_servers); + } + if (!pb_decode(stream, grpc_lb_v0_Server_fields, server)) { + return false; + } + dec_arg->servers[dec_arg->i++] = server; + } + + return true; +} + +grpc_grpclb_request *grpc_grpclb_request_create(const char *lb_service_name) { + grpc_grpclb_request *req = gpr_malloc(sizeof(grpc_grpclb_request)); + + req->has_client_stats = 0; /* TODO(dgq): add support for stats once defined */ + req->has_initial_request = 1; + req->initial_request.has_name = 1; + strncpy(req->initial_request.name, lb_service_name, + GRPC_GRPCLB_SERVICE_NAME_MAX_LENGTH); + return req; +} + +gpr_slice grpc_grpclb_request_encode(const grpc_grpclb_request *request) { + size_t encoded_length; + pb_ostream_t sizestream; + pb_ostream_t outputstream; + gpr_slice slice; + memset(&sizestream, 0, sizeof(pb_ostream_t)); + pb_encode(&sizestream, grpc_lb_v0_LoadBalanceRequest_fields, request); + encoded_length = sizestream.bytes_written; + + slice = gpr_slice_malloc(encoded_length); + outputstream = + pb_ostream_from_buffer(GPR_SLICE_START_PTR(slice), encoded_length); + GPR_ASSERT(pb_encode(&outputstream, grpc_lb_v0_LoadBalanceRequest_fields, + request) != 0); + return slice; +} + +void grpc_grpclb_request_destroy(grpc_grpclb_request *request) { + gpr_free(request); +} + +grpc_grpclb_response *grpc_grpclb_response_parse(gpr_slice encoded_response) { + bool status; + pb_istream_t stream = + pb_istream_from_buffer(GPR_SLICE_START_PTR(encoded_response), + GPR_SLICE_LENGTH(encoded_response)); + grpc_grpclb_response *res = gpr_malloc(sizeof(grpc_grpclb_response)); + memset(res, 0, sizeof(*res)); + status = pb_decode(&stream, grpc_lb_v0_LoadBalanceResponse_fields, res); + GPR_ASSERT(status == true); + return res; +} + +grpc_grpclb_serverlist *grpc_grpclb_response_parse_serverlist( + gpr_slice encoded_response) { + grpc_grpclb_serverlist *sl = gpr_malloc(sizeof(grpc_grpclb_serverlist)); + bool status; + decode_serverlist_arg arg; + pb_istream_t stream = + pb_istream_from_buffer(GPR_SLICE_START_PTR(encoded_response), + GPR_SLICE_LENGTH(encoded_response)); + pb_istream_t stream_at_start = stream; + grpc_grpclb_response *res = gpr_malloc(sizeof(grpc_grpclb_response)); + memset(res, 0, sizeof(*res)); + memset(&arg, 0, sizeof(decode_serverlist_arg)); + + res->server_list.servers.funcs.decode = decode_serverlist; + res->server_list.servers.arg = &arg; + arg.first_pass = 1; + status = pb_decode(&stream, grpc_lb_v0_LoadBalanceResponse_fields, res); + GPR_ASSERT(status == true); + GPR_ASSERT(arg.num_servers > 0); + + arg.first_pass = 0; + status = + pb_decode(&stream_at_start, grpc_lb_v0_LoadBalanceResponse_fields, res); + GPR_ASSERT(status == true); + GPR_ASSERT(arg.servers != NULL); + + sl->num_servers = arg.num_servers; + sl->servers = arg.servers; + if (res->server_list.has_expiration_interval) { + sl->expiration_interval = res->server_list.expiration_interval; + } + grpc_grpclb_response_destroy(res); + return sl; +} + +void grpc_grpclb_destroy_serverlist(grpc_grpclb_serverlist *serverlist) { + size_t i; + for (i = 0; i < serverlist->num_servers; i++) { + gpr_free(serverlist->servers[i]); + } + gpr_free(serverlist->servers); + gpr_free(serverlist); +} + +void grpc_grpclb_response_destroy(grpc_grpclb_response *response) { + gpr_free(response); +} diff --git a/src/core/lib/client_config/lb_policies/load_balancer_api.h b/src/core/lib/client_config/lb_policies/load_balancer_api.h new file mode 100644 index 0000000000..83299adfa9 --- /dev/null +++ b/src/core/lib/client_config/lb_policies/load_balancer_api.h @@ -0,0 +1,85 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CLIENT_CONFIG_LB_POLICIES_LOAD_BALANCER_API_H +#define GRPC_CORE_LIB_CLIENT_CONFIG_LB_POLICIES_LOAD_BALANCER_API_H + +#include <grpc/support/slice_buffer.h> + +#include "src/core/lib/client_config/lb_policy_factory.h" +#include "src/core/lib/proto/grpc/lb/v0/load_balancer.pb.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define GRPC_GRPCLB_SERVICE_NAME_MAX_LENGTH 128 + +typedef grpc_lb_v0_LoadBalanceRequest grpc_grpclb_request; +typedef grpc_lb_v0_LoadBalanceResponse grpc_grpclb_response; +typedef grpc_lb_v0_Server grpc_grpclb_server; +typedef grpc_lb_v0_Duration grpc_grpclb_duration; +typedef struct grpc_grpclb_serverlist { + grpc_grpclb_server **servers; + size_t num_servers; + grpc_grpclb_duration expiration_interval; +} grpc_grpclb_serverlist; + +/** Create a request for a gRPC LB service under \a lb_service_name */ +grpc_grpclb_request *grpc_grpclb_request_create(const char *lb_service_name); + +/** Protocol Buffers v3-encode \a request */ +gpr_slice grpc_grpclb_request_encode(const grpc_grpclb_request *request); + +/** Destroy \a request */ +void grpc_grpclb_request_destroy(grpc_grpclb_request *request); + +/** Parse (ie, decode) the bytes in \a encoded_response as a \a + * grpc_grpclb_response */ +grpc_grpclb_response *grpc_grpclb_response_parse(gpr_slice encoded_response); + +/** Destroy \a serverlist */ +void grpc_grpclb_destroy_serverlist(grpc_grpclb_serverlist *serverlist); + +/** Parse the list of servers from an encoded \a grpc_grpclb_response */ +grpc_grpclb_serverlist *grpc_grpclb_response_parse_serverlist( + gpr_slice encoded_response); + +/** Destroy \a response */ +void grpc_grpclb_response_destroy(grpc_grpclb_response *response); + +#ifdef __cplusplus +} +#endif + +#endif /* GRPC_CORE_LIB_CLIENT_CONFIG_LB_POLICIES_LOAD_BALANCER_API_H */ diff --git a/src/core/lib/client_config/lb_policies/pick_first.c b/src/core/lib/client_config/lb_policies/pick_first.c new file mode 100644 index 0000000000..2e399b73f9 --- /dev/null +++ b/src/core/lib/client_config/lb_policies/pick_first.c @@ -0,0 +1,421 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/client_config/lb_policies/pick_first.h" +#include "src/core/lib/client_config/lb_policy_factory.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include "src/core/lib/transport/connectivity_state.h" + +typedef struct pending_pick { + struct pending_pick *next; + grpc_pollset *pollset; + grpc_connected_subchannel **target; + grpc_closure *on_complete; +} pending_pick; + +typedef struct { + /** base policy: must be first */ + grpc_lb_policy base; + /** all our subchannels */ + grpc_subchannel **subchannels; + size_t num_subchannels; + + grpc_closure connectivity_changed; + + /** the selected channel (a grpc_connected_subchannel) */ + gpr_atm selected; + + /** mutex protecting remaining members */ + gpr_mu mu; + /** have we started picking? */ + int started_picking; + /** are we shut down? */ + int shutdown; + /** which subchannel are we watching? */ + size_t checking_subchannel; + /** what is the connectivity of that channel? */ + grpc_connectivity_state checking_connectivity; + /** list of picks that are waiting on connectivity */ + pending_pick *pending_picks; + + /** our connectivity state tracker */ + grpc_connectivity_state_tracker state_tracker; +} pick_first_lb_policy; + +#define GET_SELECTED(p) \ + ((grpc_connected_subchannel *)gpr_atm_acq_load(&(p)->selected)) + +void pf_destroy(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol) { + pick_first_lb_policy *p = (pick_first_lb_policy *)pol; + grpc_connected_subchannel *selected = GET_SELECTED(p); + size_t i; + GPR_ASSERT(p->pending_picks == NULL); + for (i = 0; i < p->num_subchannels; i++) { + GRPC_SUBCHANNEL_UNREF(exec_ctx, p->subchannels[i], "pick_first"); + } + if (selected != NULL) { + GRPC_CONNECTED_SUBCHANNEL_UNREF(exec_ctx, selected, "picked_first"); + } + grpc_connectivity_state_destroy(exec_ctx, &p->state_tracker); + gpr_free(p->subchannels); + gpr_mu_destroy(&p->mu); + gpr_free(p); +} + +void pf_shutdown(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol) { + pick_first_lb_policy *p = (pick_first_lb_policy *)pol; + pending_pick *pp; + grpc_connected_subchannel *selected; + gpr_mu_lock(&p->mu); + selected = GET_SELECTED(p); + p->shutdown = 1; + pp = p->pending_picks; + p->pending_picks = NULL; + grpc_connectivity_state_set(exec_ctx, &p->state_tracker, + GRPC_CHANNEL_FATAL_FAILURE, "shutdown"); + /* cancel subscription */ + if (selected != NULL) { + grpc_connected_subchannel_notify_on_state_change( + exec_ctx, selected, NULL, NULL, &p->connectivity_changed); + } else { + grpc_subchannel_notify_on_state_change( + exec_ctx, p->subchannels[p->checking_subchannel], NULL, NULL, + &p->connectivity_changed); + } + gpr_mu_unlock(&p->mu); + while (pp != NULL) { + pending_pick *next = pp->next; + *pp->target = NULL; + grpc_pollset_set_del_pollset(exec_ctx, p->base.interested_parties, + pp->pollset); + grpc_exec_ctx_enqueue(exec_ctx, pp->on_complete, true, NULL); + gpr_free(pp); + pp = next; + } +} + +static void pf_cancel_pick(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol, + grpc_connected_subchannel **target) { + pick_first_lb_policy *p = (pick_first_lb_policy *)pol; + pending_pick *pp; + gpr_mu_lock(&p->mu); + pp = p->pending_picks; + p->pending_picks = NULL; + while (pp != NULL) { + pending_pick *next = pp->next; + if (pp->target == target) { + grpc_pollset_set_del_pollset(exec_ctx, p->base.interested_parties, + pp->pollset); + *target = NULL; + grpc_exec_ctx_enqueue(exec_ctx, pp->on_complete, false, NULL); + gpr_free(pp); + } else { + pp->next = p->pending_picks; + p->pending_picks = pp; + } + pp = next; + } + gpr_mu_unlock(&p->mu); +} + +static void start_picking(grpc_exec_ctx *exec_ctx, pick_first_lb_policy *p) { + p->started_picking = 1; + p->checking_subchannel = 0; + p->checking_connectivity = GRPC_CHANNEL_IDLE; + GRPC_LB_POLICY_WEAK_REF(&p->base, "pick_first_connectivity"); + grpc_subchannel_notify_on_state_change( + exec_ctx, p->subchannels[p->checking_subchannel], + p->base.interested_parties, &p->checking_connectivity, + &p->connectivity_changed); +} + +void pf_exit_idle(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol) { + pick_first_lb_policy *p = (pick_first_lb_policy *)pol; + gpr_mu_lock(&p->mu); + if (!p->started_picking) { + start_picking(exec_ctx, p); + } + gpr_mu_unlock(&p->mu); +} + +int pf_pick(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol, grpc_pollset *pollset, + grpc_metadata_batch *initial_metadata, + grpc_connected_subchannel **target, grpc_closure *on_complete) { + pick_first_lb_policy *p = (pick_first_lb_policy *)pol; + pending_pick *pp; + + /* Check atomically for a selected channel */ + grpc_connected_subchannel *selected = GET_SELECTED(p); + if (selected != NULL) { + *target = selected; + return 1; + } + + /* No subchannel selected yet, so acquire lock and then attempt again */ + gpr_mu_lock(&p->mu); + selected = GET_SELECTED(p); + if (selected) { + gpr_mu_unlock(&p->mu); + *target = selected; + return 1; + } else { + if (!p->started_picking) { + start_picking(exec_ctx, p); + } + grpc_pollset_set_add_pollset(exec_ctx, p->base.interested_parties, pollset); + pp = gpr_malloc(sizeof(*pp)); + pp->next = p->pending_picks; + pp->pollset = pollset; + pp->target = target; + pp->on_complete = on_complete; + p->pending_picks = pp; + gpr_mu_unlock(&p->mu); + return 0; + } +} + +static void destroy_subchannels(grpc_exec_ctx *exec_ctx, void *arg, + bool iomgr_success) { + pick_first_lb_policy *p = arg; + size_t i; + size_t num_subchannels = p->num_subchannels; + grpc_subchannel **subchannels; + + gpr_mu_lock(&p->mu); + subchannels = p->subchannels; + p->num_subchannels = 0; + p->subchannels = NULL; + gpr_mu_unlock(&p->mu); + GRPC_LB_POLICY_WEAK_UNREF(exec_ctx, &p->base, "destroy_subchannels"); + + for (i = 0; i < num_subchannels; i++) { + GRPC_SUBCHANNEL_UNREF(exec_ctx, subchannels[i], "pick_first"); + } + + gpr_free(subchannels); +} + +static void pf_connectivity_changed(grpc_exec_ctx *exec_ctx, void *arg, + bool iomgr_success) { + pick_first_lb_policy *p = arg; + grpc_subchannel *selected_subchannel; + pending_pick *pp; + grpc_connected_subchannel *selected; + + gpr_mu_lock(&p->mu); + + selected = GET_SELECTED(p); + + if (p->shutdown) { + gpr_mu_unlock(&p->mu); + GRPC_LB_POLICY_WEAK_UNREF(exec_ctx, &p->base, "pick_first_connectivity"); + return; + } else if (selected != NULL) { + if (p->checking_connectivity == GRPC_CHANNEL_TRANSIENT_FAILURE) { + /* if the selected channel goes bad, we're done */ + p->checking_connectivity = GRPC_CHANNEL_FATAL_FAILURE; + } + grpc_connectivity_state_set(exec_ctx, &p->state_tracker, + p->checking_connectivity, "selected_changed"); + if (p->checking_connectivity != GRPC_CHANNEL_FATAL_FAILURE) { + grpc_connected_subchannel_notify_on_state_change( + exec_ctx, selected, p->base.interested_parties, + &p->checking_connectivity, &p->connectivity_changed); + } else { + GRPC_LB_POLICY_WEAK_UNREF(exec_ctx, &p->base, "pick_first_connectivity"); + } + } else { + loop: + switch (p->checking_connectivity) { + case GRPC_CHANNEL_READY: + grpc_connectivity_state_set(exec_ctx, &p->state_tracker, + GRPC_CHANNEL_READY, "connecting_ready"); + selected_subchannel = p->subchannels[p->checking_subchannel]; + selected = + grpc_subchannel_get_connected_subchannel(selected_subchannel); + GPR_ASSERT(selected != NULL); + GRPC_CONNECTED_SUBCHANNEL_REF(selected, "picked_first"); + /* drop the pick list: we are connected now */ + GRPC_LB_POLICY_WEAK_REF(&p->base, "destroy_subchannels"); + gpr_atm_rel_store(&p->selected, (gpr_atm)selected); + grpc_exec_ctx_enqueue( + exec_ctx, grpc_closure_create(destroy_subchannels, p), true, NULL); + /* update any calls that were waiting for a pick */ + while ((pp = p->pending_picks)) { + p->pending_picks = pp->next; + *pp->target = selected; + grpc_pollset_set_del_pollset(exec_ctx, p->base.interested_parties, + pp->pollset); + grpc_exec_ctx_enqueue(exec_ctx, pp->on_complete, true, NULL); + gpr_free(pp); + } + grpc_connected_subchannel_notify_on_state_change( + exec_ctx, selected, p->base.interested_parties, + &p->checking_connectivity, &p->connectivity_changed); + break; + case GRPC_CHANNEL_TRANSIENT_FAILURE: + grpc_connectivity_state_set(exec_ctx, &p->state_tracker, + GRPC_CHANNEL_TRANSIENT_FAILURE, + "connecting_transient_failure"); + p->checking_subchannel = + (p->checking_subchannel + 1) % p->num_subchannels; + p->checking_connectivity = grpc_subchannel_check_connectivity( + p->subchannels[p->checking_subchannel]); + if (p->checking_connectivity == GRPC_CHANNEL_TRANSIENT_FAILURE) { + grpc_subchannel_notify_on_state_change( + exec_ctx, p->subchannels[p->checking_subchannel], + p->base.interested_parties, &p->checking_connectivity, + &p->connectivity_changed); + } else { + goto loop; + } + break; + case GRPC_CHANNEL_CONNECTING: + case GRPC_CHANNEL_IDLE: + grpc_connectivity_state_set(exec_ctx, &p->state_tracker, + GRPC_CHANNEL_CONNECTING, + "connecting_changed"); + grpc_subchannel_notify_on_state_change( + exec_ctx, p->subchannels[p->checking_subchannel], + p->base.interested_parties, &p->checking_connectivity, + &p->connectivity_changed); + break; + case GRPC_CHANNEL_FATAL_FAILURE: + p->num_subchannels--; + GPR_SWAP(grpc_subchannel *, p->subchannels[p->checking_subchannel], + p->subchannels[p->num_subchannels]); + GRPC_SUBCHANNEL_UNREF(exec_ctx, p->subchannels[p->num_subchannels], + "pick_first"); + if (p->num_subchannels == 0) { + grpc_connectivity_state_set(exec_ctx, &p->state_tracker, + GRPC_CHANNEL_FATAL_FAILURE, + "no_more_channels"); + while ((pp = p->pending_picks)) { + p->pending_picks = pp->next; + *pp->target = NULL; + grpc_exec_ctx_enqueue(exec_ctx, pp->on_complete, true, NULL); + gpr_free(pp); + } + GRPC_LB_POLICY_WEAK_UNREF(exec_ctx, &p->base, + "pick_first_connectivity"); + } else { + grpc_connectivity_state_set(exec_ctx, &p->state_tracker, + GRPC_CHANNEL_TRANSIENT_FAILURE, + "subchannel_failed"); + p->checking_subchannel %= p->num_subchannels; + p->checking_connectivity = grpc_subchannel_check_connectivity( + p->subchannels[p->checking_subchannel]); + goto loop; + } + } + } + + gpr_mu_unlock(&p->mu); +} + +static grpc_connectivity_state pf_check_connectivity(grpc_exec_ctx *exec_ctx, + grpc_lb_policy *pol) { + pick_first_lb_policy *p = (pick_first_lb_policy *)pol; + grpc_connectivity_state st; + gpr_mu_lock(&p->mu); + st = grpc_connectivity_state_check(&p->state_tracker); + gpr_mu_unlock(&p->mu); + return st; +} + +void pf_notify_on_state_change(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol, + grpc_connectivity_state *current, + grpc_closure *notify) { + pick_first_lb_policy *p = (pick_first_lb_policy *)pol; + gpr_mu_lock(&p->mu); + grpc_connectivity_state_notify_on_state_change(exec_ctx, &p->state_tracker, + current, notify); + gpr_mu_unlock(&p->mu); +} + +void pf_ping_one(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol, + grpc_closure *closure) { + pick_first_lb_policy *p = (pick_first_lb_policy *)pol; + grpc_connected_subchannel *selected = GET_SELECTED(p); + if (selected) { + grpc_connected_subchannel_ping(exec_ctx, selected, closure); + } else { + grpc_exec_ctx_enqueue(exec_ctx, closure, false, NULL); + } +} + +static const grpc_lb_policy_vtable pick_first_lb_policy_vtable = { + pf_destroy, + pf_shutdown, + pf_pick, + pf_cancel_pick, + pf_ping_one, + pf_exit_idle, + pf_check_connectivity, + pf_notify_on_state_change}; + +static void pick_first_factory_ref(grpc_lb_policy_factory *factory) {} + +static void pick_first_factory_unref(grpc_lb_policy_factory *factory) {} + +static grpc_lb_policy *create_pick_first(grpc_lb_policy_factory *factory, + grpc_lb_policy_args *args) { + if (args->num_subchannels == 0) return NULL; + pick_first_lb_policy *p = gpr_malloc(sizeof(*p)); + memset(p, 0, sizeof(*p)); + grpc_lb_policy_init(&p->base, &pick_first_lb_policy_vtable); + p->subchannels = + gpr_malloc(sizeof(grpc_subchannel *) * args->num_subchannels); + p->num_subchannels = args->num_subchannels; + grpc_connectivity_state_init(&p->state_tracker, GRPC_CHANNEL_IDLE, + "pick_first"); + memcpy(p->subchannels, args->subchannels, + sizeof(grpc_subchannel *) * args->num_subchannels); + grpc_closure_init(&p->connectivity_changed, pf_connectivity_changed, p); + gpr_mu_init(&p->mu); + return &p->base; +} + +static const grpc_lb_policy_factory_vtable pick_first_factory_vtable = { + pick_first_factory_ref, pick_first_factory_unref, create_pick_first, + "pick_first"}; + +static grpc_lb_policy_factory pick_first_lb_policy_factory = { + &pick_first_factory_vtable}; + +grpc_lb_policy_factory *grpc_pick_first_lb_factory_create() { + return &pick_first_lb_policy_factory; +} diff --git a/src/core/lib/client_config/lb_policies/pick_first.h b/src/core/lib/client_config/lb_policies/pick_first.h new file mode 100644 index 0000000000..dba86ea7ad --- /dev/null +++ b/src/core/lib/client_config/lb_policies/pick_first.h @@ -0,0 +1,43 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CLIENT_CONFIG_LB_POLICIES_PICK_FIRST_H +#define GRPC_CORE_LIB_CLIENT_CONFIG_LB_POLICIES_PICK_FIRST_H + +#include "src/core/lib/client_config/lb_policy_factory.h" + +/** Returns a load balancing factory for the pick first policy, which picks up + * the first subchannel from \a subchannels to succesfully connect */ +grpc_lb_policy_factory *grpc_pick_first_lb_factory_create(); + +#endif /* GRPC_CORE_LIB_CLIENT_CONFIG_LB_POLICIES_PICK_FIRST_H */ diff --git a/src/core/lib/client_config/lb_policies/round_robin.c b/src/core/lib/client_config/lb_policies/round_robin.c new file mode 100644 index 0000000000..c904c5f921 --- /dev/null +++ b/src/core/lib/client_config/lb_policies/round_robin.c @@ -0,0 +1,542 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/client_config/lb_policies/round_robin.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include "src/core/lib/transport/connectivity_state.h" + +typedef struct round_robin_lb_policy round_robin_lb_policy; + +int grpc_lb_round_robin_trace = 0; + +/** List of entities waiting for a pick. + * + * Once a pick is available, \a target is updated and \a on_complete called. */ +typedef struct pending_pick { + struct pending_pick *next; + grpc_pollset *pollset; + grpc_connected_subchannel **target; + grpc_closure *on_complete; +} pending_pick; + +/** List of subchannels in a connectivity READY state */ +typedef struct ready_list { + grpc_subchannel *subchannel; + struct ready_list *next; + struct ready_list *prev; +} ready_list; + +typedef struct { + /** index within policy->subchannels */ + size_t index; + /** backpointer to owning policy */ + round_robin_lb_policy *policy; + /** subchannel itself */ + grpc_subchannel *subchannel; + /** notification that connectivity has changed on subchannel */ + grpc_closure connectivity_changed_closure; + /** this subchannels current position in subchannel->ready_list */ + ready_list *ready_list_node; + /** last observed connectivity */ + grpc_connectivity_state connectivity_state; +} subchannel_data; + +struct round_robin_lb_policy { + /** base policy: must be first */ + grpc_lb_policy base; + + /** all our subchannels */ + size_t num_subchannels; + subchannel_data **subchannels; + + /** mutex protecting remaining members */ + gpr_mu mu; + /** have we started picking? */ + int started_picking; + /** are we shutting down? */ + int shutdown; + /** List of picks that are waiting on connectivity */ + pending_pick *pending_picks; + + /** our connectivity state tracker */ + grpc_connectivity_state_tracker state_tracker; + + /** (Dummy) root of the doubly linked list containing READY subchannels */ + ready_list ready_list; + /** Last pick from the ready list. */ + ready_list *ready_list_last_pick; +}; + +/** Returns the next subchannel from the connected list or NULL if the list is + * empty. + * + * Note that this function does *not* advance p->ready_list_last_pick. Use \a + * advance_last_picked_locked() for that. */ +static ready_list *peek_next_connected_locked(const round_robin_lb_policy *p) { + ready_list *selected; + selected = p->ready_list_last_pick->next; + + while (selected != NULL) { + if (selected == &p->ready_list) { + GPR_ASSERT(selected->subchannel == NULL); + /* skip dummy root */ + selected = selected->next; + } else { + GPR_ASSERT(selected->subchannel != NULL); + return selected; + } + } + return NULL; +} + +/** Advance the \a ready_list picking head. */ +static void advance_last_picked_locked(round_robin_lb_policy *p) { + if (p->ready_list_last_pick->next != NULL) { /* non-empty list */ + p->ready_list_last_pick = p->ready_list_last_pick->next; + if (p->ready_list_last_pick == &p->ready_list) { + /* skip dummy root */ + p->ready_list_last_pick = p->ready_list_last_pick->next; + } + } else { /* should be an empty list */ + GPR_ASSERT(p->ready_list_last_pick == &p->ready_list); + } + + if (grpc_lb_round_robin_trace) { + gpr_log(GPR_DEBUG, "[READYLIST] ADVANCED LAST PICK. NOW AT NODE %p (SC %p)", + p->ready_list_last_pick, p->ready_list_last_pick->subchannel); + } +} + +/** Prepends (relative to the root at p->ready_list) the connected subchannel \a + * csc to the list of ready subchannels. */ +static ready_list *add_connected_sc_locked(round_robin_lb_policy *p, + grpc_subchannel *sc) { + ready_list *new_elem = gpr_malloc(sizeof(ready_list)); + new_elem->subchannel = sc; + if (p->ready_list.prev == NULL) { + /* first element */ + new_elem->next = &p->ready_list; + new_elem->prev = &p->ready_list; + p->ready_list.next = new_elem; + p->ready_list.prev = new_elem; + } else { + new_elem->next = &p->ready_list; + new_elem->prev = p->ready_list.prev; + p->ready_list.prev->next = new_elem; + p->ready_list.prev = new_elem; + } + if (grpc_lb_round_robin_trace) { + gpr_log(GPR_DEBUG, "[READYLIST] ADDING NODE %p (SC %p)", new_elem, sc); + } + return new_elem; +} + +/** Removes \a node from the list of connected subchannels */ +static void remove_disconnected_sc_locked(round_robin_lb_policy *p, + ready_list *node) { + if (node == NULL) { + return; + } + if (node == p->ready_list_last_pick) { + /* If removing the lastly picked node, reset the last pick pointer to the + * dummy root of the list */ + p->ready_list_last_pick = &p->ready_list; + } + + /* removing last item */ + if (node->next == &p->ready_list && node->prev == &p->ready_list) { + GPR_ASSERT(p->ready_list.next == node); + GPR_ASSERT(p->ready_list.prev == node); + p->ready_list.next = NULL; + p->ready_list.prev = NULL; + } else { + node->prev->next = node->next; + node->next->prev = node->prev; + } + + if (grpc_lb_round_robin_trace) { + gpr_log(GPR_DEBUG, "[READYLIST] REMOVED NODE %p (SC %p)", node, + node->subchannel); + } + + node->next = NULL; + node->prev = NULL; + node->subchannel = NULL; + + gpr_free(node); +} + +void rr_destroy(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol) { + round_robin_lb_policy *p = (round_robin_lb_policy *)pol; + size_t i; + ready_list *elem; + for (i = 0; i < p->num_subchannels; i++) { + subchannel_data *sd = p->subchannels[i]; + GRPC_SUBCHANNEL_UNREF(exec_ctx, sd->subchannel, "round_robin"); + gpr_free(sd); + } + + grpc_connectivity_state_destroy(exec_ctx, &p->state_tracker); + gpr_free(p->subchannels); + gpr_mu_destroy(&p->mu); + + elem = p->ready_list.next; + while (elem != NULL && elem != &p->ready_list) { + ready_list *tmp; + tmp = elem->next; + elem->next = NULL; + elem->prev = NULL; + elem->subchannel = NULL; + gpr_free(elem); + elem = tmp; + } + gpr_free(p); +} + +void rr_shutdown(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol) { + round_robin_lb_policy *p = (round_robin_lb_policy *)pol; + pending_pick *pp; + size_t i; + + gpr_mu_lock(&p->mu); + + p->shutdown = 1; + while ((pp = p->pending_picks)) { + p->pending_picks = pp->next; + *pp->target = NULL; + grpc_exec_ctx_enqueue(exec_ctx, pp->on_complete, false, NULL); + gpr_free(pp); + } + grpc_connectivity_state_set(exec_ctx, &p->state_tracker, + GRPC_CHANNEL_FATAL_FAILURE, "shutdown"); + for (i = 0; i < p->num_subchannels; i++) { + subchannel_data *sd = p->subchannels[i]; + grpc_subchannel_notify_on_state_change(exec_ctx, sd->subchannel, NULL, NULL, + &sd->connectivity_changed_closure); + } + gpr_mu_unlock(&p->mu); +} + +static void rr_cancel_pick(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol, + grpc_connected_subchannel **target) { + round_robin_lb_policy *p = (round_robin_lb_policy *)pol; + pending_pick *pp; + gpr_mu_lock(&p->mu); + pp = p->pending_picks; + p->pending_picks = NULL; + while (pp != NULL) { + pending_pick *next = pp->next; + if (pp->target == target) { + grpc_pollset_set_del_pollset(exec_ctx, p->base.interested_parties, + pp->pollset); + *target = NULL; + grpc_exec_ctx_enqueue(exec_ctx, pp->on_complete, false, NULL); + gpr_free(pp); + } else { + pp->next = p->pending_picks; + p->pending_picks = pp; + } + pp = next; + } + gpr_mu_unlock(&p->mu); +} + +static void start_picking(grpc_exec_ctx *exec_ctx, round_robin_lb_policy *p) { + size_t i; + p->started_picking = 1; + + gpr_log(GPR_DEBUG, "LB_POLICY: p=%p num_subchannels=%d", p, + p->num_subchannels); + + for (i = 0; i < p->num_subchannels; i++) { + subchannel_data *sd = p->subchannels[i]; + sd->connectivity_state = GRPC_CHANNEL_IDLE; + grpc_subchannel_notify_on_state_change( + exec_ctx, sd->subchannel, p->base.interested_parties, + &sd->connectivity_state, &sd->connectivity_changed_closure); + GRPC_LB_POLICY_WEAK_REF(&p->base, "round_robin_connectivity"); + } +} + +void rr_exit_idle(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol) { + round_robin_lb_policy *p = (round_robin_lb_policy *)pol; + gpr_mu_lock(&p->mu); + if (!p->started_picking) { + start_picking(exec_ctx, p); + } + gpr_mu_unlock(&p->mu); +} + +int rr_pick(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol, grpc_pollset *pollset, + grpc_metadata_batch *initial_metadata, + grpc_connected_subchannel **target, grpc_closure *on_complete) { + round_robin_lb_policy *p = (round_robin_lb_policy *)pol; + pending_pick *pp; + ready_list *selected; + gpr_mu_lock(&p->mu); + if ((selected = peek_next_connected_locked(p))) { + gpr_mu_unlock(&p->mu); + *target = grpc_subchannel_get_connected_subchannel(selected->subchannel); + if (grpc_lb_round_robin_trace) { + gpr_log(GPR_DEBUG, + "[RR PICK] TARGET <-- CONNECTED SUBCHANNEL %p (NODE %p)", + selected->subchannel, selected); + } + /* only advance the last picked pointer if the selection was used */ + advance_last_picked_locked(p); + return 1; + } else { + if (!p->started_picking) { + start_picking(exec_ctx, p); + } + grpc_pollset_set_add_pollset(exec_ctx, p->base.interested_parties, pollset); + pp = gpr_malloc(sizeof(*pp)); + pp->next = p->pending_picks; + pp->pollset = pollset; + pp->target = target; + pp->on_complete = on_complete; + p->pending_picks = pp; + gpr_mu_unlock(&p->mu); + return 0; + } +} + +static void rr_connectivity_changed(grpc_exec_ctx *exec_ctx, void *arg, + bool iomgr_success) { + subchannel_data *sd = arg; + round_robin_lb_policy *p = sd->policy; + pending_pick *pp; + ready_list *selected; + + int unref = 0; + + gpr_mu_lock(&p->mu); + + if (p->shutdown) { + unref = 1; + } else { + switch (sd->connectivity_state) { + case GRPC_CHANNEL_READY: + grpc_connectivity_state_set(exec_ctx, &p->state_tracker, + GRPC_CHANNEL_READY, "connecting_ready"); + /* add the newly connected subchannel to the list of connected ones. + * Note that it goes to the "end of the line". */ + sd->ready_list_node = add_connected_sc_locked(p, sd->subchannel); + /* at this point we know there's at least one suitable subchannel. Go + * ahead and pick one and notify the pending suitors in + * p->pending_picks. This preemtively replicates rr_pick()'s actions. */ + selected = peek_next_connected_locked(p); + if (p->pending_picks != NULL) { + /* if the selected subchannel is going to be used for the pending + * picks, update the last picked pointer */ + advance_last_picked_locked(p); + } + while ((pp = p->pending_picks)) { + p->pending_picks = pp->next; + *pp->target = + grpc_subchannel_get_connected_subchannel(selected->subchannel); + if (grpc_lb_round_robin_trace) { + gpr_log(GPR_DEBUG, + "[RR CONN CHANGED] TARGET <-- SUBCHANNEL %p (NODE %p)", + selected->subchannel, selected); + } + grpc_pollset_set_del_pollset(exec_ctx, p->base.interested_parties, + pp->pollset); + grpc_exec_ctx_enqueue(exec_ctx, pp->on_complete, true, NULL); + gpr_free(pp); + } + grpc_subchannel_notify_on_state_change( + exec_ctx, sd->subchannel, p->base.interested_parties, + &sd->connectivity_state, &sd->connectivity_changed_closure); + break; + case GRPC_CHANNEL_CONNECTING: + case GRPC_CHANNEL_IDLE: + grpc_connectivity_state_set(exec_ctx, &p->state_tracker, + sd->connectivity_state, + "connecting_changed"); + grpc_subchannel_notify_on_state_change( + exec_ctx, sd->subchannel, p->base.interested_parties, + &sd->connectivity_state, &sd->connectivity_changed_closure); + break; + case GRPC_CHANNEL_TRANSIENT_FAILURE: + /* renew state notification */ + grpc_subchannel_notify_on_state_change( + exec_ctx, sd->subchannel, p->base.interested_parties, + &sd->connectivity_state, &sd->connectivity_changed_closure); + + /* remove from ready list if still present */ + if (sd->ready_list_node != NULL) { + remove_disconnected_sc_locked(p, sd->ready_list_node); + sd->ready_list_node = NULL; + } + grpc_connectivity_state_set(exec_ctx, &p->state_tracker, + GRPC_CHANNEL_TRANSIENT_FAILURE, + "connecting_transient_failure"); + break; + case GRPC_CHANNEL_FATAL_FAILURE: + if (sd->ready_list_node != NULL) { + remove_disconnected_sc_locked(p, sd->ready_list_node); + sd->ready_list_node = NULL; + } + + p->num_subchannels--; + GPR_SWAP(subchannel_data *, p->subchannels[sd->index], + p->subchannels[p->num_subchannels]); + GRPC_SUBCHANNEL_UNREF(exec_ctx, sd->subchannel, "round_robin"); + p->subchannels[sd->index]->index = sd->index; + gpr_free(sd); + + unref = 1; + if (p->num_subchannels == 0) { + grpc_connectivity_state_set(exec_ctx, &p->state_tracker, + GRPC_CHANNEL_FATAL_FAILURE, + "no_more_channels"); + while ((pp = p->pending_picks)) { + p->pending_picks = pp->next; + *pp->target = NULL; + grpc_exec_ctx_enqueue(exec_ctx, pp->on_complete, true, NULL); + gpr_free(pp); + } + } else { + grpc_connectivity_state_set(exec_ctx, &p->state_tracker, + GRPC_CHANNEL_TRANSIENT_FAILURE, + "subchannel_failed"); + } + } /* switch */ + } /* !unref */ + + gpr_mu_unlock(&p->mu); + + if (unref) { + GRPC_LB_POLICY_WEAK_UNREF(exec_ctx, &p->base, "round_robin_connectivity"); + } +} + +static grpc_connectivity_state rr_check_connectivity(grpc_exec_ctx *exec_ctx, + grpc_lb_policy *pol) { + round_robin_lb_policy *p = (round_robin_lb_policy *)pol; + grpc_connectivity_state st; + gpr_mu_lock(&p->mu); + st = grpc_connectivity_state_check(&p->state_tracker); + gpr_mu_unlock(&p->mu); + return st; +} + +static void rr_notify_on_state_change(grpc_exec_ctx *exec_ctx, + grpc_lb_policy *pol, + grpc_connectivity_state *current, + grpc_closure *notify) { + round_robin_lb_policy *p = (round_robin_lb_policy *)pol; + gpr_mu_lock(&p->mu); + grpc_connectivity_state_notify_on_state_change(exec_ctx, &p->state_tracker, + current, notify); + gpr_mu_unlock(&p->mu); +} + +static void rr_ping_one(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol, + grpc_closure *closure) { + round_robin_lb_policy *p = (round_robin_lb_policy *)pol; + ready_list *selected; + grpc_connected_subchannel *target; + gpr_mu_lock(&p->mu); + if ((selected = peek_next_connected_locked(p))) { + gpr_mu_unlock(&p->mu); + target = grpc_subchannel_get_connected_subchannel(selected->subchannel); + grpc_connected_subchannel_ping(exec_ctx, target, closure); + } else { + gpr_mu_unlock(&p->mu); + grpc_exec_ctx_enqueue(exec_ctx, closure, false, NULL); + } +} + +static const grpc_lb_policy_vtable round_robin_lb_policy_vtable = { + rr_destroy, + rr_shutdown, + rr_pick, + rr_cancel_pick, + rr_ping_one, + rr_exit_idle, + rr_check_connectivity, + rr_notify_on_state_change}; + +static void round_robin_factory_ref(grpc_lb_policy_factory *factory) {} + +static void round_robin_factory_unref(grpc_lb_policy_factory *factory) {} + +static grpc_lb_policy *create_round_robin(grpc_lb_policy_factory *factory, + grpc_lb_policy_args *args) { + size_t i; + round_robin_lb_policy *p = gpr_malloc(sizeof(*p)); + GPR_ASSERT(args->num_subchannels > 0); + memset(p, 0, sizeof(*p)); + grpc_lb_policy_init(&p->base, &round_robin_lb_policy_vtable); + p->num_subchannels = args->num_subchannels; + p->subchannels = gpr_malloc(sizeof(*p->subchannels) * p->num_subchannels); + memset(p->subchannels, 0, sizeof(*p->subchannels) * p->num_subchannels); + grpc_connectivity_state_init(&p->state_tracker, GRPC_CHANNEL_IDLE, + "round_robin"); + + gpr_mu_init(&p->mu); + for (i = 0; i < args->num_subchannels; i++) { + subchannel_data *sd = gpr_malloc(sizeof(*sd)); + memset(sd, 0, sizeof(*sd)); + p->subchannels[i] = sd; + sd->policy = p; + sd->index = i; + sd->subchannel = args->subchannels[i]; + grpc_closure_init(&sd->connectivity_changed_closure, + rr_connectivity_changed, sd); + } + + /* The (dummy node) root of the ready list */ + p->ready_list.subchannel = NULL; + p->ready_list.prev = NULL; + p->ready_list.next = NULL; + p->ready_list_last_pick = &p->ready_list; + + return &p->base; +} + +static const grpc_lb_policy_factory_vtable round_robin_factory_vtable = { + round_robin_factory_ref, round_robin_factory_unref, create_round_robin, + "round_robin"}; + +static grpc_lb_policy_factory round_robin_lb_policy_factory = { + &round_robin_factory_vtable}; + +grpc_lb_policy_factory *grpc_round_robin_lb_factory_create() { + return &round_robin_lb_policy_factory; +} diff --git a/src/core/lib/client_config/lb_policies/round_robin.h b/src/core/lib/client_config/lb_policies/round_robin.h new file mode 100644 index 0000000000..52db1caa0c --- /dev/null +++ b/src/core/lib/client_config/lb_policies/round_robin.h @@ -0,0 +1,46 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CLIENT_CONFIG_LB_POLICIES_ROUND_ROBIN_H +#define GRPC_CORE_LIB_CLIENT_CONFIG_LB_POLICIES_ROUND_ROBIN_H + +#include "src/core/lib/client_config/lb_policy.h" + +extern int grpc_lb_round_robin_trace; + +#include "src/core/lib/client_config/lb_policy_factory.h" + +/** Returns a load balancing factory for the round robin policy */ +grpc_lb_policy_factory *grpc_round_robin_lb_factory_create(); + +#endif /* GRPC_CORE_LIB_CLIENT_CONFIG_LB_POLICIES_ROUND_ROBIN_H */ diff --git a/src/core/lib/client_config/lb_policy.c b/src/core/lib/client_config/lb_policy.c new file mode 100644 index 0000000000..ee20ccd76a --- /dev/null +++ b/src/core/lib/client_config/lb_policy.c @@ -0,0 +1,134 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/client_config/lb_policy.h" + +#define WEAK_REF_BITS 16 + +void grpc_lb_policy_init(grpc_lb_policy *policy, + const grpc_lb_policy_vtable *vtable) { + policy->vtable = vtable; + gpr_atm_no_barrier_store(&policy->ref_pair, 1 << WEAK_REF_BITS); + policy->interested_parties = grpc_pollset_set_create(); +} + +#ifdef GRPC_LB_POLICY_REFCOUNT_DEBUG +#define REF_FUNC_EXTRA_ARGS , const char *file, int line, const char *reason +#define REF_MUTATE_EXTRA_ARGS REF_FUNC_EXTRA_ARGS, const char *purpose +#define REF_FUNC_PASS_ARGS(new_reason) , file, line, new_reason +#define REF_MUTATE_PASS_ARGS(purpose) , file, line, reason, purpose +#else +#define REF_FUNC_EXTRA_ARGS +#define REF_MUTATE_EXTRA_ARGS +#define REF_FUNC_PASS_ARGS(new_reason) +#define REF_MUTATE_PASS_ARGS(x) +#endif + +static gpr_atm ref_mutate(grpc_lb_policy *c, gpr_atm delta, + int barrier REF_MUTATE_EXTRA_ARGS) { + gpr_atm old_val = barrier ? gpr_atm_full_fetch_add(&c->ref_pair, delta) + : gpr_atm_no_barrier_fetch_add(&c->ref_pair, delta); +#ifdef GRPC_LB_POLICY_REFCOUNT_DEBUG + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, + "LB_POLICY: %p % 12s 0x%08x -> 0x%08x [%s]", c, purpose, old_val, + old_val + delta, reason); +#endif + return old_val; +} + +void grpc_lb_policy_ref(grpc_lb_policy *policy REF_FUNC_EXTRA_ARGS) { + ref_mutate(policy, 1 << WEAK_REF_BITS, 0 REF_MUTATE_PASS_ARGS("STRONG_REF")); +} + +void grpc_lb_policy_unref(grpc_exec_ctx *exec_ctx, + grpc_lb_policy *policy REF_FUNC_EXTRA_ARGS) { + gpr_atm old_val = + ref_mutate(policy, (gpr_atm)1 - (gpr_atm)(1 << WEAK_REF_BITS), + 1 REF_MUTATE_PASS_ARGS("STRONG_UNREF")); + gpr_atm mask = ~(gpr_atm)((1 << WEAK_REF_BITS) - 1); + gpr_atm check = 1 << WEAK_REF_BITS; + if ((old_val & mask) == check) { + policy->vtable->shutdown(exec_ctx, policy); + } + grpc_lb_policy_weak_unref(exec_ctx, + policy REF_FUNC_PASS_ARGS("strong-unref")); +} + +void grpc_lb_policy_weak_ref(grpc_lb_policy *policy REF_FUNC_EXTRA_ARGS) { + ref_mutate(policy, 1, 0 REF_MUTATE_PASS_ARGS("WEAK_REF")); +} + +void grpc_lb_policy_weak_unref(grpc_exec_ctx *exec_ctx, + grpc_lb_policy *policy REF_FUNC_EXTRA_ARGS) { + gpr_atm old_val = + ref_mutate(policy, -(gpr_atm)1, 1 REF_MUTATE_PASS_ARGS("WEAK_UNREF")); + if (old_val == 1) { + grpc_pollset_set_destroy(policy->interested_parties); + policy->vtable->destroy(exec_ctx, policy); + } +} + +int grpc_lb_policy_pick(grpc_exec_ctx *exec_ctx, grpc_lb_policy *policy, + grpc_pollset *pollset, + grpc_metadata_batch *initial_metadata, + grpc_connected_subchannel **target, + grpc_closure *on_complete) { + return policy->vtable->pick(exec_ctx, policy, pollset, initial_metadata, + target, on_complete); +} + +void grpc_lb_policy_cancel_pick(grpc_exec_ctx *exec_ctx, grpc_lb_policy *policy, + grpc_connected_subchannel **target) { + policy->vtable->cancel_pick(exec_ctx, policy, target); +} + +void grpc_lb_policy_exit_idle(grpc_exec_ctx *exec_ctx, grpc_lb_policy *policy) { + policy->vtable->exit_idle(exec_ctx, policy); +} + +void grpc_lb_policy_ping_one(grpc_exec_ctx *exec_ctx, grpc_lb_policy *policy, + grpc_closure *closure) { + policy->vtable->ping_one(exec_ctx, policy, closure); +} + +void grpc_lb_policy_notify_on_state_change(grpc_exec_ctx *exec_ctx, + grpc_lb_policy *policy, + grpc_connectivity_state *state, + grpc_closure *closure) { + policy->vtable->notify_on_state_change(exec_ctx, policy, state, closure); +} + +grpc_connectivity_state grpc_lb_policy_check_connectivity( + grpc_exec_ctx *exec_ctx, grpc_lb_policy *policy) { + return policy->vtable->check_connectivity(exec_ctx, policy); +} diff --git a/src/core/lib/client_config/lb_policy.h b/src/core/lib/client_config/lb_policy.h new file mode 100644 index 0000000000..58a0a04d85 --- /dev/null +++ b/src/core/lib/client_config/lb_policy.h @@ -0,0 +1,144 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CLIENT_CONFIG_LB_POLICY_H +#define GRPC_CORE_LIB_CLIENT_CONFIG_LB_POLICY_H + +#include "src/core/lib/client_config/subchannel.h" +#include "src/core/lib/transport/connectivity_state.h" + +/** A load balancing policy: specified by a vtable and a struct (which + is expected to be extended to contain some parameters) */ +typedef struct grpc_lb_policy grpc_lb_policy; +typedef struct grpc_lb_policy_vtable grpc_lb_policy_vtable; + +typedef void (*grpc_lb_completion)(void *cb_arg, grpc_subchannel *subchannel, + grpc_status_code status, const char *errmsg); + +struct grpc_lb_policy { + const grpc_lb_policy_vtable *vtable; + gpr_atm ref_pair; + /* owned pointer to interested parties in load balancing decisions */ + grpc_pollset_set *interested_parties; +}; + +struct grpc_lb_policy_vtable { + void (*destroy)(grpc_exec_ctx *exec_ctx, grpc_lb_policy *policy); + + void (*shutdown)(grpc_exec_ctx *exec_ctx, grpc_lb_policy *policy); + + /** implement grpc_lb_policy_pick */ + int (*pick)(grpc_exec_ctx *exec_ctx, grpc_lb_policy *policy, + grpc_pollset *pollset, grpc_metadata_batch *initial_metadata, + grpc_connected_subchannel **target, grpc_closure *on_complete); + void (*cancel_pick)(grpc_exec_ctx *exec_ctx, grpc_lb_policy *policy, + grpc_connected_subchannel **target); + + void (*ping_one)(grpc_exec_ctx *exec_ctx, grpc_lb_policy *policy, + grpc_closure *closure); + + /** try to enter a READY connectivity state */ + void (*exit_idle)(grpc_exec_ctx *exec_ctx, grpc_lb_policy *policy); + + /** check the current connectivity of the lb_policy */ + grpc_connectivity_state (*check_connectivity)(grpc_exec_ctx *exec_ctx, + grpc_lb_policy *policy); + + /** call notify when the connectivity state of a channel changes from *state. + Updates *state with the new state of the policy */ + void (*notify_on_state_change)(grpc_exec_ctx *exec_ctx, + grpc_lb_policy *policy, + grpc_connectivity_state *state, + grpc_closure *closure); +}; + +/*#define GRPC_LB_POLICY_REFCOUNT_DEBUG*/ +#ifdef GRPC_LB_POLICY_REFCOUNT_DEBUG +#define GRPC_LB_POLICY_REF(p, r) \ + grpc_lb_policy_ref((p), __FILE__, __LINE__, (r)) +#define GRPC_LB_POLICY_UNREF(exec_ctx, p, r) \ + grpc_lb_policy_unref((exec_ctx), (p), __FILE__, __LINE__, (r)) +#define GRPC_LB_POLICY_WEAK_REF(p, r) \ + grpc_lb_policy_weak_ref((p), __FILE__, __LINE__, (r)) +#define GRPC_LB_POLICY_WEAK_UNREF(exec_ctx, p, r) \ + grpc_lb_policy_weak_unref((exec_ctx), (p), __FILE__, __LINE__, (r)) +void grpc_lb_policy_ref(grpc_lb_policy *policy, const char *file, int line, + const char *reason); +void grpc_lb_policy_unref(grpc_exec_ctx *exec_ctx, grpc_lb_policy *policy, + const char *file, int line, const char *reason); +void grpc_lb_policy_weak_ref(grpc_lb_policy *policy, const char *file, int line, + const char *reason); +void grpc_lb_policy_weak_unref(grpc_exec_ctx *exec_ctx, grpc_lb_policy *policy, + const char *file, int line, const char *reason); +#else +#define GRPC_LB_POLICY_REF(p, r) grpc_lb_policy_ref((p)) +#define GRPC_LB_POLICY_UNREF(cl, p, r) grpc_lb_policy_unref((cl), (p)) +#define GRPC_LB_POLICY_WEAK_REF(p, r) grpc_lb_policy_weak_ref((p)) +#define GRPC_LB_POLICY_WEAK_UNREF(cl, p, r) grpc_lb_policy_weak_unref((cl), (p)) +void grpc_lb_policy_ref(grpc_lb_policy *policy); +void grpc_lb_policy_unref(grpc_exec_ctx *exec_ctx, grpc_lb_policy *policy); +void grpc_lb_policy_weak_ref(grpc_lb_policy *policy); +void grpc_lb_policy_weak_unref(grpc_exec_ctx *exec_ctx, grpc_lb_policy *policy); +#endif + +/** called by concrete implementations to initialize the base struct */ +void grpc_lb_policy_init(grpc_lb_policy *policy, + const grpc_lb_policy_vtable *vtable); + +/** Given initial metadata in \a initial_metadata, find an appropriate + target for this rpc, and 'return' it by calling \a on_complete after setting + \a target. + Picking can be asynchronous. Any IO should be done under \a pollset. */ +int grpc_lb_policy_pick(grpc_exec_ctx *exec_ctx, grpc_lb_policy *policy, + grpc_pollset *pollset, + grpc_metadata_batch *initial_metadata, + grpc_connected_subchannel **target, + grpc_closure *on_complete); + +void grpc_lb_policy_ping_one(grpc_exec_ctx *exec_ctx, grpc_lb_policy *policy, + grpc_closure *closure); + +void grpc_lb_policy_cancel_pick(grpc_exec_ctx *exec_ctx, grpc_lb_policy *policy, + grpc_connected_subchannel **target); + +void grpc_lb_policy_exit_idle(grpc_exec_ctx *exec_ctx, grpc_lb_policy *policy); + +void grpc_lb_policy_notify_on_state_change(grpc_exec_ctx *exec_ctx, + grpc_lb_policy *policy, + grpc_connectivity_state *state, + grpc_closure *closure); + +grpc_connectivity_state grpc_lb_policy_check_connectivity( + grpc_exec_ctx *exec_ctx, grpc_lb_policy *policy); + +#endif /* GRPC_CORE_LIB_CLIENT_CONFIG_LB_POLICY_H */ diff --git a/src/core/lib/client_config/lb_policy_factory.c b/src/core/lib/client_config/lb_policy_factory.c new file mode 100644 index 0000000000..2ca6f42f89 --- /dev/null +++ b/src/core/lib/client_config/lb_policy_factory.c @@ -0,0 +1,48 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/client_config/lb_policy_factory.h" + +void grpc_lb_policy_factory_ref(grpc_lb_policy_factory* factory) { + factory->vtable->ref(factory); +} + +void grpc_lb_policy_factory_unref(grpc_lb_policy_factory* factory) { + factory->vtable->unref(factory); +} + +grpc_lb_policy* grpc_lb_policy_factory_create_lb_policy( + grpc_lb_policy_factory* factory, grpc_lb_policy_args* args) { + if (factory == NULL) return NULL; + return factory->vtable->create_lb_policy(factory, args); +} diff --git a/src/core/lib/client_config/lb_policy_factory.h b/src/core/lib/client_config/lb_policy_factory.h new file mode 100644 index 0000000000..36eaf178d9 --- /dev/null +++ b/src/core/lib/client_config/lb_policy_factory.h @@ -0,0 +1,73 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CLIENT_CONFIG_LB_POLICY_FACTORY_H +#define GRPC_CORE_LIB_CLIENT_CONFIG_LB_POLICY_FACTORY_H + +#include "src/core/lib/client_config/lb_policy.h" +#include "src/core/lib/client_config/subchannel.h" + +typedef struct grpc_lb_policy_factory grpc_lb_policy_factory; +typedef struct grpc_lb_policy_factory_vtable grpc_lb_policy_factory_vtable; + +/** grpc_lb_policy provides grpc_client_config objects to grpc_channel + objects */ +struct grpc_lb_policy_factory { + const grpc_lb_policy_factory_vtable *vtable; +}; + +typedef struct grpc_lb_policy_args { + grpc_subchannel **subchannels; + size_t num_subchannels; +} grpc_lb_policy_args; + +struct grpc_lb_policy_factory_vtable { + void (*ref)(grpc_lb_policy_factory *factory); + void (*unref)(grpc_lb_policy_factory *factory); + + /** Implementation of grpc_lb_policy_factory_create_lb_policy */ + grpc_lb_policy *(*create_lb_policy)(grpc_lb_policy_factory *factory, + grpc_lb_policy_args *args); + + /** Name for the LB policy this factory implements */ + const char *name; +}; + +void grpc_lb_policy_factory_ref(grpc_lb_policy_factory *factory); +void grpc_lb_policy_factory_unref(grpc_lb_policy_factory *factory); + +/** Create a lb_policy instance. */ +grpc_lb_policy *grpc_lb_policy_factory_create_lb_policy( + grpc_lb_policy_factory *factory, grpc_lb_policy_args *args); + +#endif /* GRPC_CORE_LIB_CLIENT_CONFIG_LB_POLICY_FACTORY_H */ diff --git a/src/core/lib/client_config/lb_policy_registry.c b/src/core/lib/client_config/lb_policy_registry.c new file mode 100644 index 0000000000..13acfe78cd --- /dev/null +++ b/src/core/lib/client_config/lb_policy_registry.c @@ -0,0 +1,88 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/client_config/lb_policy_registry.h" + +#include <string.h> + +#define MAX_POLICIES 10 + +static grpc_lb_policy_factory *g_all_of_the_lb_policies[MAX_POLICIES]; +static int g_number_of_lb_policies = 0; + +static grpc_lb_policy_factory *g_default_lb_policy_factory; + +void grpc_lb_policy_registry_init(grpc_lb_policy_factory *default_factory) { + g_number_of_lb_policies = 0; + g_default_lb_policy_factory = default_factory; +} + +void grpc_lb_policy_registry_shutdown(void) { + int i; + for (i = 0; i < g_number_of_lb_policies; i++) { + grpc_lb_policy_factory_unref(g_all_of_the_lb_policies[i]); + } +} + +void grpc_register_lb_policy(grpc_lb_policy_factory *factory) { + int i; + for (i = 0; i < g_number_of_lb_policies; i++) { + GPR_ASSERT(0 != strcmp(factory->vtable->name, + g_all_of_the_lb_policies[i]->vtable->name)); + } + GPR_ASSERT(g_number_of_lb_policies != MAX_POLICIES); + grpc_lb_policy_factory_ref(factory); + g_all_of_the_lb_policies[g_number_of_lb_policies++] = factory; +} + +static grpc_lb_policy_factory *lookup_factory(const char *name) { + int i; + + if (name == NULL) return NULL; + + for (i = 0; i < g_number_of_lb_policies; i++) { + if (0 == strcmp(name, g_all_of_the_lb_policies[i]->vtable->name)) { + return g_all_of_the_lb_policies[i]; + } + } + + return NULL; +} + +grpc_lb_policy *grpc_lb_policy_create(const char *name, + grpc_lb_policy_args *args) { + grpc_lb_policy_factory *factory = lookup_factory(name); + grpc_lb_policy *lb_policy = + grpc_lb_policy_factory_create_lb_policy(factory, args); + return lb_policy; +} diff --git a/src/core/lib/client_config/lb_policy_registry.h b/src/core/lib/client_config/lb_policy_registry.h new file mode 100644 index 0000000000..c251fd9f08 --- /dev/null +++ b/src/core/lib/client_config/lb_policy_registry.h @@ -0,0 +1,54 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CLIENT_CONFIG_LB_POLICY_REGISTRY_H +#define GRPC_CORE_LIB_CLIENT_CONFIG_LB_POLICY_REGISTRY_H + +#include "src/core/lib/client_config/lb_policy_factory.h" + +/** Initialize the registry and set \a default_factory as the factory to be + * returned when no name is provided in a lookup */ +void grpc_lb_policy_registry_init(grpc_lb_policy_factory *default_factory); +void grpc_lb_policy_registry_shutdown(void); + +/** Register a LB policy factory. */ +void grpc_register_lb_policy(grpc_lb_policy_factory *factory); + +/** Create a \a grpc_lb_policy instance. + * + * If \a name is NULL, the default factory from \a grpc_lb_policy_registry_init + * will be returned. */ +grpc_lb_policy *grpc_lb_policy_create(const char *name, + grpc_lb_policy_args *args); + +#endif /* GRPC_CORE_LIB_CLIENT_CONFIG_LB_POLICY_REGISTRY_H */ diff --git a/src/core/lib/client_config/resolver.c b/src/core/lib/client_config/resolver.c new file mode 100644 index 0000000000..32f0643adb --- /dev/null +++ b/src/core/lib/client_config/resolver.c @@ -0,0 +1,82 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/client_config/resolver.h" + +void grpc_resolver_init(grpc_resolver *resolver, + const grpc_resolver_vtable *vtable) { + resolver->vtable = vtable; + gpr_ref_init(&resolver->refs, 1); +} + +#ifdef GRPC_RESOLVER_REFCOUNT_DEBUG +void grpc_resolver_ref(grpc_resolver *resolver, grpc_closure_list *closure_list, + const char *file, int line, const char *reason) { + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, "RESOLVER:%p ref %d -> %d %s", + resolver, (int)resolver->refs.count, (int)resolver->refs.count + 1, + reason); +#else +void grpc_resolver_ref(grpc_resolver *resolver) { +#endif + gpr_ref(&resolver->refs); +} + +#ifdef GRPC_RESOLVER_REFCOUNT_DEBUG +void grpc_resolver_unref(grpc_resolver *resolver, + grpc_closure_list *closure_list, const char *file, + int line, const char *reason) { + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, "RESOLVER:%p unref %d -> %d %s", + resolver, (int)resolver->refs.count, (int)resolver->refs.count - 1, + reason); +#else +void grpc_resolver_unref(grpc_exec_ctx *exec_ctx, grpc_resolver *resolver) { +#endif + if (gpr_unref(&resolver->refs)) { + resolver->vtable->destroy(exec_ctx, resolver); + } +} + +void grpc_resolver_shutdown(grpc_exec_ctx *exec_ctx, grpc_resolver *resolver) { + resolver->vtable->shutdown(exec_ctx, resolver); +} + +void grpc_resolver_channel_saw_error(grpc_exec_ctx *exec_ctx, + grpc_resolver *resolver) { + resolver->vtable->channel_saw_error(exec_ctx, resolver); +} + +void grpc_resolver_next(grpc_exec_ctx *exec_ctx, grpc_resolver *resolver, + grpc_client_config **target_config, + grpc_closure *on_complete) { + resolver->vtable->next(exec_ctx, resolver, target_config, on_complete); +} diff --git a/src/core/lib/client_config/resolver.h b/src/core/lib/client_config/resolver.h new file mode 100644 index 0000000000..1ee879293a --- /dev/null +++ b/src/core/lib/client_config/resolver.h @@ -0,0 +1,94 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CLIENT_CONFIG_RESOLVER_H +#define GRPC_CORE_LIB_CLIENT_CONFIG_RESOLVER_H + +#include "src/core/lib/client_config/client_config.h" +#include "src/core/lib/client_config/subchannel.h" +#include "src/core/lib/iomgr/iomgr.h" + +typedef struct grpc_resolver grpc_resolver; +typedef struct grpc_resolver_vtable grpc_resolver_vtable; + +/** grpc_resolver provides grpc_client_config objects to grpc_channel + objects */ +struct grpc_resolver { + const grpc_resolver_vtable *vtable; + gpr_refcount refs; +}; + +struct grpc_resolver_vtable { + void (*destroy)(grpc_exec_ctx *exec_ctx, grpc_resolver *resolver); + void (*shutdown)(grpc_exec_ctx *exec_ctx, grpc_resolver *resolver); + void (*channel_saw_error)(grpc_exec_ctx *exec_ctx, grpc_resolver *resolver); + void (*next)(grpc_exec_ctx *exec_ctx, grpc_resolver *resolver, + grpc_client_config **target_config, grpc_closure *on_complete); +}; + +#ifdef GRPC_RESOLVER_REFCOUNT_DEBUG +#define GRPC_RESOLVER_REF(p, r) grpc_resolver_ref((p), __FILE__, __LINE__, (r)) +#define GRPC_RESOLVER_UNREF(cl, p, r) \ + grpc_resolver_unref((cl), (p), __FILE__, __LINE__, (r)) +void grpc_resolver_ref(grpc_resolver *policy, const char *file, int line, + const char *reason); +void grpc_resolver_unref(grpc_resolver *policy, grpc_closure_list *closure_list, + const char *file, int line, const char *reason); +#else +#define GRPC_RESOLVER_REF(p, r) grpc_resolver_ref((p)) +#define GRPC_RESOLVER_UNREF(cl, p, r) grpc_resolver_unref((cl), (p)) +void grpc_resolver_ref(grpc_resolver *policy); +void grpc_resolver_unref(grpc_exec_ctx *exec_ctx, grpc_resolver *policy); +#endif + +void grpc_resolver_init(grpc_resolver *resolver, + const grpc_resolver_vtable *vtable); + +void grpc_resolver_shutdown(grpc_exec_ctx *exec_ctx, grpc_resolver *resolver); + +/** Notification that the channel has seen an error on some address. + Can be used as a hint that re-resolution is desirable soon. */ +void grpc_resolver_channel_saw_error(grpc_exec_ctx *exec_ctx, + grpc_resolver *resolver); + +/** Get the next client config. Called by the channel to fetch a new + configuration. Expected to set *target_config with a new configuration, + and then schedule on_complete for execution. + + If resolution is fatally broken, set *target_config to NULL and + schedule on_complete. */ +void grpc_resolver_next(grpc_exec_ctx *exec_ctx, grpc_resolver *resolver, + grpc_client_config **target_config, + grpc_closure *on_complete); + +#endif /* GRPC_CORE_LIB_CLIENT_CONFIG_RESOLVER_H */ diff --git a/src/core/lib/client_config/resolver_factory.c b/src/core/lib/client_config/resolver_factory.c new file mode 100644 index 0000000000..0f76c664fa --- /dev/null +++ b/src/core/lib/client_config/resolver_factory.c @@ -0,0 +1,55 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/client_config/resolver_factory.h" + +void grpc_resolver_factory_ref(grpc_resolver_factory* factory) { + factory->vtable->ref(factory); +} + +void grpc_resolver_factory_unref(grpc_resolver_factory* factory) { + factory->vtable->unref(factory); +} + +/** Create a resolver instance for a name */ +grpc_resolver* grpc_resolver_factory_create_resolver( + grpc_resolver_factory* factory, grpc_resolver_args* args) { + if (factory == NULL) return NULL; + return factory->vtable->create_resolver(factory, args); +} + +char* grpc_resolver_factory_get_default_authority( + grpc_resolver_factory* factory, grpc_uri* uri) { + if (factory == NULL) return NULL; + return factory->vtable->get_default_authority(factory, uri); +} diff --git a/src/core/lib/client_config/resolver_factory.h b/src/core/lib/client_config/resolver_factory.h new file mode 100644 index 0000000000..7765c3c844 --- /dev/null +++ b/src/core/lib/client_config/resolver_factory.h @@ -0,0 +1,82 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CLIENT_CONFIG_RESOLVER_FACTORY_H +#define GRPC_CORE_LIB_CLIENT_CONFIG_RESOLVER_FACTORY_H + +#include "src/core/lib/client_config/resolver.h" +#include "src/core/lib/client_config/subchannel_factory.h" +#include "src/core/lib/client_config/uri_parser.h" + +typedef struct grpc_resolver_factory grpc_resolver_factory; +typedef struct grpc_resolver_factory_vtable grpc_resolver_factory_vtable; + +/** grpc_resolver provides grpc_client_config objects to grpc_channel + objects */ +struct grpc_resolver_factory { + const grpc_resolver_factory_vtable *vtable; +}; + +typedef struct grpc_resolver_args { + grpc_uri *uri; + grpc_subchannel_factory *subchannel_factory; +} grpc_resolver_args; + +struct grpc_resolver_factory_vtable { + void (*ref)(grpc_resolver_factory *factory); + void (*unref)(grpc_resolver_factory *factory); + + /** Implementation of grpc_resolver_factory_create_resolver */ + grpc_resolver *(*create_resolver)(grpc_resolver_factory *factory, + grpc_resolver_args *args); + + /** Implementation of grpc_resolver_factory_get_default_authority */ + char *(*get_default_authority)(grpc_resolver_factory *factory, grpc_uri *uri); + + /** URI scheme that this factory implements */ + const char *scheme; +}; + +void grpc_resolver_factory_ref(grpc_resolver_factory *resolver); +void grpc_resolver_factory_unref(grpc_resolver_factory *resolver); + +/** Create a resolver instance for a name */ +grpc_resolver *grpc_resolver_factory_create_resolver( + grpc_resolver_factory *factory, grpc_resolver_args *args); + +/** Return a (freshly allocated with gpr_malloc) string representing + the default authority to use for this scheme. */ +char *grpc_resolver_factory_get_default_authority( + grpc_resolver_factory *factory, grpc_uri *uri); + +#endif /* GRPC_CORE_LIB_CLIENT_CONFIG_RESOLVER_FACTORY_H */ diff --git a/src/core/lib/client_config/resolver_registry.c b/src/core/lib/client_config/resolver_registry.c new file mode 100644 index 0000000000..29bd00c284 --- /dev/null +++ b/src/core/lib/client_config/resolver_registry.c @@ -0,0 +1,137 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/client_config/resolver_registry.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> + +#define MAX_RESOLVERS 10 + +static grpc_resolver_factory *g_all_of_the_resolvers[MAX_RESOLVERS]; +static int g_number_of_resolvers = 0; + +static char *g_default_resolver_prefix; + +void grpc_resolver_registry_init(const char *default_resolver_prefix) { + g_number_of_resolvers = 0; + g_default_resolver_prefix = gpr_strdup(default_resolver_prefix); +} + +void grpc_resolver_registry_shutdown(void) { + int i; + for (i = 0; i < g_number_of_resolvers; i++) { + grpc_resolver_factory_unref(g_all_of_the_resolvers[i]); + } + gpr_free(g_default_resolver_prefix); +} + +void grpc_register_resolver_type(grpc_resolver_factory *factory) { + int i; + for (i = 0; i < g_number_of_resolvers; i++) { + GPR_ASSERT(0 != strcmp(factory->vtable->scheme, + g_all_of_the_resolvers[i]->vtable->scheme)); + } + GPR_ASSERT(g_number_of_resolvers != MAX_RESOLVERS); + grpc_resolver_factory_ref(factory); + g_all_of_the_resolvers[g_number_of_resolvers++] = factory; +} + +static grpc_resolver_factory *lookup_factory(grpc_uri *uri) { + int i; + + /* handling NULL uri's here simplifies grpc_resolver_create */ + if (!uri) return NULL; + + for (i = 0; i < g_number_of_resolvers; i++) { + if (0 == strcmp(uri->scheme, g_all_of_the_resolvers[i]->vtable->scheme)) { + return g_all_of_the_resolvers[i]; + } + } + + return NULL; +} + +static grpc_resolver_factory *resolve_factory(const char *target, + grpc_uri **uri) { + char *tmp; + grpc_resolver_factory *factory = NULL; + + GPR_ASSERT(uri != NULL); + *uri = grpc_uri_parse(target, 1); + factory = lookup_factory(*uri); + if (factory == NULL) { + if (g_default_resolver_prefix != NULL) { + grpc_uri_destroy(*uri); + gpr_asprintf(&tmp, "%s%s", g_default_resolver_prefix, target); + *uri = grpc_uri_parse(tmp, 1); + factory = lookup_factory(*uri); + if (factory == NULL) { + grpc_uri_destroy(grpc_uri_parse(target, 0)); + grpc_uri_destroy(grpc_uri_parse(tmp, 0)); + gpr_log(GPR_ERROR, "don't know how to resolve '%s' or '%s'", target, + tmp); + } + gpr_free(tmp); + } else { + grpc_uri_destroy(grpc_uri_parse(target, 0)); + gpr_log(GPR_ERROR, "don't know how to resolve '%s'", target); + } + } + return factory; +} + +grpc_resolver *grpc_resolver_create( + const char *target, grpc_subchannel_factory *subchannel_factory) { + grpc_uri *uri = NULL; + grpc_resolver_factory *factory = resolve_factory(target, &uri); + grpc_resolver *resolver; + grpc_resolver_args args; + memset(&args, 0, sizeof(args)); + args.uri = uri; + args.subchannel_factory = subchannel_factory; + resolver = grpc_resolver_factory_create_resolver(factory, &args); + grpc_uri_destroy(uri); + return resolver; +} + +char *grpc_get_default_authority(const char *target) { + grpc_uri *uri = NULL; + grpc_resolver_factory *factory = resolve_factory(target, &uri); + char *authority = grpc_resolver_factory_get_default_authority(factory, uri); + grpc_uri_destroy(uri); + return authority; +} diff --git a/src/core/lib/client_config/resolver_registry.h b/src/core/lib/client_config/resolver_registry.h new file mode 100644 index 0000000000..22289ca6bd --- /dev/null +++ b/src/core/lib/client_config/resolver_registry.h @@ -0,0 +1,65 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CLIENT_CONFIG_RESOLVER_REGISTRY_H +#define GRPC_CORE_LIB_CLIENT_CONFIG_RESOLVER_REGISTRY_H + +#include "src/core/lib/client_config/resolver_factory.h" + +void grpc_resolver_registry_init(const char *default_prefix); +void grpc_resolver_registry_shutdown(void); + +/** Register a resolver type. + URI's of \a scheme will be resolved with the given resolver. + If \a priority is greater than zero, then the resolver will be eligible + to resolve names that are passed in with no scheme. Higher priority + resolvers will be tried before lower priority schemes. */ +void grpc_register_resolver_type(grpc_resolver_factory *factory); + +/** Create a resolver given \a target. + First tries to parse \a target as a URI. If this succeeds, tries + to locate a registered resolver factory based on the URI scheme. + If parsing or location fails, prefixes default_prefix from + grpc_resolver_registry_init to target, and tries again (if default_prefix + was not NULL). + If a resolver factory was found, use it to instantiate a resolver and + return it. + If a resolver factory was not found, return NULL. */ +grpc_resolver *grpc_resolver_create( + const char *target, grpc_subchannel_factory *subchannel_factory); + +/** Given a target, return a (freshly allocated with gpr_malloc) string + representing the default authority to pass from a client. */ +char *grpc_get_default_authority(const char *target); + +#endif /* GRPC_CORE_LIB_CLIENT_CONFIG_RESOLVER_REGISTRY_H */ diff --git a/src/core/lib/client_config/resolvers/dns_resolver.c b/src/core/lib/client_config/resolvers/dns_resolver.c new file mode 100644 index 0000000000..ab445730ad --- /dev/null +++ b/src/core/lib/client_config/resolvers/dns_resolver.c @@ -0,0 +1,309 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/client_config/resolvers/dns_resolver.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/host_port.h> +#include <grpc/support/string_util.h> + +#include "src/core/lib/client_config/lb_policy_registry.h" +#include "src/core/lib/iomgr/resolve_address.h" +#include "src/core/lib/iomgr/timer.h" +#include "src/core/lib/support/backoff.h" +#include "src/core/lib/support/string.h" + +#define BACKOFF_MULTIPLIER 1.6 +#define BACKOFF_JITTER 0.2 +#define BACKOFF_MIN_SECONDS 1 +#define BACKOFF_MAX_SECONDS 120 + +typedef struct { + /** base class: must be first */ + grpc_resolver base; + /** refcount */ + gpr_refcount refs; + /** name to resolve */ + char *name; + /** default port to use */ + char *default_port; + /** subchannel factory */ + grpc_subchannel_factory *subchannel_factory; + /** load balancing policy name */ + char *lb_policy_name; + + /** mutex guarding the rest of the state */ + gpr_mu mu; + /** are we currently resolving? */ + int resolving; + /** which version of resolved_config have we published? */ + int published_version; + /** which version of resolved_config is current? */ + int resolved_version; + /** pending next completion, or NULL */ + grpc_closure *next_completion; + /** target config address for next completion */ + grpc_client_config **target_config; + /** current (fully resolved) config */ + grpc_client_config *resolved_config; + /** retry timer */ + bool have_retry_timer; + grpc_timer retry_timer; + /** retry backoff state */ + gpr_backoff backoff_state; +} dns_resolver; + +static void dns_destroy(grpc_exec_ctx *exec_ctx, grpc_resolver *r); + +static void dns_start_resolving_locked(dns_resolver *r); +static void dns_maybe_finish_next_locked(grpc_exec_ctx *exec_ctx, + dns_resolver *r); + +static void dns_shutdown(grpc_exec_ctx *exec_ctx, grpc_resolver *r); +static void dns_channel_saw_error(grpc_exec_ctx *exec_ctx, grpc_resolver *r); +static void dns_next(grpc_exec_ctx *exec_ctx, grpc_resolver *r, + grpc_client_config **target_config, + grpc_closure *on_complete); + +static const grpc_resolver_vtable dns_resolver_vtable = { + dns_destroy, dns_shutdown, dns_channel_saw_error, dns_next}; + +static void dns_shutdown(grpc_exec_ctx *exec_ctx, grpc_resolver *resolver) { + dns_resolver *r = (dns_resolver *)resolver; + gpr_mu_lock(&r->mu); + if (r->have_retry_timer) { + grpc_timer_cancel(exec_ctx, &r->retry_timer); + } + if (r->next_completion != NULL) { + *r->target_config = NULL; + grpc_exec_ctx_enqueue(exec_ctx, r->next_completion, true, NULL); + r->next_completion = NULL; + } + gpr_mu_unlock(&r->mu); +} + +static void dns_channel_saw_error(grpc_exec_ctx *exec_ctx, + grpc_resolver *resolver) { + dns_resolver *r = (dns_resolver *)resolver; + gpr_mu_lock(&r->mu); + if (!r->resolving) { + gpr_backoff_reset(&r->backoff_state); + dns_start_resolving_locked(r); + } + gpr_mu_unlock(&r->mu); +} + +static void dns_next(grpc_exec_ctx *exec_ctx, grpc_resolver *resolver, + grpc_client_config **target_config, + grpc_closure *on_complete) { + dns_resolver *r = (dns_resolver *)resolver; + gpr_mu_lock(&r->mu); + GPR_ASSERT(!r->next_completion); + r->next_completion = on_complete; + r->target_config = target_config; + if (r->resolved_version == 0 && !r->resolving) { + gpr_backoff_reset(&r->backoff_state); + dns_start_resolving_locked(r); + } else { + dns_maybe_finish_next_locked(exec_ctx, r); + } + gpr_mu_unlock(&r->mu); +} + +static void dns_on_retry_timer(grpc_exec_ctx *exec_ctx, void *arg, + bool success) { + dns_resolver *r = arg; + + gpr_mu_lock(&r->mu); + r->have_retry_timer = false; + if (success) { + if (!r->resolving) { + dns_start_resolving_locked(r); + } + } + gpr_mu_unlock(&r->mu); + + GRPC_RESOLVER_UNREF(exec_ctx, &r->base, "retry-timer"); +} + +static void dns_on_resolved(grpc_exec_ctx *exec_ctx, void *arg, + grpc_resolved_addresses *addresses) { + dns_resolver *r = arg; + grpc_client_config *config = NULL; + grpc_subchannel **subchannels; + grpc_subchannel_args args; + grpc_lb_policy *lb_policy; + size_t i; + gpr_mu_lock(&r->mu); + GPR_ASSERT(r->resolving); + r->resolving = 0; + if (addresses != NULL) { + grpc_lb_policy_args lb_policy_args; + config = grpc_client_config_create(); + subchannels = gpr_malloc(sizeof(grpc_subchannel *) * addresses->naddrs); + size_t naddrs = 0; + for (i = 0; i < addresses->naddrs; i++) { + memset(&args, 0, sizeof(args)); + args.addr = (struct sockaddr *)(addresses->addrs[i].addr); + args.addr_len = (size_t)addresses->addrs[i].len; + grpc_subchannel *subchannel = grpc_subchannel_factory_create_subchannel( + exec_ctx, r->subchannel_factory, &args); + if (subchannel != NULL) { + subchannels[naddrs++] = subchannel; + } + } + memset(&lb_policy_args, 0, sizeof(lb_policy_args)); + lb_policy_args.subchannels = subchannels; + lb_policy_args.num_subchannels = naddrs; + lb_policy = grpc_lb_policy_create(r->lb_policy_name, &lb_policy_args); + if (lb_policy != NULL) { + grpc_client_config_set_lb_policy(config, lb_policy); + GRPC_LB_POLICY_UNREF(exec_ctx, lb_policy, "construction"); + } + grpc_resolved_addresses_destroy(addresses); + gpr_free(subchannels); + } else { + gpr_timespec now = gpr_now(GPR_CLOCK_MONOTONIC); + gpr_timespec next_try = gpr_backoff_step(&r->backoff_state, now); + gpr_timespec timeout = gpr_time_sub(next_try, now); + gpr_log(GPR_DEBUG, "dns resolution failed: retrying in %d.%09d seconds", + timeout.tv_sec, timeout.tv_nsec); + GPR_ASSERT(!r->have_retry_timer); + r->have_retry_timer = true; + GRPC_RESOLVER_REF(&r->base, "retry-timer"); + grpc_timer_init(exec_ctx, &r->retry_timer, next_try, dns_on_retry_timer, r, + now); + } + if (r->resolved_config) { + grpc_client_config_unref(exec_ctx, r->resolved_config); + } + r->resolved_config = config; + r->resolved_version++; + dns_maybe_finish_next_locked(exec_ctx, r); + gpr_mu_unlock(&r->mu); + + GRPC_RESOLVER_UNREF(exec_ctx, &r->base, "dns-resolving"); +} + +static void dns_start_resolving_locked(dns_resolver *r) { + GRPC_RESOLVER_REF(&r->base, "dns-resolving"); + GPR_ASSERT(!r->resolving); + r->resolving = 1; + grpc_resolve_address(r->name, r->default_port, dns_on_resolved, r); +} + +static void dns_maybe_finish_next_locked(grpc_exec_ctx *exec_ctx, + dns_resolver *r) { + if (r->next_completion != NULL && + r->resolved_version != r->published_version) { + *r->target_config = r->resolved_config; + if (r->resolved_config) { + grpc_client_config_ref(r->resolved_config); + } + grpc_exec_ctx_enqueue(exec_ctx, r->next_completion, true, NULL); + r->next_completion = NULL; + r->published_version = r->resolved_version; + } +} + +static void dns_destroy(grpc_exec_ctx *exec_ctx, grpc_resolver *gr) { + dns_resolver *r = (dns_resolver *)gr; + gpr_mu_destroy(&r->mu); + if (r->resolved_config) { + grpc_client_config_unref(exec_ctx, r->resolved_config); + } + grpc_subchannel_factory_unref(exec_ctx, r->subchannel_factory); + gpr_free(r->name); + gpr_free(r->default_port); + gpr_free(r->lb_policy_name); + gpr_free(r); +} + +static grpc_resolver *dns_create(grpc_resolver_args *args, + const char *default_port, + const char *lb_policy_name) { + dns_resolver *r; + const char *path = args->uri->path; + + if (0 != strcmp(args->uri->authority, "")) { + gpr_log(GPR_ERROR, "authority based dns uri's not supported"); + return NULL; + } + + if (path[0] == '/') ++path; + + r = gpr_malloc(sizeof(dns_resolver)); + memset(r, 0, sizeof(*r)); + gpr_ref_init(&r->refs, 1); + gpr_mu_init(&r->mu); + grpc_resolver_init(&r->base, &dns_resolver_vtable); + r->name = gpr_strdup(path); + r->default_port = gpr_strdup(default_port); + r->subchannel_factory = args->subchannel_factory; + gpr_backoff_init(&r->backoff_state, BACKOFF_MULTIPLIER, BACKOFF_JITTER, + BACKOFF_MIN_SECONDS * 1000, BACKOFF_MAX_SECONDS * 1000); + grpc_subchannel_factory_ref(r->subchannel_factory); + r->lb_policy_name = gpr_strdup(lb_policy_name); + return &r->base; +} + +/* + * FACTORY + */ + +static void dns_factory_ref(grpc_resolver_factory *factory) {} + +static void dns_factory_unref(grpc_resolver_factory *factory) {} + +static grpc_resolver *dns_factory_create_resolver( + grpc_resolver_factory *factory, grpc_resolver_args *args) { + return dns_create(args, "https", "pick_first"); +} + +char *dns_factory_get_default_host_name(grpc_resolver_factory *factory, + grpc_uri *uri) { + const char *path = uri->path; + if (path[0] == '/') ++path; + return gpr_strdup(path); +} + +static const grpc_resolver_factory_vtable dns_factory_vtable = { + dns_factory_ref, dns_factory_unref, dns_factory_create_resolver, + dns_factory_get_default_host_name, "dns"}; +static grpc_resolver_factory dns_resolver_factory = {&dns_factory_vtable}; + +grpc_resolver_factory *grpc_dns_resolver_factory_create() { + return &dns_resolver_factory; +} diff --git a/src/core/lib/client_config/resolvers/dns_resolver.h b/src/core/lib/client_config/resolvers/dns_resolver.h new file mode 100644 index 0000000000..eb46e41c77 --- /dev/null +++ b/src/core/lib/client_config/resolvers/dns_resolver.h @@ -0,0 +1,42 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CLIENT_CONFIG_RESOLVERS_DNS_RESOLVER_H +#define GRPC_CORE_LIB_CLIENT_CONFIG_RESOLVERS_DNS_RESOLVER_H + +#include "src/core/lib/client_config/resolver_factory.h" + +/** Create a dns resolver factory */ +grpc_resolver_factory *grpc_dns_resolver_factory_create(void); + +#endif /* GRPC_CORE_LIB_CLIENT_CONFIG_RESOLVERS_DNS_RESOLVER_H */ diff --git a/src/core/lib/client_config/resolvers/sockaddr_resolver.c b/src/core/lib/client_config/resolvers/sockaddr_resolver.c new file mode 100644 index 0000000000..66cddc3ed9 --- /dev/null +++ b/src/core/lib/client_config/resolvers/sockaddr_resolver.c @@ -0,0 +1,372 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#include "src/core/lib/client_config/resolvers/sockaddr_resolver.h" + +#include <stdio.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/host_port.h> +#include <grpc/support/string_util.h> + +#include "src/core/lib/client_config/lb_policy_registry.h" +#include "src/core/lib/iomgr/resolve_address.h" +#include "src/core/lib/iomgr/unix_sockets_posix.h" +#include "src/core/lib/support/string.h" + +typedef struct { + /** base class: must be first */ + grpc_resolver base; + /** refcount */ + gpr_refcount refs; + /** subchannel factory */ + grpc_subchannel_factory *subchannel_factory; + /** load balancing policy name */ + char *lb_policy_name; + + /** the addresses that we've 'resolved' */ + struct sockaddr_storage *addrs; + /** the corresponding length of the addresses */ + size_t *addrs_len; + /** how many elements in \a addrs */ + size_t num_addrs; + + /** mutex guarding the rest of the state */ + gpr_mu mu; + /** have we published? */ + int published; + /** pending next completion, or NULL */ + grpc_closure *next_completion; + /** target config address for next completion */ + grpc_client_config **target_config; +} sockaddr_resolver; + +static void sockaddr_destroy(grpc_exec_ctx *exec_ctx, grpc_resolver *r); + +static void sockaddr_maybe_finish_next_locked(grpc_exec_ctx *exec_ctx, + sockaddr_resolver *r); + +static void sockaddr_shutdown(grpc_exec_ctx *exec_ctx, grpc_resolver *r); +static void sockaddr_channel_saw_error(grpc_exec_ctx *exec_ctx, + grpc_resolver *r); +static void sockaddr_next(grpc_exec_ctx *exec_ctx, grpc_resolver *r, + grpc_client_config **target_config, + grpc_closure *on_complete); + +static const grpc_resolver_vtable sockaddr_resolver_vtable = { + sockaddr_destroy, sockaddr_shutdown, sockaddr_channel_saw_error, + sockaddr_next}; + +static void sockaddr_shutdown(grpc_exec_ctx *exec_ctx, + grpc_resolver *resolver) { + sockaddr_resolver *r = (sockaddr_resolver *)resolver; + gpr_mu_lock(&r->mu); + if (r->next_completion != NULL) { + *r->target_config = NULL; + grpc_exec_ctx_enqueue(exec_ctx, r->next_completion, true, NULL); + r->next_completion = NULL; + } + gpr_mu_unlock(&r->mu); +} + +static void sockaddr_channel_saw_error(grpc_exec_ctx *exec_ctx, + grpc_resolver *resolver) { + sockaddr_resolver *r = (sockaddr_resolver *)resolver; + gpr_mu_lock(&r->mu); + r->published = 0; + sockaddr_maybe_finish_next_locked(exec_ctx, r); + gpr_mu_unlock(&r->mu); +} + +static void sockaddr_next(grpc_exec_ctx *exec_ctx, grpc_resolver *resolver, + grpc_client_config **target_config, + grpc_closure *on_complete) { + sockaddr_resolver *r = (sockaddr_resolver *)resolver; + gpr_mu_lock(&r->mu); + GPR_ASSERT(!r->next_completion); + r->next_completion = on_complete; + r->target_config = target_config; + sockaddr_maybe_finish_next_locked(exec_ctx, r); + gpr_mu_unlock(&r->mu); +} + +static void sockaddr_maybe_finish_next_locked(grpc_exec_ctx *exec_ctx, + sockaddr_resolver *r) { + grpc_client_config *cfg; + grpc_lb_policy *lb_policy; + grpc_lb_policy_args lb_policy_args; + grpc_subchannel **subchannels; + grpc_subchannel_args args; + + if (r->next_completion != NULL && !r->published) { + size_t i; + cfg = grpc_client_config_create(); + subchannels = gpr_malloc(sizeof(grpc_subchannel *) * r->num_addrs); + for (i = 0; i < r->num_addrs; i++) { + memset(&args, 0, sizeof(args)); + args.addr = (struct sockaddr *)&r->addrs[i]; + args.addr_len = r->addrs_len[i]; + subchannels[i] = grpc_subchannel_factory_create_subchannel( + exec_ctx, r->subchannel_factory, &args); + } + memset(&lb_policy_args, 0, sizeof(lb_policy_args)); + lb_policy_args.subchannels = subchannels; + lb_policy_args.num_subchannels = r->num_addrs; + lb_policy = grpc_lb_policy_create(r->lb_policy_name, &lb_policy_args); + gpr_free(subchannels); + grpc_client_config_set_lb_policy(cfg, lb_policy); + GRPC_LB_POLICY_UNREF(exec_ctx, lb_policy, "sockaddr"); + r->published = 1; + *r->target_config = cfg; + grpc_exec_ctx_enqueue(exec_ctx, r->next_completion, true, NULL); + r->next_completion = NULL; + } +} + +static void sockaddr_destroy(grpc_exec_ctx *exec_ctx, grpc_resolver *gr) { + sockaddr_resolver *r = (sockaddr_resolver *)gr; + gpr_mu_destroy(&r->mu); + grpc_subchannel_factory_unref(exec_ctx, r->subchannel_factory); + gpr_free(r->addrs); + gpr_free(r->addrs_len); + gpr_free(r->lb_policy_name); + gpr_free(r); +} + +static char *ip_get_default_authority(grpc_uri *uri) { + const char *path = uri->path; + if (path[0] == '/') ++path; + return gpr_strdup(path); +} + +static char *ipv4_get_default_authority(grpc_resolver_factory *factory, + grpc_uri *uri) { + return ip_get_default_authority(uri); +} + +static char *ipv6_get_default_authority(grpc_resolver_factory *factory, + grpc_uri *uri) { + return ip_get_default_authority(uri); +} + +static int parse_ipv4(grpc_uri *uri, struct sockaddr_storage *addr, + size_t *len) { + const char *host_port = uri->path; + char *host; + char *port; + int port_num; + int result = 0; + struct sockaddr_in *in = (struct sockaddr_in *)addr; + + if (*host_port == '/') ++host_port; + if (!gpr_split_host_port(host_port, &host, &port)) { + return 0; + } + + memset(in, 0, sizeof(*in)); + *len = sizeof(*in); + in->sin_family = AF_INET; + if (inet_pton(AF_INET, host, &in->sin_addr) == 0) { + gpr_log(GPR_ERROR, "invalid ipv4 address: '%s'", host); + goto done; + } + + if (port != NULL) { + if (sscanf(port, "%d", &port_num) != 1 || port_num < 0 || + port_num > 65535) { + gpr_log(GPR_ERROR, "invalid ipv4 port: '%s'", port); + goto done; + } + in->sin_port = htons((uint16_t)port_num); + } else { + gpr_log(GPR_ERROR, "no port given for ipv4 scheme"); + goto done; + } + + result = 1; +done: + gpr_free(host); + gpr_free(port); + return result; +} + +static int parse_ipv6(grpc_uri *uri, struct sockaddr_storage *addr, + size_t *len) { + const char *host_port = uri->path; + char *host; + char *port; + int port_num; + int result = 0; + struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)addr; + + if (*host_port == '/') ++host_port; + if (!gpr_split_host_port(host_port, &host, &port)) { + return 0; + } + + memset(in6, 0, sizeof(*in6)); + *len = sizeof(*in6); + in6->sin6_family = AF_INET6; + if (inet_pton(AF_INET6, host, &in6->sin6_addr) == 0) { + gpr_log(GPR_ERROR, "invalid ipv6 address: '%s'", host); + goto done; + } + + if (port != NULL) { + if (sscanf(port, "%d", &port_num) != 1 || port_num < 0 || + port_num > 65535) { + gpr_log(GPR_ERROR, "invalid ipv6 port: '%s'", port); + goto done; + } + in6->sin6_port = htons((uint16_t)port_num); + } else { + gpr_log(GPR_ERROR, "no port given for ipv6 scheme"); + goto done; + } + + result = 1; +done: + gpr_free(host); + gpr_free(port); + return result; +} + +static void do_nothing(void *ignored) {} + +static grpc_resolver *sockaddr_create( + grpc_resolver_args *args, const char *default_lb_policy_name, + int parse(grpc_uri *uri, struct sockaddr_storage *dst, size_t *len)) { + size_t i; + int errors_found = 0; /* GPR_FALSE */ + sockaddr_resolver *r; + gpr_slice path_slice; + gpr_slice_buffer path_parts; + + if (0 != strcmp(args->uri->authority, "")) { + gpr_log(GPR_ERROR, "authority based uri's not supported by the %s scheme", + args->uri->scheme); + return NULL; + } + + r = gpr_malloc(sizeof(sockaddr_resolver)); + memset(r, 0, sizeof(*r)); + + r->lb_policy_name = NULL; + if (0 != strcmp(args->uri->query, "")) { + gpr_slice query_slice; + gpr_slice_buffer query_parts; + + query_slice = + gpr_slice_new(args->uri->query, strlen(args->uri->query), do_nothing); + gpr_slice_buffer_init(&query_parts); + gpr_slice_split(query_slice, "=", &query_parts); + GPR_ASSERT(query_parts.count == 2); + if (0 == gpr_slice_str_cmp(query_parts.slices[0], "lb_policy")) { + r->lb_policy_name = gpr_dump_slice(query_parts.slices[1], GPR_DUMP_ASCII); + } + gpr_slice_buffer_destroy(&query_parts); + gpr_slice_unref(query_slice); + } + if (r->lb_policy_name == NULL) { + r->lb_policy_name = gpr_strdup(default_lb_policy_name); + } + + path_slice = + gpr_slice_new(args->uri->path, strlen(args->uri->path), do_nothing); + gpr_slice_buffer_init(&path_parts); + + gpr_slice_split(path_slice, ",", &path_parts); + r->num_addrs = path_parts.count; + r->addrs = gpr_malloc(sizeof(struct sockaddr_storage) * r->num_addrs); + r->addrs_len = gpr_malloc(sizeof(*r->addrs_len) * r->num_addrs); + + for (i = 0; i < r->num_addrs; i++) { + grpc_uri ith_uri = *args->uri; + char *part_str = gpr_dump_slice(path_parts.slices[i], GPR_DUMP_ASCII); + ith_uri.path = part_str; + if (!parse(&ith_uri, &r->addrs[i], &r->addrs_len[i])) { + errors_found = 1; /* GPR_TRUE */ + } + gpr_free(part_str); + if (errors_found) break; + } + + gpr_slice_buffer_destroy(&path_parts); + gpr_slice_unref(path_slice); + if (errors_found) { + gpr_free(r->lb_policy_name); + gpr_free(r->addrs); + gpr_free(r->addrs_len); + gpr_free(r); + return NULL; + } + + gpr_ref_init(&r->refs, 1); + gpr_mu_init(&r->mu); + grpc_resolver_init(&r->base, &sockaddr_resolver_vtable); + r->subchannel_factory = args->subchannel_factory; + grpc_subchannel_factory_ref(r->subchannel_factory); + + return &r->base; +} + +/* + * FACTORY + */ + +static void sockaddr_factory_ref(grpc_resolver_factory *factory) {} + +static void sockaddr_factory_unref(grpc_resolver_factory *factory) {} + +#define DECL_FACTORY(name, prefix) \ + static grpc_resolver *name##_factory_create_resolver( \ + grpc_resolver_factory *factory, grpc_resolver_args *args) { \ + return sockaddr_create(args, "pick_first", prefix##parse_##name); \ + } \ + static const grpc_resolver_factory_vtable name##_factory_vtable = { \ + sockaddr_factory_ref, sockaddr_factory_unref, \ + name##_factory_create_resolver, prefix##name##_get_default_authority, \ + #name}; \ + static grpc_resolver_factory name##_resolver_factory = { \ + &name##_factory_vtable}; \ + grpc_resolver_factory *grpc_##name##_resolver_factory_create() { \ + return &name##_resolver_factory; \ + } + +#ifdef GPR_HAVE_UNIX_SOCKET +DECL_FACTORY(unix, grpc_) +#endif +DECL_FACTORY(ipv4, ) DECL_FACTORY(ipv6, ) diff --git a/src/core/lib/client_config/resolvers/sockaddr_resolver.h b/src/core/lib/client_config/resolvers/sockaddr_resolver.h new file mode 100644 index 0000000000..45c55bd160 --- /dev/null +++ b/src/core/lib/client_config/resolvers/sockaddr_resolver.h @@ -0,0 +1,50 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CLIENT_CONFIG_RESOLVERS_SOCKADDR_RESOLVER_H +#define GRPC_CORE_LIB_CLIENT_CONFIG_RESOLVERS_SOCKADDR_RESOLVER_H + +#include <grpc/support/port_platform.h> + +#include "src/core/lib/client_config/resolver_factory.h" + +grpc_resolver_factory *grpc_ipv4_resolver_factory_create(void); + +grpc_resolver_factory *grpc_ipv6_resolver_factory_create(void); + +#ifdef GPR_POSIX_SOCKET +/** Create a unix resolver factory */ +grpc_resolver_factory *grpc_unix_resolver_factory_create(void); +#endif + +#endif /* GRPC_CORE_LIB_CLIENT_CONFIG_RESOLVERS_SOCKADDR_RESOLVER_H */ diff --git a/src/core/lib/client_config/resolvers/zookeeper_resolver.c b/src/core/lib/client_config/resolvers/zookeeper_resolver.c new file mode 100644 index 0000000000..3bb0bbdf5c --- /dev/null +++ b/src/core/lib/client_config/resolvers/zookeeper_resolver.c @@ -0,0 +1,520 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/client_config/resolvers/zookeeper_resolver.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/string_util.h> + +#include <grpc/grpc_zookeeper.h> +#include <zookeeper/zookeeper.h> + +#include "src/core/lib/client_config/lb_policy_registry.h" +#include "src/core/lib/client_config/resolver_registry.h" +#include "src/core/lib/iomgr/resolve_address.h" +#include "src/core/lib/json/json.h" +#include "src/core/lib/support/string.h" +#include "src/core/lib/surface/api_trace.h" + +/** Zookeeper session expiration time in milliseconds */ +#define GRPC_ZOOKEEPER_SESSION_TIMEOUT 15000 + +typedef struct { + /** base class: must be first */ + grpc_resolver base; + /** refcount */ + gpr_refcount refs; + /** name to resolve */ + char *name; + /** subchannel factory */ + grpc_subchannel_factory *subchannel_factory; + /** load balancing policy name */ + char *lb_policy_name; + + /** mutex guarding the rest of the state */ + gpr_mu mu; + /** are we currently resolving? */ + int resolving; + /** which version of resolved_config have we published? */ + int published_version; + /** which version of resolved_config is current? */ + int resolved_version; + /** pending next completion, or NULL */ + grpc_closure *next_completion; + /** target config address for next completion */ + grpc_client_config **target_config; + /** current (fully resolved) config */ + grpc_client_config *resolved_config; + + /** zookeeper handle */ + zhandle_t *zookeeper_handle; + /** zookeeper resolved addresses */ + grpc_resolved_addresses *resolved_addrs; + /** total number of addresses to be resolved */ + int resolved_total; + /** number of addresses resolved */ + int resolved_num; +} zookeeper_resolver; + +static void zookeeper_destroy(grpc_exec_ctx *exec_ctx, grpc_resolver *r); + +static void zookeeper_start_resolving_locked(zookeeper_resolver *r); +static void zookeeper_maybe_finish_next_locked(grpc_exec_ctx *exec_ctx, + zookeeper_resolver *r); + +static void zookeeper_shutdown(grpc_exec_ctx *exec_ctx, grpc_resolver *r); +static void zookeeper_channel_saw_error(grpc_exec_ctx *exec_ctx, + grpc_resolver *r); +static void zookeeper_next(grpc_exec_ctx *exec_ctx, grpc_resolver *r, + grpc_client_config **target_config, + grpc_closure *on_complete); + +static const grpc_resolver_vtable zookeeper_resolver_vtable = { + zookeeper_destroy, zookeeper_shutdown, zookeeper_channel_saw_error, + zookeeper_next}; + +static void zookeeper_shutdown(grpc_exec_ctx *exec_ctx, + grpc_resolver *resolver) { + zookeeper_resolver *r = (zookeeper_resolver *)resolver; + grpc_closure *call = NULL; + gpr_mu_lock(&r->mu); + if (r->next_completion != NULL) { + *r->target_config = NULL; + call = r->next_completion; + r->next_completion = NULL; + } + zookeeper_close(r->zookeeper_handle); + gpr_mu_unlock(&r->mu); + if (call != NULL) { + call->cb(exec_ctx, call->cb_arg, 1); + } +} + +static void zookeeper_channel_saw_error(grpc_exec_ctx *exec_ctx, + grpc_resolver *resolver) { + zookeeper_resolver *r = (zookeeper_resolver *)resolver; + gpr_mu_lock(&r->mu); + if (r->resolving == 0) { + zookeeper_start_resolving_locked(r); + } + gpr_mu_unlock(&r->mu); +} + +static void zookeeper_next(grpc_exec_ctx *exec_ctx, grpc_resolver *resolver, + grpc_client_config **target_config, + grpc_closure *on_complete) { + zookeeper_resolver *r = (zookeeper_resolver *)resolver; + gpr_mu_lock(&r->mu); + GPR_ASSERT(r->next_completion == NULL); + r->next_completion = on_complete; + r->target_config = target_config; + if (r->resolved_version == 0 && r->resolving == 0) { + zookeeper_start_resolving_locked(r); + } else { + zookeeper_maybe_finish_next_locked(exec_ctx, r); + } + gpr_mu_unlock(&r->mu); +} + +/** Zookeeper global watcher for connection management + TODO: better connection management besides logs */ +static void zookeeper_global_watcher(zhandle_t *zookeeper_handle, int type, + int state, const char *path, + void *watcher_ctx) { + if (type == ZOO_SESSION_EVENT) { + if (state == ZOO_EXPIRED_SESSION_STATE) { + gpr_log(GPR_ERROR, "Zookeeper session expired"); + } else if (state == ZOO_AUTH_FAILED_STATE) { + gpr_log(GPR_ERROR, "Zookeeper authentication failed"); + } + } +} + +/** Zookeeper watcher triggered by changes to watched nodes + Once triggered, it tries to resolve again to get updated addresses */ +static void zookeeper_watcher(zhandle_t *zookeeper_handle, int type, int state, + const char *path, void *watcher_ctx) { + if (watcher_ctx != NULL) { + zookeeper_resolver *r = (zookeeper_resolver *)watcher_ctx; + if (state == ZOO_CONNECTED_STATE) { + gpr_mu_lock(&r->mu); + if (r->resolving == 0) { + zookeeper_start_resolving_locked(r); + } + gpr_mu_unlock(&r->mu); + } + } +} + +/** Callback function after getting all resolved addresses + Creates a subchannel for each address */ +static void zookeeper_on_resolved(grpc_exec_ctx *exec_ctx, void *arg, + grpc_resolved_addresses *addresses) { + zookeeper_resolver *r = arg; + grpc_client_config *config = NULL; + grpc_subchannel **subchannels; + grpc_subchannel_args args; + grpc_lb_policy *lb_policy; + size_t i; + if (addresses != NULL) { + grpc_lb_policy_args lb_policy_args; + config = grpc_client_config_create(); + subchannels = gpr_malloc(sizeof(grpc_subchannel *) * addresses->naddrs); + for (i = 0; i < addresses->naddrs; i++) { + memset(&args, 0, sizeof(args)); + args.addr = (struct sockaddr *)(addresses->addrs[i].addr); + args.addr_len = addresses->addrs[i].len; + subchannels[i] = grpc_subchannel_factory_create_subchannel( + exec_ctx, r->subchannel_factory, &args); + } + lb_policy_args.subchannels = subchannels; + lb_policy_args.num_subchannels = addresses->naddrs; + lb_policy = grpc_lb_policy_create(r->lb_policy_name, &lb_policy_args); + grpc_client_config_set_lb_policy(config, lb_policy); + GRPC_LB_POLICY_UNREF(exec_ctx, lb_policy, "construction"); + grpc_resolved_addresses_destroy(addresses); + gpr_free(subchannels); + } + gpr_mu_lock(&r->mu); + GPR_ASSERT(r->resolving == 1); + r->resolving = 0; + if (r->resolved_config != NULL) { + grpc_client_config_unref(exec_ctx, r->resolved_config); + } + r->resolved_config = config; + r->resolved_version++; + zookeeper_maybe_finish_next_locked(exec_ctx, r); + gpr_mu_unlock(&r->mu); + + GRPC_RESOLVER_UNREF(exec_ctx, &r->base, "zookeeper-resolving"); +} + +/** Callback function for each DNS resolved address */ +static void zookeeper_dns_resolved(grpc_exec_ctx *exec_ctx, void *arg, + grpc_resolved_addresses *addresses) { + size_t i; + zookeeper_resolver *r = arg; + int resolve_done = 0; + + gpr_mu_lock(&r->mu); + r->resolved_num++; + r->resolved_addrs->addrs = + gpr_realloc(r->resolved_addrs->addrs, + sizeof(grpc_resolved_address) * + (r->resolved_addrs->naddrs + addresses->naddrs)); + for (i = 0; i < addresses->naddrs; i++) { + memcpy(r->resolved_addrs->addrs[i + r->resolved_addrs->naddrs].addr, + addresses->addrs[i].addr, addresses->addrs[i].len); + r->resolved_addrs->addrs[i + r->resolved_addrs->naddrs].len = + addresses->addrs[i].len; + } + + r->resolved_addrs->naddrs += addresses->naddrs; + grpc_resolved_addresses_destroy(addresses); + + /** Wait for all addresses to be resolved */ + resolve_done = (r->resolved_num == r->resolved_total); + gpr_mu_unlock(&r->mu); + if (resolve_done) { + zookeeper_on_resolved(exec_ctx, r, r->resolved_addrs); + } +} + +/** Parses JSON format address of a zookeeper node */ +static char *zookeeper_parse_address(const char *value, size_t value_len) { + grpc_json *json; + grpc_json *cur; + const char *host; + const char *port; + char *buffer; + char *address = NULL; + + buffer = gpr_malloc(value_len); + memcpy(buffer, value, value_len); + json = grpc_json_parse_string_with_len(buffer, value_len); + if (json != NULL) { + host = NULL; + port = NULL; + for (cur = json->child; cur != NULL; cur = cur->next) { + if (!strcmp(cur->key, "host")) { + host = cur->value; + if (port != NULL) { + break; + } + } else if (!strcmp(cur->key, "port")) { + port = cur->value; + if (host != NULL) { + break; + } + } + } + if (host != NULL && port != NULL) { + gpr_asprintf(&address, "%s:%s", host, port); + } + grpc_json_destroy(json); + } + gpr_free(buffer); + + return address; +} + +static void zookeeper_get_children_node_completion(int rc, const char *value, + int value_len, + const struct Stat *stat, + const void *arg) { + char *address = NULL; + zookeeper_resolver *r = (zookeeper_resolver *)arg; + int resolve_done = 0; + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + + if (rc != 0) { + gpr_log(GPR_ERROR, "Error in getting a child node of %s", r->name); + grpc_exec_ctx_finish(&exec_ctx); + return; + } + + address = zookeeper_parse_address(value, (size_t)value_len); + if (address != NULL) { + /** Further resolves address by DNS */ + grpc_resolve_address(address, NULL, zookeeper_dns_resolved, r); + gpr_free(address); + } else { + gpr_log(GPR_ERROR, "Error in resolving a child node of %s", r->name); + gpr_mu_lock(&r->mu); + r->resolved_total--; + resolve_done = (r->resolved_num == r->resolved_total); + gpr_mu_unlock(&r->mu); + if (resolve_done) { + zookeeper_on_resolved(&exec_ctx, r, r->resolved_addrs); + } + } + + grpc_exec_ctx_finish(&exec_ctx); +} + +static void zookeeper_get_children_completion( + int rc, const struct String_vector *children, const void *arg) { + char *path; + int status; + int i; + zookeeper_resolver *r = (zookeeper_resolver *)arg; + + if (rc != 0) { + gpr_log(GPR_ERROR, "Error in getting zookeeper children of %s", r->name); + return; + } + + if (children->count == 0) { + gpr_log(GPR_ERROR, "Error in resolving zookeeper address %s", r->name); + return; + } + + r->resolved_addrs = gpr_malloc(sizeof(grpc_resolved_addresses)); + r->resolved_addrs->addrs = NULL; + r->resolved_addrs->naddrs = 0; + r->resolved_total = children->count; + + /** TODO: Replace expensive heap allocation with stack + if we can get maximum length of zookeeper path */ + for (i = 0; i < children->count; i++) { + gpr_asprintf(&path, "%s/%s", r->name, children->data[i]); + status = zoo_awget(r->zookeeper_handle, path, zookeeper_watcher, r, + zookeeper_get_children_node_completion, r); + gpr_free(path); + if (status != 0) { + gpr_log(GPR_ERROR, "Error in getting zookeeper node %s", path); + } + } +} + +static void zookeeper_get_node_completion(int rc, const char *value, + int value_len, + const struct Stat *stat, + const void *arg) { + int status; + char *address = NULL; + zookeeper_resolver *r = (zookeeper_resolver *)arg; + r->resolved_addrs = NULL; + r->resolved_total = 0; + r->resolved_num = 0; + + if (rc != 0) { + gpr_log(GPR_ERROR, "Error in getting zookeeper node %s", r->name); + return; + } + + /** If zookeeper node of path r->name does not have address + (i.e. service node), get its children */ + address = zookeeper_parse_address(value, (size_t)value_len); + if (address != NULL) { + r->resolved_addrs = gpr_malloc(sizeof(grpc_resolved_addresses)); + r->resolved_addrs->addrs = NULL; + r->resolved_addrs->naddrs = 0; + r->resolved_total = 1; + /** Further resolves address by DNS */ + grpc_resolve_address(address, NULL, zookeeper_dns_resolved, r); + gpr_free(address); + return; + } + + status = zoo_awget_children(r->zookeeper_handle, r->name, zookeeper_watcher, + r, zookeeper_get_children_completion, r); + if (status != 0) { + gpr_log(GPR_ERROR, "Error in getting zookeeper children of %s", r->name); + } +} + +static void zookeeper_resolve_address(zookeeper_resolver *r) { + int status; + status = zoo_awget(r->zookeeper_handle, r->name, zookeeper_watcher, r, + zookeeper_get_node_completion, r); + if (status != 0) { + gpr_log(GPR_ERROR, "Error in getting zookeeper node %s", r->name); + } +} + +static void zookeeper_start_resolving_locked(zookeeper_resolver *r) { + GRPC_RESOLVER_REF(&r->base, "zookeeper-resolving"); + GPR_ASSERT(r->resolving == 0); + r->resolving = 1; + zookeeper_resolve_address(r); +} + +static void zookeeper_maybe_finish_next_locked(grpc_exec_ctx *exec_ctx, + zookeeper_resolver *r) { + if (r->next_completion != NULL && + r->resolved_version != r->published_version) { + *r->target_config = r->resolved_config; + if (r->resolved_config != NULL) { + grpc_client_config_ref(r->resolved_config); + } + grpc_exec_ctx_enqueue(exec_ctx, r->next_completion, true, NULL); + r->next_completion = NULL; + r->published_version = r->resolved_version; + } +} + +static void zookeeper_destroy(grpc_exec_ctx *exec_ctx, grpc_resolver *gr) { + zookeeper_resolver *r = (zookeeper_resolver *)gr; + gpr_mu_destroy(&r->mu); + if (r->resolved_config != NULL) { + grpc_client_config_unref(exec_ctx, r->resolved_config); + } + grpc_subchannel_factory_unref(exec_ctx, r->subchannel_factory); + gpr_free(r->name); + gpr_free(r->lb_policy_name); + gpr_free(r); +} + +static grpc_resolver *zookeeper_create(grpc_resolver_args *args, + const char *lb_policy_name) { + zookeeper_resolver *r; + size_t length; + char *path = args->uri->path; + + if (0 == strcmp(args->uri->authority, "")) { + gpr_log(GPR_ERROR, "No authority specified in zookeeper uri"); + return NULL; + } + + /** Removes the trailing slash if exists */ + length = strlen(path); + if (length > 1 && path[length - 1] == '/') { + path[length - 1] = 0; + } + + r = gpr_malloc(sizeof(zookeeper_resolver)); + memset(r, 0, sizeof(*r)); + gpr_ref_init(&r->refs, 1); + gpr_mu_init(&r->mu); + grpc_resolver_init(&r->base, &zookeeper_resolver_vtable); + r->name = gpr_strdup(path); + + r->subchannel_factory = args->subchannel_factory; + grpc_subchannel_factory_ref(r->subchannel_factory); + + r->lb_policy_name = gpr_strdup(lb_policy_name); + + /** Initializes zookeeper client */ + zoo_set_debug_level(ZOO_LOG_LEVEL_WARN); + r->zookeeper_handle = + zookeeper_init(args->uri->authority, zookeeper_global_watcher, + GRPC_ZOOKEEPER_SESSION_TIMEOUT, 0, 0, 0); + if (r->zookeeper_handle == NULL) { + gpr_log(GPR_ERROR, "Unable to connect to zookeeper server"); + return NULL; + } + + return &r->base; +} + +static void zookeeper_plugin_init() { + grpc_register_resolver_type(grpc_zookeeper_resolver_factory_create()); +} + +void grpc_zookeeper_register() { + GRPC_API_TRACE("grpc_zookeeper_register(void)", 0, ()); + grpc_register_plugin(zookeeper_plugin_init, NULL); +} + +/* + * FACTORY + */ + +static void zookeeper_factory_ref(grpc_resolver_factory *factory) {} + +static void zookeeper_factory_unref(grpc_resolver_factory *factory) {} + +static char *zookeeper_factory_get_default_hostname( + grpc_resolver_factory *factory, grpc_uri *uri) { + return NULL; +} + +static grpc_resolver *zookeeper_factory_create_resolver( + grpc_resolver_factory *factory, grpc_resolver_args *args) { + return zookeeper_create(args, "pick_first"); +} + +static const grpc_resolver_factory_vtable zookeeper_factory_vtable = { + zookeeper_factory_ref, zookeeper_factory_unref, + zookeeper_factory_create_resolver, zookeeper_factory_get_default_hostname, + "zookeeper"}; + +static grpc_resolver_factory zookeeper_resolver_factory = { + &zookeeper_factory_vtable}; + +grpc_resolver_factory *grpc_zookeeper_resolver_factory_create() { + return &zookeeper_resolver_factory; +} diff --git a/src/core/lib/client_config/resolvers/zookeeper_resolver.h b/src/core/lib/client_config/resolvers/zookeeper_resolver.h new file mode 100644 index 0000000000..7ee7604360 --- /dev/null +++ b/src/core/lib/client_config/resolvers/zookeeper_resolver.h @@ -0,0 +1,42 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CLIENT_CONFIG_RESOLVERS_ZOOKEEPER_RESOLVER_H +#define GRPC_CORE_LIB_CLIENT_CONFIG_RESOLVERS_ZOOKEEPER_RESOLVER_H + +#include "src/core/lib/client_config/resolver_factory.h" + +/** Create a zookeeper resolver factory */ +grpc_resolver_factory *grpc_zookeeper_resolver_factory_create(void); + +#endif /* GRPC_CORE_LIB_CLIENT_CONFIG_RESOLVERS_ZOOKEEPER_RESOLVER_H */ diff --git a/src/core/lib/client_config/subchannel.c b/src/core/lib/client_config/subchannel.c new file mode 100644 index 0000000000..41242f0dd7 --- /dev/null +++ b/src/core/lib/client_config/subchannel.c @@ -0,0 +1,678 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/client_config/subchannel.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/avl.h> + +#include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/channel/client_channel.h" +#include "src/core/lib/channel/connected_channel.h" +#include "src/core/lib/client_config/initial_connect_string.h" +#include "src/core/lib/client_config/subchannel_index.h" +#include "src/core/lib/iomgr/timer.h" +#include "src/core/lib/profiling/timers.h" +#include "src/core/lib/support/backoff.h" +#include "src/core/lib/surface/channel.h" +#include "src/core/lib/surface/channel_init.h" +#include "src/core/lib/transport/connectivity_state.h" + +#define INTERNAL_REF_BITS 16 +#define STRONG_REF_MASK (~(gpr_atm)((1 << INTERNAL_REF_BITS) - 1)) + +#define GRPC_SUBCHANNEL_MIN_CONNECT_TIMEOUT_SECONDS 20 +#define GRPC_SUBCHANNEL_INITIAL_CONNECT_BACKOFF_SECONDS 1 +#define GRPC_SUBCHANNEL_RECONNECT_BACKOFF_MULTIPLIER 1.6 +#define GRPC_SUBCHANNEL_RECONNECT_MAX_BACKOFF_SECONDS 120 +#define GRPC_SUBCHANNEL_RECONNECT_JITTER 0.2 + +#define GET_CONNECTED_SUBCHANNEL(subchannel, barrier) \ + ((grpc_connected_subchannel *)(gpr_atm_##barrier##_load( \ + &(subchannel)->connected_subchannel))) + +typedef struct { + grpc_closure closure; + grpc_subchannel *subchannel; + grpc_connectivity_state connectivity_state; +} state_watcher; + +typedef struct external_state_watcher { + grpc_subchannel *subchannel; + grpc_pollset_set *pollset_set; + grpc_closure *notify; + grpc_closure closure; + struct external_state_watcher *next; + struct external_state_watcher *prev; +} external_state_watcher; + +struct grpc_subchannel { + grpc_connector *connector; + + /** refcount + - lower INTERNAL_REF_BITS bits are for internal references: + these do not keep the subchannel open. + - upper remaining bits are for public references: these do + keep the subchannel open */ + gpr_atm ref_pair; + + /** non-transport related channel filters */ + const grpc_channel_filter **filters; + size_t num_filters; + /** channel arguments */ + grpc_channel_args *args; + /** address to connect to */ + struct sockaddr *addr; + size_t addr_len; + + grpc_subchannel_key *key; + + /** initial string to send to peer */ + gpr_slice initial_connect_string; + + /** set during connection */ + grpc_connect_out_args connecting_result; + + /** callback for connection finishing */ + grpc_closure connected; + + /** pollset_set tracking who's interested in a connection + being setup */ + grpc_pollset_set *pollset_set; + + /** active connection, or null; of type grpc_connected_subchannel */ + gpr_atm connected_subchannel; + + /** mutex protecting remaining elements */ + gpr_mu mu; + + /** have we seen a disconnection? */ + int disconnected; + /** are we connecting */ + int connecting; + /** connectivity state tracking */ + grpc_connectivity_state_tracker state_tracker; + + external_state_watcher root_external_state_watcher; + + /** next connect attempt time */ + gpr_timespec next_attempt; + /** backoff state */ + gpr_backoff backoff_state; + /** do we have an active alarm? */ + int have_alarm; + /** our alarm */ + grpc_timer alarm; + /** current random value */ + uint32_t random; +}; + +struct grpc_subchannel_call { + grpc_connected_subchannel *connection; +}; + +#define SUBCHANNEL_CALL_TO_CALL_STACK(call) ((grpc_call_stack *)((call) + 1)) +#define CHANNEL_STACK_FROM_CONNECTION(con) ((grpc_channel_stack *)(con)) +#define CALLSTACK_TO_SUBCHANNEL_CALL(callstack) \ + (((grpc_subchannel_call *)(callstack)) - 1) + +static void subchannel_connected(grpc_exec_ctx *exec_ctx, void *subchannel, + bool iomgr_success); + +#ifdef GRPC_STREAM_REFCOUNT_DEBUG +#define REF_REASON reason +#define REF_LOG(name, p) \ + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, "%s: %p ref %d -> %d %s", \ + (name), (p), (p)->refs.count, (p)->refs.count + 1, reason) +#define UNREF_LOG(name, p) \ + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, "%s: %p unref %d -> %d %s", \ + (name), (p), (p)->refs.count, (p)->refs.count - 1, reason) +#define REF_MUTATE_EXTRA_ARGS \ + GRPC_SUBCHANNEL_REF_EXTRA_ARGS, const char *purpose +#define REF_MUTATE_PURPOSE(x) , file, line, reason, x +#else +#define REF_REASON "" +#define REF_LOG(name, p) \ + do { \ + } while (0) +#define UNREF_LOG(name, p) \ + do { \ + } while (0) +#define REF_MUTATE_EXTRA_ARGS +#define REF_MUTATE_PURPOSE(x) +#endif + +/* + * connection implementation + */ + +static void connection_destroy(grpc_exec_ctx *exec_ctx, void *arg, + bool success) { + grpc_connected_subchannel *c = arg; + grpc_channel_stack_destroy(exec_ctx, CHANNEL_STACK_FROM_CONNECTION(c)); + gpr_free(c); +} + +void grpc_connected_subchannel_ref( + grpc_connected_subchannel *c GRPC_SUBCHANNEL_REF_EXTRA_ARGS) { + GRPC_CHANNEL_STACK_REF(CHANNEL_STACK_FROM_CONNECTION(c), REF_REASON); +} + +void grpc_connected_subchannel_unref(grpc_exec_ctx *exec_ctx, + grpc_connected_subchannel *c + GRPC_SUBCHANNEL_REF_EXTRA_ARGS) { + GRPC_CHANNEL_STACK_UNREF(exec_ctx, CHANNEL_STACK_FROM_CONNECTION(c), + REF_REASON); +} + +/* + * grpc_subchannel implementation + */ + +static void subchannel_destroy(grpc_exec_ctx *exec_ctx, void *arg, + bool success) { + grpc_subchannel *c = arg; + gpr_free((void *)c->filters); + grpc_channel_args_destroy(c->args); + gpr_free(c->addr); + gpr_slice_unref(c->initial_connect_string); + grpc_connectivity_state_destroy(exec_ctx, &c->state_tracker); + grpc_connector_unref(exec_ctx, c->connector); + grpc_pollset_set_destroy(c->pollset_set); + grpc_subchannel_key_destroy(exec_ctx, c->key); + gpr_free(c); +} + +static gpr_atm ref_mutate(grpc_subchannel *c, gpr_atm delta, + int barrier REF_MUTATE_EXTRA_ARGS) { + gpr_atm old_val = barrier ? gpr_atm_full_fetch_add(&c->ref_pair, delta) + : gpr_atm_no_barrier_fetch_add(&c->ref_pair, delta); +#ifdef GRPC_STREAM_REFCOUNT_DEBUG + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, + "SUBCHANNEL: %p % 12s 0x%08x -> 0x%08x [%s]", c, purpose, old_val, + old_val + delta, reason); +#endif + return old_val; +} + +grpc_subchannel *grpc_subchannel_ref( + grpc_subchannel *c GRPC_SUBCHANNEL_REF_EXTRA_ARGS) { + gpr_atm old_refs; + old_refs = ref_mutate(c, (1 << INTERNAL_REF_BITS), + 0 REF_MUTATE_PURPOSE("STRONG_REF")); + GPR_ASSERT((old_refs & STRONG_REF_MASK) != 0); + return c; +} + +grpc_subchannel *grpc_subchannel_weak_ref( + grpc_subchannel *c GRPC_SUBCHANNEL_REF_EXTRA_ARGS) { + gpr_atm old_refs; + old_refs = ref_mutate(c, 1, 0 REF_MUTATE_PURPOSE("WEAK_REF")); + GPR_ASSERT(old_refs != 0); + return c; +} + +grpc_subchannel *grpc_subchannel_ref_from_weak_ref( + grpc_subchannel *c GRPC_SUBCHANNEL_REF_EXTRA_ARGS) { + if (!c) return NULL; + for (;;) { + gpr_atm old_refs = gpr_atm_acq_load(&c->ref_pair); + if (old_refs >= (1 << INTERNAL_REF_BITS)) { + gpr_atm new_refs = old_refs + (1 << INTERNAL_REF_BITS); + if (gpr_atm_rel_cas(&c->ref_pair, old_refs, new_refs)) { + return c; + } + } else { + return NULL; + } + } +} + +static void disconnect(grpc_exec_ctx *exec_ctx, grpc_subchannel *c) { + grpc_connected_subchannel *con; + grpc_subchannel_index_unregister(exec_ctx, c->key, c); + gpr_mu_lock(&c->mu); + GPR_ASSERT(!c->disconnected); + c->disconnected = 1; + grpc_connector_shutdown(exec_ctx, c->connector); + con = GET_CONNECTED_SUBCHANNEL(c, no_barrier); + if (con != NULL) { + GRPC_CONNECTED_SUBCHANNEL_UNREF(exec_ctx, con, "connection"); + gpr_atm_no_barrier_store(&c->connected_subchannel, 0xdeadbeef); + } + gpr_mu_unlock(&c->mu); +} + +void grpc_subchannel_unref(grpc_exec_ctx *exec_ctx, + grpc_subchannel *c GRPC_SUBCHANNEL_REF_EXTRA_ARGS) { + gpr_atm old_refs; + old_refs = ref_mutate(c, (gpr_atm)1 - (gpr_atm)(1 << INTERNAL_REF_BITS), + 1 REF_MUTATE_PURPOSE("STRONG_UNREF")); + if ((old_refs & STRONG_REF_MASK) == (1 << INTERNAL_REF_BITS)) { + disconnect(exec_ctx, c); + } + GRPC_SUBCHANNEL_WEAK_UNREF(exec_ctx, c, "strong-unref"); +} + +void grpc_subchannel_weak_unref(grpc_exec_ctx *exec_ctx, + grpc_subchannel *c + GRPC_SUBCHANNEL_REF_EXTRA_ARGS) { + gpr_atm old_refs; + old_refs = ref_mutate(c, -(gpr_atm)1, 1 REF_MUTATE_PURPOSE("WEAK_UNREF")); + if (old_refs == 1) { + grpc_exec_ctx_enqueue(exec_ctx, grpc_closure_create(subchannel_destroy, c), + true, NULL); + } +} + +static uint32_t random_seed() { + return (uint32_t)(gpr_time_to_millis(gpr_now(GPR_CLOCK_MONOTONIC))); +} + +grpc_subchannel *grpc_subchannel_create(grpc_exec_ctx *exec_ctx, + grpc_connector *connector, + grpc_subchannel_args *args) { + grpc_subchannel_key *key = grpc_subchannel_key_create(connector, args); + grpc_subchannel *c = grpc_subchannel_index_find(exec_ctx, key); + if (c) { + grpc_subchannel_key_destroy(exec_ctx, key); + return c; + } + + c = gpr_malloc(sizeof(*c)); + memset(c, 0, sizeof(*c)); + c->key = key; + gpr_atm_no_barrier_store(&c->ref_pair, 1 << INTERNAL_REF_BITS); + c->connector = connector; + grpc_connector_ref(c->connector); + c->num_filters = args->filter_count; + if (c->num_filters > 0) { + c->filters = gpr_malloc(sizeof(grpc_channel_filter *) * c->num_filters); + memcpy((void *)c->filters, args->filters, + sizeof(grpc_channel_filter *) * c->num_filters); + } else { + c->filters = NULL; + } + c->addr = gpr_malloc(args->addr_len); + memcpy(c->addr, args->addr, args->addr_len); + c->pollset_set = grpc_pollset_set_create(); + c->addr_len = args->addr_len; + grpc_set_initial_connect_string(&c->addr, &c->addr_len, + &c->initial_connect_string); + c->args = grpc_channel_args_copy(args->args); + c->random = random_seed(); + c->root_external_state_watcher.next = c->root_external_state_watcher.prev = + &c->root_external_state_watcher; + grpc_closure_init(&c->connected, subchannel_connected, c); + grpc_connectivity_state_init(&c->state_tracker, GRPC_CHANNEL_IDLE, + "subchannel"); + gpr_backoff_init(&c->backoff_state, + GRPC_SUBCHANNEL_RECONNECT_BACKOFF_MULTIPLIER, + GRPC_SUBCHANNEL_RECONNECT_JITTER, + GRPC_SUBCHANNEL_INITIAL_CONNECT_BACKOFF_SECONDS * 1000, + GRPC_SUBCHANNEL_RECONNECT_MAX_BACKOFF_SECONDS * 1000); + if (c->args) { + for (size_t i = 0; i < c->args->num_args; i++) { + if (0 == strcmp(c->args->args[i].key, + "grpc.testing.fixed_reconnect_backoff")) { + GPR_ASSERT(c->args->args[i].type == GRPC_ARG_INTEGER); + gpr_backoff_init(&c->backoff_state, 1.0, 0.0, + c->args->args[i].value.integer, + c->args->args[i].value.integer); + } + } + } + gpr_mu_init(&c->mu); + + return grpc_subchannel_index_register(exec_ctx, key, c); +} + +static void continue_connect(grpc_exec_ctx *exec_ctx, grpc_subchannel *c) { + grpc_connect_in_args args; + + args.interested_parties = c->pollset_set; + args.addr = c->addr; + args.addr_len = c->addr_len; + args.deadline = c->next_attempt; + args.channel_args = c->args; + args.initial_connect_string = c->initial_connect_string; + + grpc_connectivity_state_set(exec_ctx, &c->state_tracker, + GRPC_CHANNEL_CONNECTING, "state_change"); + grpc_connector_connect(exec_ctx, c->connector, &args, &c->connecting_result, + &c->connected); +} + +static void start_connect(grpc_exec_ctx *exec_ctx, grpc_subchannel *c) { + c->next_attempt = + gpr_backoff_begin(&c->backoff_state, gpr_now(GPR_CLOCK_MONOTONIC)); + continue_connect(exec_ctx, c); +} + +grpc_connectivity_state grpc_subchannel_check_connectivity(grpc_subchannel *c) { + grpc_connectivity_state state; + gpr_mu_lock(&c->mu); + state = grpc_connectivity_state_check(&c->state_tracker); + gpr_mu_unlock(&c->mu); + return state; +} + +static void on_external_state_watcher_done(grpc_exec_ctx *exec_ctx, void *arg, + bool success) { + external_state_watcher *w = arg; + grpc_closure *follow_up = w->notify; + if (w->pollset_set != NULL) { + grpc_pollset_set_del_pollset_set(exec_ctx, w->subchannel->pollset_set, + w->pollset_set); + } + gpr_mu_lock(&w->subchannel->mu); + w->next->prev = w->prev; + w->prev->next = w->next; + gpr_mu_unlock(&w->subchannel->mu); + GRPC_SUBCHANNEL_WEAK_UNREF(exec_ctx, w->subchannel, "external_state_watcher"); + gpr_free(w); + follow_up->cb(exec_ctx, follow_up->cb_arg, success); +} + +void grpc_subchannel_notify_on_state_change( + grpc_exec_ctx *exec_ctx, grpc_subchannel *c, + grpc_pollset_set *interested_parties, grpc_connectivity_state *state, + grpc_closure *notify) { + external_state_watcher *w; + + if (state == NULL) { + gpr_mu_lock(&c->mu); + for (w = c->root_external_state_watcher.next; + w != &c->root_external_state_watcher; w = w->next) { + if (w->notify == notify) { + grpc_connectivity_state_notify_on_state_change( + exec_ctx, &c->state_tracker, NULL, &w->closure); + } + } + gpr_mu_unlock(&c->mu); + } else { + w = gpr_malloc(sizeof(*w)); + w->subchannel = c; + w->pollset_set = interested_parties; + w->notify = notify; + grpc_closure_init(&w->closure, on_external_state_watcher_done, w); + if (interested_parties != NULL) { + grpc_pollset_set_add_pollset_set(exec_ctx, c->pollset_set, + interested_parties); + } + GRPC_SUBCHANNEL_WEAK_REF(c, "external_state_watcher"); + gpr_mu_lock(&c->mu); + w->next = &c->root_external_state_watcher; + w->prev = w->next->prev; + w->next->prev = w->prev->next = w; + if (grpc_connectivity_state_notify_on_state_change( + exec_ctx, &c->state_tracker, state, &w->closure)) { + c->connecting = 1; + /* released by connection */ + GRPC_SUBCHANNEL_WEAK_REF(c, "connecting"); + start_connect(exec_ctx, c); + } + gpr_mu_unlock(&c->mu); + } +} + +void grpc_connected_subchannel_process_transport_op( + grpc_exec_ctx *exec_ctx, grpc_connected_subchannel *con, + grpc_transport_op *op) { + grpc_channel_stack *channel_stack = CHANNEL_STACK_FROM_CONNECTION(con); + grpc_channel_element *top_elem = grpc_channel_stack_element(channel_stack, 0); + top_elem->filter->start_transport_op(exec_ctx, top_elem, op); +} + +static void subchannel_on_child_state_changed(grpc_exec_ctx *exec_ctx, void *p, + bool iomgr_success) { + state_watcher *sw = p; + grpc_subchannel *c = sw->subchannel; + gpr_mu *mu = &c->mu; + + gpr_mu_lock(mu); + + /* if we failed just leave this closure */ + if (iomgr_success) { + if (sw->connectivity_state == GRPC_CHANNEL_TRANSIENT_FAILURE) { + /* any errors on a subchannel ==> we're done, create a new one */ + sw->connectivity_state = GRPC_CHANNEL_FATAL_FAILURE; + } + grpc_connectivity_state_set(exec_ctx, &c->state_tracker, + sw->connectivity_state, "reflect_child"); + if (sw->connectivity_state != GRPC_CHANNEL_FATAL_FAILURE) { + grpc_connected_subchannel_notify_on_state_change( + exec_ctx, GET_CONNECTED_SUBCHANNEL(c, no_barrier), NULL, + &sw->connectivity_state, &sw->closure); + GRPC_SUBCHANNEL_WEAK_REF(c, "state_watcher"); + sw = NULL; + } + } + + gpr_mu_unlock(mu); + GRPC_SUBCHANNEL_WEAK_UNREF(exec_ctx, c, "state_watcher"); + gpr_free(sw); +} + +static void connected_subchannel_state_op(grpc_exec_ctx *exec_ctx, + grpc_connected_subchannel *con, + grpc_pollset_set *interested_parties, + grpc_connectivity_state *state, + grpc_closure *closure) { + grpc_transport_op op; + grpc_channel_element *elem; + memset(&op, 0, sizeof(op)); + op.connectivity_state = state; + op.on_connectivity_state_change = closure; + op.bind_pollset_set = interested_parties; + elem = grpc_channel_stack_element(CHANNEL_STACK_FROM_CONNECTION(con), 0); + elem->filter->start_transport_op(exec_ctx, elem, &op); +} + +void grpc_connected_subchannel_notify_on_state_change( + grpc_exec_ctx *exec_ctx, grpc_connected_subchannel *con, + grpc_pollset_set *interested_parties, grpc_connectivity_state *state, + grpc_closure *closure) { + connected_subchannel_state_op(exec_ctx, con, interested_parties, state, + closure); +} + +void grpc_connected_subchannel_ping(grpc_exec_ctx *exec_ctx, + grpc_connected_subchannel *con, + grpc_closure *closure) { + grpc_transport_op op; + grpc_channel_element *elem; + memset(&op, 0, sizeof(op)); + op.send_ping = closure; + elem = grpc_channel_stack_element(CHANNEL_STACK_FROM_CONNECTION(con), 0); + elem->filter->start_transport_op(exec_ctx, elem, &op); +} + +static void publish_transport_locked(grpc_exec_ctx *exec_ctx, + grpc_subchannel *c) { + grpc_connected_subchannel *con; + grpc_channel_stack *stk; + state_watcher *sw_subchannel; + + /* construct channel stack */ + con = grpc_channel_init_create_stack( + exec_ctx, GRPC_CLIENT_SUBCHANNEL, 0, c->connecting_result.channel_args, 1, + connection_destroy, NULL, c->connecting_result.transport); + stk = CHANNEL_STACK_FROM_CONNECTION(con); + memset(&c->connecting_result, 0, sizeof(c->connecting_result)); + + /* initialize state watcher */ + sw_subchannel = gpr_malloc(sizeof(*sw_subchannel)); + sw_subchannel->subchannel = c; + sw_subchannel->connectivity_state = GRPC_CHANNEL_READY; + grpc_closure_init(&sw_subchannel->closure, subchannel_on_child_state_changed, + sw_subchannel); + + if (c->disconnected) { + gpr_free(sw_subchannel); + grpc_channel_stack_destroy(exec_ctx, stk); + gpr_free(con); + GRPC_SUBCHANNEL_WEAK_UNREF(exec_ctx, c, "connecting"); + return; + } + + /* publish */ + /* TODO(ctiller): this full barrier seems to clear up a TSAN failure. + I'd have expected the rel_cas below to be enough, but + seemingly it's not. + Re-evaluate if we really need this. */ + gpr_atm_full_barrier(); + GPR_ASSERT(gpr_atm_rel_cas(&c->connected_subchannel, 0, (gpr_atm)con)); + c->connecting = 0; + + /* setup subchannel watching connected subchannel for changes; subchannel ref + for connecting is donated + to the state watcher */ + GRPC_SUBCHANNEL_WEAK_REF(c, "state_watcher"); + GRPC_SUBCHANNEL_WEAK_UNREF(exec_ctx, c, "connecting"); + grpc_connected_subchannel_notify_on_state_change( + exec_ctx, con, c->pollset_set, &sw_subchannel->connectivity_state, + &sw_subchannel->closure); + + /* signal completion */ + grpc_connectivity_state_set(exec_ctx, &c->state_tracker, GRPC_CHANNEL_READY, + "connected"); +} + +static void on_alarm(grpc_exec_ctx *exec_ctx, void *arg, bool iomgr_success) { + grpc_subchannel *c = arg; + gpr_mu_lock(&c->mu); + c->have_alarm = 0; + if (c->disconnected) { + iomgr_success = 0; + } + if (iomgr_success) { + c->next_attempt = + gpr_backoff_step(&c->backoff_state, gpr_now(GPR_CLOCK_MONOTONIC)); + continue_connect(exec_ctx, c); + gpr_mu_unlock(&c->mu); + } else { + gpr_mu_unlock(&c->mu); + GRPC_SUBCHANNEL_WEAK_UNREF(exec_ctx, c, "connecting"); + } +} + +static void subchannel_connected(grpc_exec_ctx *exec_ctx, void *arg, + bool iomgr_success) { + grpc_subchannel *c = arg; + + GRPC_SUBCHANNEL_WEAK_REF(c, "connected"); + gpr_mu_lock(&c->mu); + if (c->connecting_result.transport != NULL) { + publish_transport_locked(exec_ctx, c); + } else if (c->disconnected) { + GRPC_SUBCHANNEL_WEAK_UNREF(exec_ctx, c, "connecting"); + } else { + gpr_timespec now = gpr_now(GPR_CLOCK_MONOTONIC); + GPR_ASSERT(!c->have_alarm); + c->have_alarm = 1; + grpc_connectivity_state_set(exec_ctx, &c->state_tracker, + GRPC_CHANNEL_TRANSIENT_FAILURE, + "connect_failed"); + grpc_timer_init(exec_ctx, &c->alarm, c->next_attempt, on_alarm, c, now); + } + gpr_mu_unlock(&c->mu); + GRPC_SUBCHANNEL_WEAK_UNREF(exec_ctx, c, "connecting"); +} + +/* + * grpc_subchannel_call implementation + */ + +static void subchannel_call_destroy(grpc_exec_ctx *exec_ctx, void *call, + bool success) { + grpc_subchannel_call *c = call; + GPR_TIMER_BEGIN("grpc_subchannel_call_unref.destroy", 0); + grpc_call_stack_destroy(exec_ctx, SUBCHANNEL_CALL_TO_CALL_STACK(c)); + GRPC_CONNECTED_SUBCHANNEL_UNREF(exec_ctx, c->connection, "subchannel_call"); + gpr_free(c); + GPR_TIMER_END("grpc_subchannel_call_unref.destroy", 0); +} + +void grpc_subchannel_call_ref( + grpc_subchannel_call *c GRPC_SUBCHANNEL_REF_EXTRA_ARGS) { + GRPC_CALL_STACK_REF(SUBCHANNEL_CALL_TO_CALL_STACK(c), REF_REASON); +} + +void grpc_subchannel_call_unref(grpc_exec_ctx *exec_ctx, + grpc_subchannel_call *c + GRPC_SUBCHANNEL_REF_EXTRA_ARGS) { + GRPC_CALL_STACK_UNREF(exec_ctx, SUBCHANNEL_CALL_TO_CALL_STACK(c), REF_REASON); +} + +char *grpc_subchannel_call_get_peer(grpc_exec_ctx *exec_ctx, + grpc_subchannel_call *call) { + grpc_call_stack *call_stack = SUBCHANNEL_CALL_TO_CALL_STACK(call); + grpc_call_element *top_elem = grpc_call_stack_element(call_stack, 0); + return top_elem->filter->get_peer(exec_ctx, top_elem); +} + +void grpc_subchannel_call_process_op(grpc_exec_ctx *exec_ctx, + grpc_subchannel_call *call, + grpc_transport_stream_op *op) { + grpc_call_stack *call_stack = SUBCHANNEL_CALL_TO_CALL_STACK(call); + grpc_call_element *top_elem = grpc_call_stack_element(call_stack, 0); + top_elem->filter->start_transport_stream_op(exec_ctx, top_elem, op); +} + +grpc_connected_subchannel *grpc_subchannel_get_connected_subchannel( + grpc_subchannel *c) { + return GET_CONNECTED_SUBCHANNEL(c, acq); +} + +grpc_subchannel_call *grpc_connected_subchannel_create_call( + grpc_exec_ctx *exec_ctx, grpc_connected_subchannel *con, + grpc_pollset *pollset) { + grpc_channel_stack *chanstk = CHANNEL_STACK_FROM_CONNECTION(con); + grpc_subchannel_call *call = + gpr_malloc(sizeof(grpc_subchannel_call) + chanstk->call_stack_size); + grpc_call_stack *callstk = SUBCHANNEL_CALL_TO_CALL_STACK(call); + call->connection = con; + GRPC_CONNECTED_SUBCHANNEL_REF(con, "subchannel_call"); + grpc_call_stack_init(exec_ctx, chanstk, 1, subchannel_call_destroy, call, + NULL, NULL, callstk); + grpc_call_stack_set_pollset(exec_ctx, callstk, pollset); + return call; +} + +grpc_call_stack *grpc_subchannel_call_get_call_stack( + grpc_subchannel_call *subchannel_call) { + return SUBCHANNEL_CALL_TO_CALL_STACK(subchannel_call); +} diff --git a/src/core/lib/client_config/subchannel.h b/src/core/lib/client_config/subchannel.h new file mode 100644 index 0000000000..b4f545be52 --- /dev/null +++ b/src/core/lib/client_config/subchannel.h @@ -0,0 +1,174 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CLIENT_CONFIG_SUBCHANNEL_H +#define GRPC_CORE_LIB_CLIENT_CONFIG_SUBCHANNEL_H + +#include "src/core/lib/channel/channel_stack.h" +#include "src/core/lib/client_config/connector.h" +#include "src/core/lib/transport/connectivity_state.h" + +/** A (sub-)channel that knows how to connect to exactly one target + address. Provides a target for load balancing. */ +typedef struct grpc_subchannel grpc_subchannel; +typedef struct grpc_connected_subchannel grpc_connected_subchannel; +typedef struct grpc_subchannel_call grpc_subchannel_call; +typedef struct grpc_subchannel_args grpc_subchannel_args; + +#ifdef GRPC_STREAM_REFCOUNT_DEBUG +#define GRPC_SUBCHANNEL_REF(p, r) \ + grpc_subchannel_ref((p), __FILE__, __LINE__, (r)) +#define GRPC_SUBCHANNEL_REF_FROM_WEAK_REF(p, r) \ + grpc_subchannel_ref_from_weak_ref((p), __FILE__, __LINE__, (r)) +#define GRPC_SUBCHANNEL_UNREF(cl, p, r) \ + grpc_subchannel_unref((cl), (p), __FILE__, __LINE__, (r)) +#define GRPC_SUBCHANNEL_WEAK_REF(p, r) \ + grpc_subchannel_weak_ref((p), __FILE__, __LINE__, (r)) +#define GRPC_SUBCHANNEL_WEAK_UNREF(cl, p, r) \ + grpc_subchannel_weak_unref((cl), (p), __FILE__, __LINE__, (r)) +#define GRPC_CONNECTED_SUBCHANNEL_REF(p, r) \ + grpc_connected_subchannel_ref((p), __FILE__, __LINE__, (r)) +#define GRPC_CONNECTED_SUBCHANNEL_UNREF(cl, p, r) \ + grpc_connected_subchannel_unref((cl), (p), __FILE__, __LINE__, (r)) +#define GRPC_SUBCHANNEL_CALL_REF(p, r) \ + grpc_subchannel_call_ref((p), __FILE__, __LINE__, (r)) +#define GRPC_SUBCHANNEL_CALL_UNREF(cl, p, r) \ + grpc_subchannel_call_unref((cl), (p), __FILE__, __LINE__, (r)) +#define GRPC_SUBCHANNEL_REF_EXTRA_ARGS \ + , const char *file, int line, const char *reason +#else +#define GRPC_SUBCHANNEL_REF(p, r) grpc_subchannel_ref((p)) +#define GRPC_SUBCHANNEL_REF_FROM_WEAK_REF(p, r) \ + grpc_subchannel_ref_from_weak_ref((p)) +#define GRPC_SUBCHANNEL_UNREF(cl, p, r) grpc_subchannel_unref((cl), (p)) +#define GRPC_SUBCHANNEL_WEAK_REF(p, r) grpc_subchannel_weak_ref((p)) +#define GRPC_SUBCHANNEL_WEAK_UNREF(cl, p, r) \ + grpc_subchannel_weak_unref((cl), (p)) +#define GRPC_CONNECTED_SUBCHANNEL_REF(p, r) grpc_connected_subchannel_ref((p)) +#define GRPC_CONNECTED_SUBCHANNEL_UNREF(cl, p, r) \ + grpc_connected_subchannel_unref((cl), (p)) +#define GRPC_SUBCHANNEL_CALL_REF(p, r) grpc_subchannel_call_ref((p)) +#define GRPC_SUBCHANNEL_CALL_UNREF(cl, p, r) \ + grpc_subchannel_call_unref((cl), (p)) +#define GRPC_SUBCHANNEL_REF_EXTRA_ARGS +#endif + +grpc_subchannel *grpc_subchannel_ref( + grpc_subchannel *channel GRPC_SUBCHANNEL_REF_EXTRA_ARGS); +grpc_subchannel *grpc_subchannel_ref_from_weak_ref( + grpc_subchannel *channel GRPC_SUBCHANNEL_REF_EXTRA_ARGS); +void grpc_subchannel_unref(grpc_exec_ctx *exec_ctx, + grpc_subchannel *channel + GRPC_SUBCHANNEL_REF_EXTRA_ARGS); +grpc_subchannel *grpc_subchannel_weak_ref( + grpc_subchannel *channel GRPC_SUBCHANNEL_REF_EXTRA_ARGS); +void grpc_subchannel_weak_unref(grpc_exec_ctx *exec_ctx, + grpc_subchannel *channel + GRPC_SUBCHANNEL_REF_EXTRA_ARGS); +void grpc_connected_subchannel_ref( + grpc_connected_subchannel *channel GRPC_SUBCHANNEL_REF_EXTRA_ARGS); +void grpc_connected_subchannel_unref(grpc_exec_ctx *exec_ctx, + grpc_connected_subchannel *channel + GRPC_SUBCHANNEL_REF_EXTRA_ARGS); +void grpc_subchannel_call_ref( + grpc_subchannel_call *call GRPC_SUBCHANNEL_REF_EXTRA_ARGS); +void grpc_subchannel_call_unref(grpc_exec_ctx *exec_ctx, + grpc_subchannel_call *call + GRPC_SUBCHANNEL_REF_EXTRA_ARGS); + +/** construct a subchannel call */ +grpc_subchannel_call *grpc_connected_subchannel_create_call( + grpc_exec_ctx *exec_ctx, grpc_connected_subchannel *connected_subchannel, + grpc_pollset *pollset); + +/** process a transport level op */ +void grpc_connected_subchannel_process_transport_op( + grpc_exec_ctx *exec_ctx, grpc_connected_subchannel *subchannel, + grpc_transport_op *op); + +/** poll the current connectivity state of a channel */ +grpc_connectivity_state grpc_subchannel_check_connectivity( + grpc_subchannel *channel); + +/** call notify when the connectivity state of a channel changes from *state. + Updates *state with the new state of the channel */ +void grpc_subchannel_notify_on_state_change( + grpc_exec_ctx *exec_ctx, grpc_subchannel *channel, + grpc_pollset_set *interested_parties, grpc_connectivity_state *state, + grpc_closure *notify); +void grpc_connected_subchannel_notify_on_state_change( + grpc_exec_ctx *exec_ctx, grpc_connected_subchannel *channel, + grpc_pollset_set *interested_parties, grpc_connectivity_state *state, + grpc_closure *notify); +void grpc_connected_subchannel_ping(grpc_exec_ctx *exec_ctx, + grpc_connected_subchannel *channel, + grpc_closure *notify); + +/** retrieve the grpc_connected_subchannel - or NULL if called before + the subchannel becomes connected */ +grpc_connected_subchannel *grpc_subchannel_get_connected_subchannel( + grpc_subchannel *subchannel); + +/** continue processing a transport op */ +void grpc_subchannel_call_process_op(grpc_exec_ctx *exec_ctx, + grpc_subchannel_call *subchannel_call, + grpc_transport_stream_op *op); + +/** continue querying for peer */ +char *grpc_subchannel_call_get_peer(grpc_exec_ctx *exec_ctx, + grpc_subchannel_call *subchannel_call); + +grpc_call_stack *grpc_subchannel_call_get_call_stack( + grpc_subchannel_call *subchannel_call); + +struct grpc_subchannel_args { + /* When updating this struct, also update subchannel_index.c */ + + /** Channel filters for this channel - wrapped factories will likely + want to mutate this */ + const grpc_channel_filter **filters; + /** The number of filters in the above array */ + size_t filter_count; + /** Channel arguments to be supplied to the newly created channel */ + const grpc_channel_args *args; + /** Address to connect to */ + struct sockaddr *addr; + size_t addr_len; +}; + +/** create a subchannel given a connector */ +grpc_subchannel *grpc_subchannel_create(grpc_exec_ctx *exec_ctx, + grpc_connector *connector, + grpc_subchannel_args *args); + +#endif /* GRPC_CORE_LIB_CLIENT_CONFIG_SUBCHANNEL_H */ diff --git a/src/core/lib/client_config/subchannel_factory.c b/src/core/lib/client_config/subchannel_factory.c new file mode 100644 index 0000000000..727a48a6c8 --- /dev/null +++ b/src/core/lib/client_config/subchannel_factory.c @@ -0,0 +1,49 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/client_config/subchannel_factory.h" + +void grpc_subchannel_factory_ref(grpc_subchannel_factory* factory) { + factory->vtable->ref(factory); +} + +void grpc_subchannel_factory_unref(grpc_exec_ctx* exec_ctx, + grpc_subchannel_factory* factory) { + factory->vtable->unref(exec_ctx, factory); +} + +grpc_subchannel* grpc_subchannel_factory_create_subchannel( + grpc_exec_ctx* exec_ctx, grpc_subchannel_factory* factory, + grpc_subchannel_args* args) { + return factory->vtable->create_subchannel(exec_ctx, factory, args); +} diff --git a/src/core/lib/client_config/subchannel_factory.h b/src/core/lib/client_config/subchannel_factory.h new file mode 100644 index 0000000000..3ba2f860fe --- /dev/null +++ b/src/core/lib/client_config/subchannel_factory.h @@ -0,0 +1,66 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CLIENT_CONFIG_SUBCHANNEL_FACTORY_H +#define GRPC_CORE_LIB_CLIENT_CONFIG_SUBCHANNEL_FACTORY_H + +#include "src/core/lib/channel/channel_stack.h" +#include "src/core/lib/client_config/subchannel.h" + +typedef struct grpc_subchannel_factory grpc_subchannel_factory; +typedef struct grpc_subchannel_factory_vtable grpc_subchannel_factory_vtable; + +/** Constructor for new configured channels. + Creating decorators around this type is encouraged to adapt behavior. */ +struct grpc_subchannel_factory { + const grpc_subchannel_factory_vtable *vtable; +}; + +struct grpc_subchannel_factory_vtable { + void (*ref)(grpc_subchannel_factory *factory); + void (*unref)(grpc_exec_ctx *exec_ctx, grpc_subchannel_factory *factory); + grpc_subchannel *(*create_subchannel)(grpc_exec_ctx *exec_ctx, + grpc_subchannel_factory *factory, + grpc_subchannel_args *args); +}; + +void grpc_subchannel_factory_ref(grpc_subchannel_factory *factory); +void grpc_subchannel_factory_unref(grpc_exec_ctx *exec_ctx, + grpc_subchannel_factory *factory); + +/** Create a new grpc_subchannel */ +grpc_subchannel *grpc_subchannel_factory_create_subchannel( + grpc_exec_ctx *exec_ctx, grpc_subchannel_factory *factory, + grpc_subchannel_args *args); + +#endif /* GRPC_CORE_LIB_CLIENT_CONFIG_SUBCHANNEL_FACTORY_H */ diff --git a/src/core/lib/client_config/subchannel_index.c b/src/core/lib/client_config/subchannel_index.c new file mode 100644 index 0000000000..2c545002a2 --- /dev/null +++ b/src/core/lib/client_config/subchannel_index.c @@ -0,0 +1,262 @@ +// +// +// Copyright 2016, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// + +#include "src/core/lib/client_config/subchannel_index.h" + +#include <stdbool.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/avl.h> +#include <grpc/support/tls.h> + +#include "src/core/lib/channel/channel_args.h" + +// a map of subchannel_key --> subchannel, used for detecting connections +// to the same destination in order to share them +static gpr_avl g_subchannel_index; + +static gpr_mu g_mu; + +struct grpc_subchannel_key { + grpc_connector *connector; + grpc_subchannel_args args; +}; + +GPR_TLS_DECL(subchannel_index_exec_ctx); + +static void enter_ctx(grpc_exec_ctx *exec_ctx) { + GPR_ASSERT(gpr_tls_get(&subchannel_index_exec_ctx) == 0); + gpr_tls_set(&subchannel_index_exec_ctx, (intptr_t)exec_ctx); +} + +static void leave_ctx(grpc_exec_ctx *exec_ctx) { + GPR_ASSERT(gpr_tls_get(&subchannel_index_exec_ctx) == (intptr_t)exec_ctx); + gpr_tls_set(&subchannel_index_exec_ctx, 0); +} + +static grpc_exec_ctx *current_ctx() { + grpc_exec_ctx *c = (grpc_exec_ctx *)gpr_tls_get(&subchannel_index_exec_ctx); + GPR_ASSERT(c != NULL); + return c; +} + +static grpc_subchannel_key *create_key( + grpc_connector *connector, grpc_subchannel_args *args, + grpc_channel_args *(*copy_channel_args)(const grpc_channel_args *args)) { + grpc_subchannel_key *k = gpr_malloc(sizeof(*k)); + k->connector = grpc_connector_ref(connector); + k->args.filter_count = args->filter_count; + k->args.filters = gpr_malloc(sizeof(*k->args.filters) * k->args.filter_count); + memcpy((grpc_channel_filter *)k->args.filters, args->filters, + sizeof(*k->args.filters) * k->args.filter_count); + k->args.addr_len = args->addr_len; + k->args.addr = gpr_malloc(args->addr_len); + memcpy(k->args.addr, args->addr, k->args.addr_len); + k->args.args = copy_channel_args(args->args); + return k; +} + +grpc_subchannel_key *grpc_subchannel_key_create(grpc_connector *connector, + grpc_subchannel_args *args) { + return create_key(connector, args, grpc_channel_args_normalize); +} + +static grpc_subchannel_key *subchannel_key_copy(grpc_subchannel_key *k) { + return create_key(k->connector, &k->args, grpc_channel_args_copy); +} + +static int subchannel_key_compare(grpc_subchannel_key *a, + grpc_subchannel_key *b) { + int c = GPR_ICMP(a->connector, b->connector); + if (c != 0) return c; + c = GPR_ICMP(a->args.addr_len, b->args.addr_len); + if (c != 0) return c; + c = GPR_ICMP(a->args.filter_count, b->args.filter_count); + if (c != 0) return c; + c = memcmp(a->args.addr, b->args.addr, a->args.addr_len); + if (c != 0) return c; + c = memcmp(a->args.filters, b->args.filters, + a->args.filter_count * sizeof(*a->args.filters)); + if (c != 0) return c; + return grpc_channel_args_compare(a->args.args, b->args.args); +} + +void grpc_subchannel_key_destroy(grpc_exec_ctx *exec_ctx, + grpc_subchannel_key *k) { + grpc_connector_unref(exec_ctx, k->connector); + gpr_free(k->args.addr); + gpr_free((grpc_channel_args *)k->args.filters); + grpc_channel_args_destroy((grpc_channel_args *)k->args.args); + gpr_free(k); +} + +static void sck_avl_destroy(void *p) { + grpc_subchannel_key_destroy(current_ctx(), p); +} + +static void *sck_avl_copy(void *p) { return subchannel_key_copy(p); } + +static long sck_avl_compare(void *a, void *b) { + return subchannel_key_compare(a, b); +} + +static void scv_avl_destroy(void *p) { + GRPC_SUBCHANNEL_WEAK_UNREF(current_ctx(), p, "subchannel_index"); +} + +static void *scv_avl_copy(void *p) { + GRPC_SUBCHANNEL_WEAK_REF(p, "subchannel_index"); + return p; +} + +static const gpr_avl_vtable subchannel_avl_vtable = { + .destroy_key = sck_avl_destroy, + .copy_key = sck_avl_copy, + .compare_keys = sck_avl_compare, + .destroy_value = scv_avl_destroy, + .copy_value = scv_avl_copy}; + +void grpc_subchannel_index_init(void) { + g_subchannel_index = gpr_avl_create(&subchannel_avl_vtable); + gpr_mu_init(&g_mu); + gpr_tls_init(&subchannel_index_exec_ctx); +} + +void grpc_subchannel_index_shutdown(void) { + gpr_mu_destroy(&g_mu); + gpr_avl_unref(g_subchannel_index); + gpr_tls_destroy(&subchannel_index_exec_ctx); +} + +grpc_subchannel *grpc_subchannel_index_find(grpc_exec_ctx *exec_ctx, + grpc_subchannel_key *key) { + enter_ctx(exec_ctx); + + // Lock, and take a reference to the subchannel index. + // We don't need to do the search under a lock as avl's are immutable. + gpr_mu_lock(&g_mu); + gpr_avl index = gpr_avl_ref(g_subchannel_index); + gpr_mu_unlock(&g_mu); + + grpc_subchannel *c = + GRPC_SUBCHANNEL_REF_FROM_WEAK_REF(gpr_avl_get(index, key), "index_find"); + gpr_avl_unref(index); + + leave_ctx(exec_ctx); + return c; +} + +grpc_subchannel *grpc_subchannel_index_register(grpc_exec_ctx *exec_ctx, + grpc_subchannel_key *key, + grpc_subchannel *constructed) { + enter_ctx(exec_ctx); + + grpc_subchannel *c = NULL; + + while (c == NULL) { + // Compare and swap loop: + // - take a reference to the current index + gpr_mu_lock(&g_mu); + gpr_avl index = gpr_avl_ref(g_subchannel_index); + gpr_mu_unlock(&g_mu); + + // - Check to see if a subchannel already exists + c = gpr_avl_get(index, key); + if (c != NULL) { + // yes -> we're done + GRPC_SUBCHANNEL_WEAK_UNREF(exec_ctx, constructed, "index_register"); + } else { + // no -> update the avl and compare/swap + gpr_avl updated = + gpr_avl_add(gpr_avl_ref(index), subchannel_key_copy(key), + GRPC_SUBCHANNEL_WEAK_REF(constructed, "index_register")); + + // it may happen (but it's expected to be unlikely) + // that some other thread has changed the index: + // compare/swap here to check that, and retry as necessary + gpr_mu_lock(&g_mu); + if (index.root == g_subchannel_index.root) { + GPR_SWAP(gpr_avl, updated, g_subchannel_index); + c = constructed; + } + gpr_mu_unlock(&g_mu); + + gpr_avl_unref(updated); + } + gpr_avl_unref(index); + } + + leave_ctx(exec_ctx); + + return c; +} + +void grpc_subchannel_index_unregister(grpc_exec_ctx *exec_ctx, + grpc_subchannel_key *key, + grpc_subchannel *constructed) { + enter_ctx(exec_ctx); + + bool done = false; + while (!done) { + // Compare and swap loop: + // - take a reference to the current index + gpr_mu_lock(&g_mu); + gpr_avl index = gpr_avl_ref(g_subchannel_index); + gpr_mu_unlock(&g_mu); + + // Check to see if this key still refers to the previously + // registered subchannel + grpc_subchannel *c = gpr_avl_get(index, key); + if (c != constructed) { + gpr_avl_unref(index); + break; + } + + // compare and swap the update (some other thread may have + // mutated the index behind us) + gpr_avl updated = gpr_avl_remove(gpr_avl_ref(index), key); + + gpr_mu_lock(&g_mu); + if (index.root == g_subchannel_index.root) { + GPR_SWAP(gpr_avl, updated, g_subchannel_index); + done = true; + } + gpr_mu_unlock(&g_mu); + + gpr_avl_unref(updated); + gpr_avl_unref(index); + } + + leave_ctx(exec_ctx); +} diff --git a/src/core/lib/client_config/subchannel_index.h b/src/core/lib/client_config/subchannel_index.h new file mode 100644 index 0000000000..bc5f03beb4 --- /dev/null +++ b/src/core/lib/client_config/subchannel_index.h @@ -0,0 +1,77 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CLIENT_CONFIG_SUBCHANNEL_INDEX_H +#define GRPC_CORE_LIB_CLIENT_CONFIG_SUBCHANNEL_INDEX_H + +#include "src/core/lib/client_config/connector.h" +#include "src/core/lib/client_config/subchannel.h" + +/** \file Provides an index of active subchannels so that they can be + shared amongst channels */ + +typedef struct grpc_subchannel_key grpc_subchannel_key; + +/** Create a key that can be used to uniquely identify a subchannel */ +grpc_subchannel_key *grpc_subchannel_key_create(grpc_connector *con, + grpc_subchannel_args *args); + +/** Destroy a subchannel key */ +void grpc_subchannel_key_destroy(grpc_exec_ctx *exec_ctx, + grpc_subchannel_key *key); + +/** Given a subchannel key, find the subchannel registered for it. + Returns NULL if no such channel exists. + Thread-safe. */ +grpc_subchannel *grpc_subchannel_index_find(grpc_exec_ctx *exec_ctx, + grpc_subchannel_key *key); + +/** Register a subchannel against a key. + Takes ownership of \a constructed. + Returns the registered subchannel. This may be different from + \a constructed in the case of a registration race. */ +grpc_subchannel *grpc_subchannel_index_register(grpc_exec_ctx *exec_ctx, + grpc_subchannel_key *key, + grpc_subchannel *constructed); + +/** Remove \a constructed as the registered subchannel for \a key. */ +void grpc_subchannel_index_unregister(grpc_exec_ctx *exec_ctx, + grpc_subchannel_key *key, + grpc_subchannel *constructed); + +/** Initialize the subchannel index (global) */ +void grpc_subchannel_index_init(void); +/** Shutdown the subchannel index (global) */ +void grpc_subchannel_index_shutdown(void); + +#endif /* GRPC_CORE_LIB_CLIENT_CONFIG_SUBCHANNEL_INDEX_H */ diff --git a/src/core/lib/client_config/uri_parser.c b/src/core/lib/client_config/uri_parser.c new file mode 100644 index 0000000000..d3228dec5f --- /dev/null +++ b/src/core/lib/client_config/uri_parser.c @@ -0,0 +1,242 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/client_config/uri_parser.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/port_platform.h> +#include <grpc/support/string_util.h> + +/** a size_t default value... maps to all 1's */ +#define NOT_SET (~(size_t)0) + +static grpc_uri *bad_uri(const char *uri_text, size_t pos, const char *section, + int suppress_errors) { + char *line_prefix; + size_t pfx_len; + + if (!suppress_errors) { + gpr_asprintf(&line_prefix, "bad uri.%s: '", section); + pfx_len = strlen(line_prefix) + pos; + gpr_log(GPR_ERROR, "%s%s'", line_prefix, uri_text); + gpr_free(line_prefix); + + line_prefix = gpr_malloc(pfx_len + 1); + memset(line_prefix, ' ', pfx_len); + line_prefix[pfx_len] = 0; + gpr_log(GPR_ERROR, "%s^ here", line_prefix); + gpr_free(line_prefix); + } + + return NULL; +} + +/** Returns a copy of \a src[begin, end) */ +static char *copy_component(const char *src, size_t begin, size_t end) { + char *out = gpr_malloc(end - begin + 1); + memcpy(out, src + begin, end - begin); + out[end - begin] = 0; + return out; +} + +/** Returns how many chars to advance if \a uri_text[i] begins a valid \a pchar + * production. If \a uri_text[i] introduces an invalid \a pchar (such as percent + * sign not followed by two hex digits), NOT_SET is returned. */ +static size_t parse_pchar(const char *uri_text, size_t i) { + /* pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * pct-encoded = "%" HEXDIG HEXDIG + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + / "*" / "+" / "," / ";" / "=" */ + char c = uri_text[i]; + if (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) || + ((c >= '0') && (c <= '9')) || + (c == '-' || c == '.' || c == '_' || c == '~') || /* unreserved */ + (c == '!' || c == '$' || c == '&' || c == '\'' || c == '$' || c == '&' || + c == '(' || c == ')' || c == '*' || c == '+' || c == ',' || c == ';' || + c == '=') /* sub-delims */) { + return 1; + } + if (c == '%') { /* pct-encoded */ + size_t j; + if (uri_text[i + 1] == 0 || uri_text[i + 2] == 0) { + return NOT_SET; + } + for (j = i + 1; j < 2; j++) { + c = uri_text[j]; + if (!(((c >= '0') && (c <= '9')) || ((c >= 'a') && (c <= 'f')) || + ((c >= 'A') && (c <= 'F')))) { + return NOT_SET; + } + } + return 2; + } + return 0; +} + +/* *( pchar / "?" / "/" ) */ +static int parse_fragment_or_query(const char *uri_text, size_t *i) { + char c; + while ((c = uri_text[*i]) != 0) { + const size_t advance = parse_pchar(uri_text, *i); /* pchar */ + switch (advance) { + case 0: /* uri_text[i] isn't in pchar */ + /* maybe it's ? or / */ + if (uri_text[*i] == '?' || uri_text[*i] == '/') { + (*i)++; + break; + } else { + return 1; + } + GPR_UNREACHABLE_CODE(return 0); + default: + (*i) += advance; + break; + case NOT_SET: /* uri_text[i] introduces an invalid URI */ + return 0; + } + } + /* *i is the first uri_text position past the \a query production, maybe \0 */ + return 1; +} + +grpc_uri *grpc_uri_parse(const char *uri_text, int suppress_errors) { + grpc_uri *uri; + size_t scheme_begin = 0; + size_t scheme_end = NOT_SET; + size_t authority_begin = NOT_SET; + size_t authority_end = NOT_SET; + size_t path_begin = NOT_SET; + size_t path_end = NOT_SET; + size_t query_begin = NOT_SET; + size_t query_end = NOT_SET; + size_t fragment_begin = NOT_SET; + size_t fragment_end = NOT_SET; + size_t i; + + for (i = scheme_begin; uri_text[i] != 0; i++) { + if (uri_text[i] == ':') { + scheme_end = i; + break; + } + if (uri_text[i] >= 'a' && uri_text[i] <= 'z') continue; + if (uri_text[i] >= 'A' && uri_text[i] <= 'Z') continue; + if (i != scheme_begin) { + if (uri_text[i] >= '0' && uri_text[i] <= '9') continue; + if (uri_text[i] == '+') continue; + if (uri_text[i] == '-') continue; + if (uri_text[i] == '.') continue; + } + break; + } + if (scheme_end == NOT_SET) { + return bad_uri(uri_text, i, "scheme", suppress_errors); + } + + if (uri_text[scheme_end + 1] == '/' && uri_text[scheme_end + 2] == '/') { + authority_begin = scheme_end + 3; + for (i = authority_begin; uri_text[i] != 0 && authority_end == NOT_SET; + i++) { + if (uri_text[i] == '/' || uri_text[i] == '?' || uri_text[i] == '#') { + authority_end = i; + } + } + if (authority_end == NOT_SET && uri_text[i] == 0) { + authority_end = i; + } + if (authority_end == NOT_SET) { + return bad_uri(uri_text, i, "authority", suppress_errors); + } + /* TODO(ctiller): parse the authority correctly */ + path_begin = authority_end; + } else { + path_begin = scheme_end + 1; + } + + for (i = path_begin; uri_text[i] != 0; i++) { + if (uri_text[i] == '?' || uri_text[i] == '#') { + path_end = i; + break; + } + } + if (path_end == NOT_SET && uri_text[i] == 0) { + path_end = i; + } + if (path_end == NOT_SET) { + return bad_uri(uri_text, i, "path", suppress_errors); + } + + if (uri_text[i] == '?') { + query_begin = ++i; + if (!parse_fragment_or_query(uri_text, &i)) { + return bad_uri(uri_text, i, "query", suppress_errors); + } else if (uri_text[i] != 0 && uri_text[i] != '#') { + /* We must be at the end or at the beginning of a fragment */ + return bad_uri(uri_text, i, "query", suppress_errors); + } + query_end = i; + } + if (uri_text[i] == '#') { + fragment_begin = ++i; + if (!parse_fragment_or_query(uri_text, &i)) { + return bad_uri(uri_text, i - fragment_end, "fragment", suppress_errors); + } else if (uri_text[i] != 0) { + /* We must be at the end */ + return bad_uri(uri_text, i, "fragment", suppress_errors); + } + fragment_end = i; + } + + uri = gpr_malloc(sizeof(*uri)); + memset(uri, 0, sizeof(*uri)); + uri->scheme = copy_component(uri_text, scheme_begin, scheme_end); + uri->authority = copy_component(uri_text, authority_begin, authority_end); + uri->path = copy_component(uri_text, path_begin, path_end); + uri->query = copy_component(uri_text, query_begin, query_end); + uri->fragment = copy_component(uri_text, fragment_begin, fragment_end); + + return uri; +} + +void grpc_uri_destroy(grpc_uri *uri) { + if (!uri) return; + gpr_free(uri->scheme); + gpr_free(uri->authority); + gpr_free(uri->path); + gpr_free(uri->query); + gpr_free(uri->fragment); + gpr_free(uri); +} diff --git a/src/core/lib/client_config/uri_parser.h b/src/core/lib/client_config/uri_parser.h new file mode 100644 index 0000000000..d70d451e60 --- /dev/null +++ b/src/core/lib/client_config/uri_parser.h @@ -0,0 +1,51 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_CLIENT_CONFIG_URI_PARSER_H +#define GRPC_CORE_LIB_CLIENT_CONFIG_URI_PARSER_H + +typedef struct { + char *scheme; + char *authority; + char *path; + char *query; + char *fragment; +} grpc_uri; + +/** parse a uri, return NULL on failure */ +grpc_uri *grpc_uri_parse(const char *uri_text, int suppress_errors); + +/** destroy a uri */ +void grpc_uri_destroy(grpc_uri *uri); + +#endif /* GRPC_CORE_LIB_CLIENT_CONFIG_URI_PARSER_H */ diff --git a/src/core/lib/compression/algorithm_metadata.h b/src/core/lib/compression/algorithm_metadata.h new file mode 100644 index 0000000000..47f33abdc7 --- /dev/null +++ b/src/core/lib/compression/algorithm_metadata.h @@ -0,0 +1,53 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_COMPRESSION_ALGORITHM_METADATA_H +#define GRPC_CORE_LIB_COMPRESSION_ALGORITHM_METADATA_H + +#include <grpc/compression.h> +#include "src/core/lib/transport/metadata.h" + +/** Return compression algorithm based metadata value */ +grpc_mdstr *grpc_compression_algorithm_mdstr( + grpc_compression_algorithm algorithm); + +/** Return compression algorithm based metadata element (grpc-encoding: xxx) */ +grpc_mdelem *grpc_compression_encoding_mdelem( + grpc_compression_algorithm algorithm); + +/** Find compression algorithm based on passed in mdstr - returns + * GRPC_COMPRESS_ALGORITHM_COUNT on failure */ +grpc_compression_algorithm grpc_compression_algorithm_from_mdstr( + grpc_mdstr *str); + +#endif /* GRPC_CORE_LIB_COMPRESSION_ALGORITHM_METADATA_H */ diff --git a/src/core/lib/compression/compression_algorithm.c b/src/core/lib/compression/compression_algorithm.c new file mode 100644 index 0000000000..f781b45042 --- /dev/null +++ b/src/core/lib/compression/compression_algorithm.c @@ -0,0 +1,203 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <stdlib.h> +#include <string.h> + +#include <grpc/compression.h> +#include <grpc/support/useful.h> + +#include "src/core/lib/compression/algorithm_metadata.h" +#include "src/core/lib/surface/api_trace.h" +#include "src/core/lib/transport/static_metadata.h" + +int grpc_compression_algorithm_parse(const char *name, size_t name_length, + grpc_compression_algorithm *algorithm) { + /* we use strncmp not only because it's safer (even though in this case it + * doesn't matter, given that we are comparing against string literals, but + * because this way we needn't have "name" nil-terminated (useful for slice + * data, for example) */ + GRPC_API_TRACE( + "grpc_compression_algorithm_parse(" + "name=%*.*s, name_length=%lu, algorithm=%p)", + 5, ((int)name_length, (int)name_length, name, (unsigned long)name_length, + algorithm)); + if (name_length == 0) { + return 0; + } + if (strncmp(name, "identity", name_length) == 0) { + *algorithm = GRPC_COMPRESS_NONE; + } else if (strncmp(name, "gzip", name_length) == 0) { + *algorithm = GRPC_COMPRESS_GZIP; + } else if (strncmp(name, "deflate", name_length) == 0) { + *algorithm = GRPC_COMPRESS_DEFLATE; + } else { + return 0; + } + return 1; +} + +int grpc_compression_algorithm_name(grpc_compression_algorithm algorithm, + char **name) { + GRPC_API_TRACE("grpc_compression_algorithm_parse(algorithm=%d, name=%p)", 2, + ((int)algorithm, name)); + switch (algorithm) { + case GRPC_COMPRESS_NONE: + *name = "identity"; + return 1; + case GRPC_COMPRESS_DEFLATE: + *name = "deflate"; + return 1; + case GRPC_COMPRESS_GZIP: + *name = "gzip"; + return 1; + case GRPC_COMPRESS_ALGORITHMS_COUNT: + return 0; + } + return 0; +} + +grpc_compression_algorithm grpc_compression_algorithm_from_mdstr( + grpc_mdstr *str) { + if (str == GRPC_MDSTR_IDENTITY) return GRPC_COMPRESS_NONE; + if (str == GRPC_MDSTR_DEFLATE) return GRPC_COMPRESS_DEFLATE; + if (str == GRPC_MDSTR_GZIP) return GRPC_COMPRESS_GZIP; + return GRPC_COMPRESS_ALGORITHMS_COUNT; +} + +grpc_mdstr *grpc_compression_algorithm_mdstr( + grpc_compression_algorithm algorithm) { + switch (algorithm) { + case GRPC_COMPRESS_NONE: + return GRPC_MDSTR_IDENTITY; + case GRPC_COMPRESS_DEFLATE: + return GRPC_MDSTR_DEFLATE; + case GRPC_COMPRESS_GZIP: + return GRPC_MDSTR_GZIP; + case GRPC_COMPRESS_ALGORITHMS_COUNT: + return NULL; + } + return NULL; +} + +grpc_mdelem *grpc_compression_encoding_mdelem( + grpc_compression_algorithm algorithm) { + switch (algorithm) { + case GRPC_COMPRESS_NONE: + return GRPC_MDELEM_GRPC_ENCODING_IDENTITY; + case GRPC_COMPRESS_DEFLATE: + return GRPC_MDELEM_GRPC_ENCODING_DEFLATE; + case GRPC_COMPRESS_GZIP: + return GRPC_MDELEM_GRPC_ENCODING_GZIP; + default: + break; + } + return NULL; +} + +/* 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, 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[GRPC_COMPRESS_ALGORITHMS_COUNT]; + 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: + 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 sorted_supported_algos[num_supported - 1]; + default: + abort(); + }; +} + +void grpc_compression_options_init(grpc_compression_options *opts) { + opts->enabled_algorithms_bitset = (1u << GRPC_COMPRESS_ALGORITHMS_COUNT) - 1; + opts->default_compression_algorithm = GRPC_COMPRESS_NONE; +} + +void grpc_compression_options_enable_algorithm( + grpc_compression_options *opts, grpc_compression_algorithm algorithm) { + GPR_BITSET(&opts->enabled_algorithms_bitset, algorithm); +} + +void grpc_compression_options_disable_algorithm( + grpc_compression_options *opts, grpc_compression_algorithm algorithm) { + GPR_BITCLEAR(&opts->enabled_algorithms_bitset, algorithm); +} + +int grpc_compression_options_is_algorithm_enabled( + const grpc_compression_options *opts, + grpc_compression_algorithm algorithm) { + return GPR_BITGET(opts->enabled_algorithms_bitset, algorithm); +} diff --git a/src/core/lib/compression/message_compress.c b/src/core/lib/compression/message_compress.c new file mode 100644 index 0000000000..b4b6a2d75e --- /dev/null +++ b/src/core/lib/compression/message_compress.c @@ -0,0 +1,198 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/compression/message_compress.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +#include <zlib.h> + +#define OUTPUT_BLOCK_SIZE 1024 + +static int zlib_body(z_stream* zs, gpr_slice_buffer* input, + gpr_slice_buffer* output, + int (*flate)(z_stream* zs, int flush)) { + int r; + int flush; + size_t i; + gpr_slice outbuf = gpr_slice_malloc(OUTPUT_BLOCK_SIZE); + const uInt uint_max = ~(uInt)0; + + GPR_ASSERT(GPR_SLICE_LENGTH(outbuf) <= uint_max); + zs->avail_out = (uInt)GPR_SLICE_LENGTH(outbuf); + zs->next_out = GPR_SLICE_START_PTR(outbuf); + flush = Z_NO_FLUSH; + for (i = 0; i < input->count; i++) { + if (i == input->count - 1) flush = Z_FINISH; + GPR_ASSERT(GPR_SLICE_LENGTH(input->slices[i]) <= uint_max); + zs->avail_in = (uInt)GPR_SLICE_LENGTH(input->slices[i]); + zs->next_in = GPR_SLICE_START_PTR(input->slices[i]); + do { + if (zs->avail_out == 0) { + gpr_slice_buffer_add_indexed(output, outbuf); + outbuf = gpr_slice_malloc(OUTPUT_BLOCK_SIZE); + GPR_ASSERT(GPR_SLICE_LENGTH(outbuf) <= uint_max); + zs->avail_out = (uInt)GPR_SLICE_LENGTH(outbuf); + zs->next_out = GPR_SLICE_START_PTR(outbuf); + } + r = flate(zs, flush); + if (r < 0 && r != Z_BUF_ERROR /* not fatal */) { + gpr_log(GPR_INFO, "zlib error (%d)", r); + goto error; + } + } while (zs->avail_out == 0); + if (zs->avail_in) { + gpr_log(GPR_INFO, "zlib: not all input consumed"); + goto error; + } + } + + GPR_ASSERT(outbuf.refcount); + outbuf.data.refcounted.length -= zs->avail_out; + gpr_slice_buffer_add_indexed(output, outbuf); + + return 1; + +error: + gpr_slice_unref(outbuf); + return 0; +} + +static void* zalloc_gpr(void* opaque, unsigned int items, unsigned int size) { + return gpr_malloc(items * size); +} + +static void zfree_gpr(void* opaque, void* address) { gpr_free(address); } + +static int zlib_compress(gpr_slice_buffer* input, gpr_slice_buffer* output, + int gzip) { + z_stream zs; + int r; + size_t i; + size_t count_before = output->count; + size_t length_before = output->length; + memset(&zs, 0, sizeof(zs)); + zs.zalloc = zalloc_gpr; + zs.zfree = zfree_gpr; + r = deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 | (gzip ? 16 : 0), + 8, Z_DEFAULT_STRATEGY); + GPR_ASSERT(r == Z_OK); + r = zlib_body(&zs, input, output, deflate) && output->length < input->length; + if (!r) { + for (i = count_before; i < output->count; i++) { + gpr_slice_unref(output->slices[i]); + } + output->count = count_before; + output->length = length_before; + } + deflateEnd(&zs); + return r; +} + +static int zlib_decompress(gpr_slice_buffer* input, gpr_slice_buffer* output, + int gzip) { + z_stream zs; + int r; + size_t i; + size_t count_before = output->count; + size_t length_before = output->length; + memset(&zs, 0, sizeof(zs)); + zs.zalloc = zalloc_gpr; + zs.zfree = zfree_gpr; + r = inflateInit2(&zs, 15 | (gzip ? 16 : 0)); + GPR_ASSERT(r == Z_OK); + r = zlib_body(&zs, input, output, inflate); + if (!r) { + for (i = count_before; i < output->count; i++) { + gpr_slice_unref(output->slices[i]); + } + output->count = count_before; + output->length = length_before; + } + inflateEnd(&zs); + return r; +} + +static int copy(gpr_slice_buffer* input, gpr_slice_buffer* output) { + size_t i; + for (i = 0; i < input->count; i++) { + gpr_slice_buffer_add(output, gpr_slice_ref(input->slices[i])); + } + return 1; +} + +static int compress_inner(grpc_compression_algorithm algorithm, + gpr_slice_buffer* input, gpr_slice_buffer* output) { + switch (algorithm) { + case GRPC_COMPRESS_NONE: + /* the fallback path always needs to be send uncompressed: we simply + rely on that here */ + return 0; + case GRPC_COMPRESS_DEFLATE: + return zlib_compress(input, output, 0); + case GRPC_COMPRESS_GZIP: + return zlib_compress(input, output, 1); + case GRPC_COMPRESS_ALGORITHMS_COUNT: + break; + } + gpr_log(GPR_ERROR, "invalid compression algorithm %d", algorithm); + return 0; +} + +int grpc_msg_compress(grpc_compression_algorithm algorithm, + gpr_slice_buffer* input, gpr_slice_buffer* output) { + if (!compress_inner(algorithm, input, output)) { + copy(input, output); + return 0; + } + return 1; +} + +int grpc_msg_decompress(grpc_compression_algorithm algorithm, + gpr_slice_buffer* input, gpr_slice_buffer* output) { + switch (algorithm) { + case GRPC_COMPRESS_NONE: + return copy(input, output); + case GRPC_COMPRESS_DEFLATE: + return zlib_decompress(input, output, 0); + case GRPC_COMPRESS_GZIP: + return zlib_decompress(input, output, 1); + case GRPC_COMPRESS_ALGORITHMS_COUNT: + break; + } + gpr_log(GPR_ERROR, "invalid compression algorithm %d", algorithm); + return 0; +} diff --git a/src/core/lib/compression/message_compress.h b/src/core/lib/compression/message_compress.h new file mode 100644 index 0000000000..b71608139e --- /dev/null +++ b/src/core/lib/compression/message_compress.h @@ -0,0 +1,52 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_COMPRESSION_MESSAGE_COMPRESS_H +#define GRPC_CORE_LIB_COMPRESSION_MESSAGE_COMPRESS_H + +#include <grpc/compression.h> +#include <grpc/support/slice_buffer.h> + +/* compress 'input' to 'output' using 'algorithm'. + On success, appends compressed slices to output and returns 1. + On failure, appends uncompressed slices to output and returns 0. */ +int grpc_msg_compress(grpc_compression_algorithm algorithm, + gpr_slice_buffer* input, gpr_slice_buffer* output); + +/* decompress 'input' to 'output' using 'algorithm'. + On success, appends slices to output and returns 1. + On failure, output is unchanged, and returns 0. */ +int grpc_msg_decompress(grpc_compression_algorithm algorithm, + gpr_slice_buffer* input, gpr_slice_buffer* output); + +#endif /* GRPC_CORE_LIB_COMPRESSION_MESSAGE_COMPRESS_H */ diff --git a/src/core/lib/debug/trace.c b/src/core/lib/debug/trace.c new file mode 100644 index 0000000000..786dd9324f --- /dev/null +++ b/src/core/lib/debug/trace.c @@ -0,0 +1,136 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/debug/trace.h" + +#include <string.h> + +#include <grpc/grpc.h> +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include "src/core/lib/support/env.h" + +typedef struct tracer { + const char *name; + int *flag; + struct tracer *next; +} tracer; +static tracer *tracers; + +void grpc_register_tracer(const char *name, int *flag) { + tracer *t = gpr_malloc(sizeof(*t)); + t->name = name; + t->flag = flag; + t->next = tracers; + *flag = 0; + tracers = t; +} + +static void add(const char *beg, const char *end, char ***ss, size_t *ns) { + size_t n = *ns; + size_t np = n + 1; + char *s; + size_t len; + GPR_ASSERT(end >= beg); + len = (size_t)(end - beg); + s = gpr_malloc(len + 1); + memcpy(s, beg, len); + s[len] = 0; + *ss = gpr_realloc(*ss, sizeof(char **) * np); + (*ss)[n] = s; + *ns = np; +} + +static void split(const char *s, char ***ss, size_t *ns) { + const char *c = strchr(s, ','); + if (c == NULL) { + add(s, s + strlen(s), ss, ns); + } else { + add(s, c, ss, ns); + split(c + 1, ss, ns); + } +} + +static void parse(const char *s) { + char **strings = NULL; + size_t nstrings = 0; + size_t i; + split(s, &strings, &nstrings); + + for (i = 0; i < nstrings; i++) { + grpc_tracer_set_enabled(strings[i], 1); + } + + for (i = 0; i < nstrings; i++) { + gpr_free(strings[i]); + } + gpr_free(strings); +} + +void grpc_tracer_init(const char *env_var) { + char *e = gpr_getenv(env_var); + if (e != NULL) { + parse(e); + gpr_free(e); + } +} + +void grpc_tracer_shutdown(void) { + while (tracers) { + tracer *t = tracers; + tracers = t->next; + gpr_free(t); + } +} + +int grpc_tracer_set_enabled(const char *name, int enabled) { + tracer *t; + if (0 == strcmp(name, "all")) { + for (t = tracers; t; t = t->next) { + *t->flag = 1; + } + } else { + int found = 0; + for (t = tracers; t; t = t->next) { + if (0 == strcmp(name, t->name)) { + *t->flag = enabled; + found = 1; + } + } + if (!found) { + gpr_log(GPR_ERROR, "Unknown trace var: '%s'", name); + return 0; /* early return */ + } + } + return 1; +} diff --git a/src/core/lib/debug/trace.h b/src/core/lib/debug/trace.h new file mode 100644 index 0000000000..76ea5a7c64 --- /dev/null +++ b/src/core/lib/debug/trace.h @@ -0,0 +1,43 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_DEBUG_TRACE_H +#define GRPC_CORE_LIB_DEBUG_TRACE_H + +#include <grpc/support/port_platform.h> + +void grpc_register_tracer(const char *name, int *flag); +void grpc_tracer_init(const char *env_var_name); +void grpc_tracer_shutdown(void); + +#endif /* GRPC_CORE_LIB_DEBUG_TRACE_H */ diff --git a/src/core/lib/http/format_request.c b/src/core/lib/http/format_request.c new file mode 100644 index 0000000000..95b3918646 --- /dev/null +++ b/src/core/lib/http/format_request.c @@ -0,0 +1,120 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/http/format_request.h" + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/slice.h> +#include <grpc/support/string_util.h> +#include <grpc/support/useful.h> +#include "src/core/lib/support/string.h" + +static void fill_common_header(const grpc_httpcli_request *request, + gpr_strvec *buf) { + size_t i; + gpr_strvec_add(buf, gpr_strdup(request->http.path)); + gpr_strvec_add(buf, gpr_strdup(" HTTP/1.0\r\n")); + /* just in case some crazy server really expects HTTP/1.1 */ + gpr_strvec_add(buf, gpr_strdup("Host: ")); + gpr_strvec_add(buf, gpr_strdup(request->host)); + gpr_strvec_add(buf, gpr_strdup("\r\n")); + gpr_strvec_add(buf, gpr_strdup("Connection: close\r\n")); + gpr_strvec_add(buf, + gpr_strdup("User-Agent: " GRPC_HTTPCLI_USER_AGENT "\r\n")); + /* user supplied headers */ + for (i = 0; i < request->http.hdr_count; i++) { + gpr_strvec_add(buf, gpr_strdup(request->http.hdrs[i].key)); + gpr_strvec_add(buf, gpr_strdup(": ")); + gpr_strvec_add(buf, gpr_strdup(request->http.hdrs[i].value)); + gpr_strvec_add(buf, gpr_strdup("\r\n")); + } +} + +gpr_slice grpc_httpcli_format_get_request(const grpc_httpcli_request *request) { + gpr_strvec out; + char *flat; + size_t flat_len; + + gpr_strvec_init(&out); + gpr_strvec_add(&out, gpr_strdup("GET ")); + fill_common_header(request, &out); + gpr_strvec_add(&out, gpr_strdup("\r\n")); + + flat = gpr_strvec_flatten(&out, &flat_len); + gpr_strvec_destroy(&out); + + return gpr_slice_new(flat, flat_len, gpr_free); +} + +gpr_slice grpc_httpcli_format_post_request(const grpc_httpcli_request *request, + const char *body_bytes, + size_t body_size) { + gpr_strvec out; + char *tmp; + size_t out_len; + size_t i; + + gpr_strvec_init(&out); + + gpr_strvec_add(&out, gpr_strdup("POST ")); + fill_common_header(request, &out); + if (body_bytes) { + uint8_t has_content_type = 0; + for (i = 0; i < request->http.hdr_count; i++) { + if (strcmp(request->http.hdrs[i].key, "Content-Type") == 0) { + has_content_type = 1; + break; + } + } + if (!has_content_type) { + gpr_strvec_add(&out, gpr_strdup("Content-Type: text/plain\r\n")); + } + gpr_asprintf(&tmp, "Content-Length: %lu\r\n", (unsigned long)body_size); + gpr_strvec_add(&out, tmp); + } + gpr_strvec_add(&out, gpr_strdup("\r\n")); + tmp = gpr_strvec_flatten(&out, &out_len); + gpr_strvec_destroy(&out); + + if (body_bytes) { + tmp = gpr_realloc(tmp, out_len + body_size); + memcpy(tmp + out_len, body_bytes, body_size); + out_len += body_size; + } + + return gpr_slice_new(tmp, out_len, gpr_free); +} diff --git a/src/core/lib/http/format_request.h b/src/core/lib/http/format_request.h new file mode 100644 index 0000000000..2e933d804b --- /dev/null +++ b/src/core/lib/http/format_request.h @@ -0,0 +1,45 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_HTTP_FORMAT_REQUEST_H +#define GRPC_CORE_LIB_HTTP_FORMAT_REQUEST_H + +#include <grpc/support/slice.h> +#include "src/core/lib/http/httpcli.h" + +gpr_slice grpc_httpcli_format_get_request(const grpc_httpcli_request *request); +gpr_slice grpc_httpcli_format_post_request(const grpc_httpcli_request *request, + const char *body_bytes, + size_t body_size); + +#endif /* GRPC_CORE_LIB_HTTP_FORMAT_REQUEST_H */ diff --git a/src/core/lib/http/httpcli.c b/src/core/lib/http/httpcli.c new file mode 100644 index 0000000000..aab28ad8b6 --- /dev/null +++ b/src/core/lib/http/httpcli.c @@ -0,0 +1,293 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/http/httpcli.h" +#include "src/core/lib/iomgr/sockaddr.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> + +#include "src/core/lib/http/format_request.h" +#include "src/core/lib/http/parser.h" +#include "src/core/lib/iomgr/endpoint.h" +#include "src/core/lib/iomgr/iomgr_internal.h" +#include "src/core/lib/iomgr/resolve_address.h" +#include "src/core/lib/iomgr/tcp_client.h" +#include "src/core/lib/support/string.h" + +typedef struct { + gpr_slice request_text; + grpc_http_parser parser; + grpc_resolved_addresses *addresses; + size_t next_address; + grpc_endpoint *ep; + char *host; + char *ssl_host_override; + gpr_timespec deadline; + int have_read_byte; + const grpc_httpcli_handshaker *handshaker; + grpc_httpcli_response_cb on_response; + void *user_data; + grpc_httpcli_context *context; + grpc_pollset *pollset; + grpc_iomgr_object iomgr_obj; + gpr_slice_buffer incoming; + gpr_slice_buffer outgoing; + grpc_closure on_read; + grpc_closure done_write; + grpc_closure connected; +} internal_request; + +static grpc_httpcli_get_override g_get_override = NULL; +static grpc_httpcli_post_override g_post_override = NULL; + +static void plaintext_handshake(grpc_exec_ctx *exec_ctx, void *arg, + grpc_endpoint *endpoint, const char *host, + void (*on_done)(grpc_exec_ctx *exec_ctx, + void *arg, + grpc_endpoint *endpoint)) { + on_done(exec_ctx, arg, endpoint); +} + +const grpc_httpcli_handshaker grpc_httpcli_plaintext = {"http", + plaintext_handshake}; + +void grpc_httpcli_context_init(grpc_httpcli_context *context) { + context->pollset_set = grpc_pollset_set_create(); +} + +void grpc_httpcli_context_destroy(grpc_httpcli_context *context) { + grpc_pollset_set_destroy(context->pollset_set); +} + +static void next_address(grpc_exec_ctx *exec_ctx, internal_request *req); + +static void finish(grpc_exec_ctx *exec_ctx, internal_request *req, + int success) { + grpc_pollset_set_del_pollset(exec_ctx, req->context->pollset_set, + req->pollset); + req->on_response(exec_ctx, req->user_data, + success ? &req->parser.http.response : NULL); + grpc_http_parser_destroy(&req->parser); + if (req->addresses != NULL) { + grpc_resolved_addresses_destroy(req->addresses); + } + if (req->ep != NULL) { + grpc_endpoint_destroy(exec_ctx, req->ep); + } + gpr_slice_unref(req->request_text); + gpr_free(req->host); + gpr_free(req->ssl_host_override); + grpc_iomgr_unregister_object(&req->iomgr_obj); + gpr_slice_buffer_destroy(&req->incoming); + gpr_slice_buffer_destroy(&req->outgoing); + gpr_free(req); +} + +static void on_read(grpc_exec_ctx *exec_ctx, void *user_data, bool success); + +static void do_read(grpc_exec_ctx *exec_ctx, internal_request *req) { + grpc_endpoint_read(exec_ctx, req->ep, &req->incoming, &req->on_read); +} + +static void on_read(grpc_exec_ctx *exec_ctx, void *user_data, bool success) { + internal_request *req = user_data; + size_t i; + + for (i = 0; i < req->incoming.count; i++) { + if (GPR_SLICE_LENGTH(req->incoming.slices[i])) { + req->have_read_byte = 1; + if (!grpc_http_parser_parse(&req->parser, req->incoming.slices[i])) { + finish(exec_ctx, req, 0); + return; + } + } + } + + if (success) { + do_read(exec_ctx, req); + } else if (!req->have_read_byte) { + next_address(exec_ctx, req); + } else { + int parse_success = grpc_http_parser_eof(&req->parser); + if (parse_success && (req->parser.type != GRPC_HTTP_RESPONSE)) { + parse_success = 0; + } + finish(exec_ctx, req, parse_success); + } +} + +static void on_written(grpc_exec_ctx *exec_ctx, internal_request *req) { + do_read(exec_ctx, req); +} + +static void done_write(grpc_exec_ctx *exec_ctx, void *arg, bool success) { + internal_request *req = arg; + if (success) { + on_written(exec_ctx, req); + } else { + next_address(exec_ctx, req); + } +} + +static void start_write(grpc_exec_ctx *exec_ctx, internal_request *req) { + gpr_slice_ref(req->request_text); + gpr_slice_buffer_add(&req->outgoing, req->request_text); + grpc_endpoint_write(exec_ctx, req->ep, &req->outgoing, &req->done_write); +} + +static void on_handshake_done(grpc_exec_ctx *exec_ctx, void *arg, + grpc_endpoint *ep) { + internal_request *req = arg; + + if (!ep) { + next_address(exec_ctx, req); + return; + } + + req->ep = ep; + start_write(exec_ctx, req); +} + +static void on_connected(grpc_exec_ctx *exec_ctx, void *arg, bool success) { + internal_request *req = arg; + + if (!req->ep) { + next_address(exec_ctx, req); + return; + } + req->handshaker->handshake( + exec_ctx, req, req->ep, + req->ssl_host_override ? req->ssl_host_override : req->host, + on_handshake_done); +} + +static void next_address(grpc_exec_ctx *exec_ctx, internal_request *req) { + grpc_resolved_address *addr; + if (req->next_address == req->addresses->naddrs) { + finish(exec_ctx, req, 0); + return; + } + addr = &req->addresses->addrs[req->next_address++]; + grpc_closure_init(&req->connected, on_connected, req); + grpc_tcp_client_connect( + exec_ctx, &req->connected, &req->ep, req->context->pollset_set, + (struct sockaddr *)&addr->addr, addr->len, req->deadline); +} + +static void on_resolved(grpc_exec_ctx *exec_ctx, void *arg, + grpc_resolved_addresses *addresses) { + internal_request *req = arg; + if (!addresses) { + finish(exec_ctx, req, 0); + return; + } + req->addresses = addresses; + req->next_address = 0; + next_address(exec_ctx, req); +} + +static void internal_request_begin( + grpc_exec_ctx *exec_ctx, grpc_httpcli_context *context, + grpc_pollset *pollset, const grpc_httpcli_request *request, + gpr_timespec deadline, grpc_httpcli_response_cb on_response, + void *user_data, const char *name, gpr_slice request_text) { + internal_request *req = gpr_malloc(sizeof(internal_request)); + memset(req, 0, sizeof(*req)); + req->request_text = request_text; + grpc_http_parser_init(&req->parser); + req->on_response = on_response; + req->user_data = user_data; + req->deadline = deadline; + req->handshaker = + request->handshaker ? request->handshaker : &grpc_httpcli_plaintext; + req->context = context; + req->pollset = pollset; + grpc_closure_init(&req->on_read, on_read, req); + grpc_closure_init(&req->done_write, done_write, req); + gpr_slice_buffer_init(&req->incoming); + gpr_slice_buffer_init(&req->outgoing); + grpc_iomgr_register_object(&req->iomgr_obj, name); + req->host = gpr_strdup(request->host); + req->ssl_host_override = gpr_strdup(request->ssl_host_override); + + grpc_pollset_set_add_pollset(exec_ctx, req->context->pollset_set, + req->pollset); + grpc_resolve_address(request->host, req->handshaker->default_port, + on_resolved, req); +} + +void grpc_httpcli_get(grpc_exec_ctx *exec_ctx, grpc_httpcli_context *context, + grpc_pollset *pollset, + const grpc_httpcli_request *request, + gpr_timespec deadline, + grpc_httpcli_response_cb on_response, void *user_data) { + char *name; + if (g_get_override && + g_get_override(exec_ctx, request, deadline, on_response, user_data)) { + return; + } + gpr_asprintf(&name, "HTTP:GET:%s:%s", request->host, request->http.path); + internal_request_begin(exec_ctx, context, pollset, request, deadline, + on_response, user_data, name, + grpc_httpcli_format_get_request(request)); + gpr_free(name); +} + +void grpc_httpcli_post(grpc_exec_ctx *exec_ctx, grpc_httpcli_context *context, + grpc_pollset *pollset, + const grpc_httpcli_request *request, + const char *body_bytes, size_t body_size, + gpr_timespec deadline, + grpc_httpcli_response_cb on_response, void *user_data) { + char *name; + if (g_post_override && + g_post_override(exec_ctx, request, body_bytes, body_size, deadline, + on_response, user_data)) { + return; + } + gpr_asprintf(&name, "HTTP:POST:%s:%s", request->host, request->http.path); + internal_request_begin( + exec_ctx, context, pollset, request, deadline, on_response, user_data, + name, grpc_httpcli_format_post_request(request, body_bytes, body_size)); + gpr_free(name); +} + +void grpc_httpcli_set_override(grpc_httpcli_get_override get, + grpc_httpcli_post_override post) { + g_get_override = get; + g_post_override = post; +} diff --git a/src/core/lib/http/httpcli.h b/src/core/lib/http/httpcli.h new file mode 100644 index 0000000000..b8d54a8586 --- /dev/null +++ b/src/core/lib/http/httpcli.h @@ -0,0 +1,144 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_HTTP_HTTPCLI_H +#define GRPC_CORE_LIB_HTTP_HTTPCLI_H + +#include <stddef.h> + +#include <grpc/support/time.h> + +#include "src/core/lib/http/parser.h" +#include "src/core/lib/iomgr/endpoint.h" +#include "src/core/lib/iomgr/iomgr_internal.h" +#include "src/core/lib/iomgr/pollset_set.h" + +/* User agent this library reports */ +#define GRPC_HTTPCLI_USER_AGENT "grpc-httpcli/0.0" + +/* Tracks in-progress http requests + TODO(ctiller): allow caching and capturing multiple requests for the + same content and combining them */ +typedef struct grpc_httpcli_context { + grpc_pollset_set *pollset_set; +} grpc_httpcli_context; + +typedef struct { + const char *default_port; + void (*handshake)(grpc_exec_ctx *exec_ctx, void *arg, grpc_endpoint *endpoint, + const char *host, + void (*on_done)(grpc_exec_ctx *exec_ctx, void *arg, + grpc_endpoint *endpoint)); +} grpc_httpcli_handshaker; + +extern const grpc_httpcli_handshaker grpc_httpcli_plaintext; +extern const grpc_httpcli_handshaker grpc_httpcli_ssl; + +/* A request */ +typedef struct grpc_httpcli_request { + /* The host name to connect to */ + char *host; + /* The host to verify in the SSL handshake (or NULL) */ + char *ssl_host_override; + /* The main part of the request + The following headers are supplied automatically and MUST NOT be set here: + Host, Connection, User-Agent */ + grpc_http_request http; + /* handshaker to use ssl for the request */ + const grpc_httpcli_handshaker *handshaker; +} grpc_httpcli_request; + +/* Expose the parser response type as a httpcli response too */ +typedef struct grpc_http_response grpc_httpcli_response; + +/* Callback for grpc_httpcli_get and grpc_httpcli_post. */ +typedef void (*grpc_httpcli_response_cb)(grpc_exec_ctx *exec_ctx, + void *user_data, + const grpc_http_response *response); + +void grpc_httpcli_context_init(grpc_httpcli_context *context); +void grpc_httpcli_context_destroy(grpc_httpcli_context *context); + +/* Asynchronously perform a HTTP GET. + 'context' specifies the http context under which to do the get + 'pollset' indicates a grpc_pollset that is interested in the result + of the get - work on this pollset may be used to progress the get + operation + 'request' contains request parameters - these are caller owned and can be + destroyed once the call returns + 'deadline' contains a deadline for the request (or gpr_inf_future) + 'on_response' is a callback to report results to (and 'user_data' is a user + supplied pointer to pass to said call) */ +void grpc_httpcli_get(grpc_exec_ctx *exec_ctx, grpc_httpcli_context *context, + grpc_pollset *pollset, + const grpc_httpcli_request *request, + gpr_timespec deadline, + grpc_httpcli_response_cb on_response, void *user_data); + +/* Asynchronously perform a HTTP POST. + 'context' specifies the http context under which to do the post + 'pollset' indicates a grpc_pollset that is interested in the result + of the post - work on this pollset may be used to progress the post + operation + 'request' contains request parameters - these are caller owned and can be + destroyed once the call returns + 'body_bytes' and 'body_size' specify the payload for the post. + When there is no body, pass in NULL as body_bytes. + 'deadline' contains a deadline for the request (or gpr_inf_future) + 'em' points to a caller owned event manager that must be alive for the + lifetime of the request + 'on_response' is a callback to report results to (and 'user_data' is a user + supplied pointer to pass to said call) + Does not support ?var1=val1&var2=val2 in the path. */ +void grpc_httpcli_post(grpc_exec_ctx *exec_ctx, grpc_httpcli_context *context, + grpc_pollset *pollset, + const grpc_httpcli_request *request, + const char *body_bytes, size_t body_size, + gpr_timespec deadline, + grpc_httpcli_response_cb on_response, void *user_data); + +/* override functions return 1 if they handled the request, 0 otherwise */ +typedef int (*grpc_httpcli_get_override)(grpc_exec_ctx *exec_ctx, + const grpc_httpcli_request *request, + gpr_timespec deadline, + grpc_httpcli_response_cb on_response, + void *user_data); +typedef int (*grpc_httpcli_post_override)( + grpc_exec_ctx *exec_ctx, const grpc_httpcli_request *request, + const char *body_bytes, size_t body_size, gpr_timespec deadline, + grpc_httpcli_response_cb on_response, void *user_data); + +void grpc_httpcli_set_override(grpc_httpcli_get_override get, + grpc_httpcli_post_override post); + +#endif /* GRPC_CORE_LIB_HTTP_HTTPCLI_H */ diff --git a/src/core/lib/http/httpcli_security_connector.c b/src/core/lib/http/httpcli_security_connector.c new file mode 100644 index 0000000000..6f1630ac1f --- /dev/null +++ b/src/core/lib/http/httpcli_security_connector.c @@ -0,0 +1,188 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/http/httpcli.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> +#include "src/core/lib/security/handshake.h" +#include "src/core/lib/support/string.h" +#include "src/core/lib/tsi/ssl_transport_security.h" + +typedef struct { + grpc_channel_security_connector base; + tsi_ssl_handshaker_factory *handshaker_factory; + char *secure_peer_name; +} grpc_httpcli_ssl_channel_security_connector; + +static void httpcli_ssl_destroy(grpc_security_connector *sc) { + grpc_httpcli_ssl_channel_security_connector *c = + (grpc_httpcli_ssl_channel_security_connector *)sc; + if (c->handshaker_factory != NULL) { + tsi_ssl_handshaker_factory_destroy(c->handshaker_factory); + } + if (c->secure_peer_name != NULL) gpr_free(c->secure_peer_name); + gpr_free(sc); +} + +static void httpcli_ssl_do_handshake(grpc_exec_ctx *exec_ctx, + grpc_channel_security_connector *sc, + grpc_endpoint *nonsecure_endpoint, + grpc_security_handshake_done_cb cb, + void *user_data) { + grpc_httpcli_ssl_channel_security_connector *c = + (grpc_httpcli_ssl_channel_security_connector *)sc; + tsi_result result = TSI_OK; + tsi_handshaker *handshaker; + if (c->handshaker_factory == NULL) { + cb(exec_ctx, user_data, GRPC_SECURITY_ERROR, NULL, NULL); + return; + } + result = tsi_ssl_handshaker_factory_create_handshaker( + c->handshaker_factory, c->secure_peer_name, &handshaker); + if (result != TSI_OK) { + gpr_log(GPR_ERROR, "Handshaker creation failed with error %s.", + tsi_result_to_string(result)); + cb(exec_ctx, user_data, GRPC_SECURITY_ERROR, NULL, NULL); + } else { + grpc_do_security_handshake(exec_ctx, handshaker, &sc->base, true, + nonsecure_endpoint, cb, user_data); + } +} + +static void httpcli_ssl_check_peer(grpc_exec_ctx *exec_ctx, + grpc_security_connector *sc, tsi_peer peer, + grpc_security_peer_check_cb cb, + void *user_data) { + grpc_httpcli_ssl_channel_security_connector *c = + (grpc_httpcli_ssl_channel_security_connector *)sc; + grpc_security_status status = GRPC_SECURITY_OK; + + /* Check the peer name. */ + if (c->secure_peer_name != NULL && + !tsi_ssl_peer_matches_name(&peer, c->secure_peer_name)) { + gpr_log(GPR_ERROR, "Peer name %s is not in peer certificate", + c->secure_peer_name); + status = GRPC_SECURITY_ERROR; + } + cb(exec_ctx, user_data, status, NULL); + tsi_peer_destruct(&peer); +} + +static grpc_security_connector_vtable httpcli_ssl_vtable = { + httpcli_ssl_destroy, httpcli_ssl_check_peer}; + +static grpc_security_status httpcli_ssl_channel_security_connector_create( + const unsigned char *pem_root_certs, size_t pem_root_certs_size, + const char *secure_peer_name, grpc_channel_security_connector **sc) { + tsi_result result = TSI_OK; + grpc_httpcli_ssl_channel_security_connector *c; + + if (secure_peer_name != NULL && pem_root_certs == NULL) { + gpr_log(GPR_ERROR, + "Cannot assert a secure peer name without a trust root."); + return GRPC_SECURITY_ERROR; + } + + c = gpr_malloc(sizeof(grpc_httpcli_ssl_channel_security_connector)); + memset(c, 0, sizeof(grpc_httpcli_ssl_channel_security_connector)); + + gpr_ref_init(&c->base.base.refcount, 1); + c->base.base.vtable = &httpcli_ssl_vtable; + if (secure_peer_name != NULL) { + c->secure_peer_name = gpr_strdup(secure_peer_name); + } + result = tsi_create_ssl_client_handshaker_factory( + NULL, 0, NULL, 0, pem_root_certs, pem_root_certs_size, NULL, NULL, NULL, + 0, &c->handshaker_factory); + if (result != TSI_OK) { + gpr_log(GPR_ERROR, "Handshaker factory creation failed with %s.", + tsi_result_to_string(result)); + httpcli_ssl_destroy(&c->base.base); + *sc = NULL; + return GRPC_SECURITY_ERROR; + } + c->base.do_handshake = httpcli_ssl_do_handshake; + *sc = &c->base; + return GRPC_SECURITY_OK; +} + +/* handshaker */ + +typedef struct { + void (*func)(grpc_exec_ctx *exec_ctx, void *arg, grpc_endpoint *endpoint); + void *arg; +} on_done_closure; + +static void on_secure_transport_setup_done(grpc_exec_ctx *exec_ctx, void *rp, + grpc_security_status status, + grpc_endpoint *secure_endpoint, + grpc_auth_context *auth_context) { + on_done_closure *c = rp; + if (status != GRPC_SECURITY_OK) { + gpr_log(GPR_ERROR, "Secure transport setup failed with error %d.", status); + c->func(exec_ctx, c->arg, NULL); + } else { + c->func(exec_ctx, c->arg, secure_endpoint); + } + gpr_free(c); +} + +static void ssl_handshake(grpc_exec_ctx *exec_ctx, void *arg, + grpc_endpoint *tcp, const char *host, + void (*on_done)(grpc_exec_ctx *exec_ctx, void *arg, + grpc_endpoint *endpoint)) { + grpc_channel_security_connector *sc = NULL; + const unsigned char *pem_root_certs = NULL; + on_done_closure *c = gpr_malloc(sizeof(*c)); + size_t pem_root_certs_size = grpc_get_default_ssl_roots(&pem_root_certs); + if (pem_root_certs == NULL || pem_root_certs_size == 0) { + gpr_log(GPR_ERROR, "Could not get default pem root certs."); + on_done(exec_ctx, arg, NULL); + gpr_free(c); + return; + } + c->func = on_done; + c->arg = arg; + GPR_ASSERT(httpcli_ssl_channel_security_connector_create( + pem_root_certs, pem_root_certs_size, host, &sc) == + GRPC_SECURITY_OK); + grpc_channel_security_connector_do_handshake( + exec_ctx, sc, tcp, on_secure_transport_setup_done, c); + GRPC_SECURITY_CONNECTOR_UNREF(&sc->base, "httpcli"); +} + +const grpc_httpcli_handshaker grpc_httpcli_ssl = {"https", ssl_handshake}; diff --git a/src/core/lib/http/parser.c b/src/core/lib/http/parser.c new file mode 100644 index 0000000000..5d4e304615 --- /dev/null +++ b/src/core/lib/http/parser.c @@ -0,0 +1,313 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/http/parser.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/useful.h> + +static char *buf2str(void *buffer, size_t length) { + char *out = gpr_malloc(length + 1); + memcpy(out, buffer, length); + out[length] = 0; + return out; +} + +static int handle_response_line(grpc_http_parser *parser) { + uint8_t *beg = parser->cur_line; + uint8_t *cur = beg; + uint8_t *end = beg + parser->cur_line_length; + + if (cur == end || *cur++ != 'H') goto error; + if (cur == end || *cur++ != 'T') goto error; + if (cur == end || *cur++ != 'T') goto error; + if (cur == end || *cur++ != 'P') goto error; + if (cur == end || *cur++ != '/') goto error; + if (cur == end || *cur++ != '1') goto error; + if (cur == end || *cur++ != '.') goto error; + if (cur == end || *cur < '0' || *cur++ > '1') goto error; + if (cur == end || *cur++ != ' ') goto error; + if (cur == end || *cur < '1' || *cur++ > '9') goto error; + if (cur == end || *cur < '0' || *cur++ > '9') goto error; + if (cur == end || *cur < '0' || *cur++ > '9') goto error; + parser->http.response.status = + (cur[-3] - '0') * 100 + (cur[-2] - '0') * 10 + (cur[-1] - '0'); + if (cur == end || *cur++ != ' ') goto error; + + /* we don't really care about the status code message */ + + return 1; + +error: + gpr_log(GPR_ERROR, "Failed parsing response line"); + return 0; +} + +static int handle_request_line(grpc_http_parser *parser) { + uint8_t *beg = parser->cur_line; + uint8_t *cur = beg; + uint8_t *end = beg + parser->cur_line_length; + uint8_t vers_major = 0; + uint8_t vers_minor = 0; + + while (cur != end && *cur++ != ' ') + ; + if (cur == end) goto error; + parser->http.request.method = buf2str(beg, (size_t)(cur - beg - 1)); + + beg = cur; + while (cur != end && *cur++ != ' ') + ; + if (cur == end) goto error; + parser->http.request.path = buf2str(beg, (size_t)(cur - beg - 1)); + + if (cur == end || *cur++ != 'H') goto error; + if (cur == end || *cur++ != 'T') goto error; + if (cur == end || *cur++ != 'T') goto error; + if (cur == end || *cur++ != 'P') goto error; + if (cur == end || *cur++ != '/') goto error; + vers_major = (uint8_t)(*cur++ - '1' + 1); + ++cur; + if (cur == end) goto error; + vers_minor = (uint8_t)(*cur++ - '1' + 1); + + if (vers_major == 1) { + if (vers_minor == 0) { + parser->http.request.version = GRPC_HTTP_HTTP10; + } else if (vers_minor == 1) { + parser->http.request.version = GRPC_HTTP_HTTP11; + } else { + goto error; + } + } else if (vers_major == 2) { + if (vers_minor == 0) { + parser->http.request.version = GRPC_HTTP_HTTP20; + } else { + goto error; + } + } else { + goto error; + } + + return 1; + +error: + gpr_log(GPR_ERROR, "Failed parsing request line"); + return 0; +} + +static int handle_first_line(grpc_http_parser *parser) { + if (parser->cur_line[0] == 'H') { + parser->type = GRPC_HTTP_RESPONSE; + return handle_response_line(parser); + } else { + parser->type = GRPC_HTTP_REQUEST; + return handle_request_line(parser); + } +} + +static int add_header(grpc_http_parser *parser) { + uint8_t *beg = parser->cur_line; + uint8_t *cur = beg; + uint8_t *end = beg + parser->cur_line_length; + size_t *hdr_count = NULL; + grpc_http_header **hdrs = NULL; + grpc_http_header hdr = {NULL, NULL}; + + GPR_ASSERT(cur != end); + + if (*cur == ' ' || *cur == '\t') { + gpr_log(GPR_ERROR, "Continued header lines not supported yet"); + goto error; + } + + while (cur != end && *cur != ':') { + cur++; + } + if (cur == end) { + gpr_log(GPR_ERROR, "Didn't find ':' in header string"); + goto error; + } + GPR_ASSERT(cur >= beg); + hdr.key = buf2str(beg, (size_t)(cur - beg)); + cur++; /* skip : */ + + while (cur != end && (*cur == ' ' || *cur == '\t')) { + cur++; + } + GPR_ASSERT(end - cur >= 2); + hdr.value = buf2str(cur, (size_t)(end - cur) - 2); + + if (parser->type == GRPC_HTTP_RESPONSE) { + hdr_count = &parser->http.response.hdr_count; + hdrs = &parser->http.response.hdrs; + } else if (parser->type == GRPC_HTTP_REQUEST) { + hdr_count = &parser->http.request.hdr_count; + hdrs = &parser->http.request.hdrs; + } else { + return 0; + } + + if (*hdr_count == parser->hdr_capacity) { + parser->hdr_capacity = + GPR_MAX(parser->hdr_capacity + 1, parser->hdr_capacity * 3 / 2); + *hdrs = gpr_realloc(*hdrs, parser->hdr_capacity * sizeof(**hdrs)); + } + (*hdrs)[(*hdr_count)++] = hdr; + return 1; + +error: + gpr_free(hdr.key); + gpr_free(hdr.value); + return 0; +} + +static int finish_line(grpc_http_parser *parser) { + switch (parser->state) { + case GRPC_HTTP_FIRST_LINE: + if (!handle_first_line(parser)) { + return 0; + } + parser->state = GRPC_HTTP_HEADERS; + break; + case GRPC_HTTP_HEADERS: + if (parser->cur_line_length == 2) { + parser->state = GRPC_HTTP_BODY; + break; + } + if (!add_header(parser)) { + return 0; + } + break; + case GRPC_HTTP_BODY: + GPR_UNREACHABLE_CODE(return 0); + } + + parser->cur_line_length = 0; + return 1; +} + +static int addbyte_body(grpc_http_parser *parser, uint8_t byte) { + size_t *body_length = NULL; + char **body = NULL; + + if (parser->type == GRPC_HTTP_RESPONSE) { + body_length = &parser->http.response.body_length; + body = &parser->http.response.body; + } else if (parser->type == GRPC_HTTP_REQUEST) { + body_length = &parser->http.request.body_length; + body = &parser->http.request.body; + } else { + return 0; + } + + if (*body_length == parser->body_capacity) { + parser->body_capacity = GPR_MAX(8, parser->body_capacity * 3 / 2); + *body = gpr_realloc((void *)*body, parser->body_capacity); + } + (*body)[*body_length] = (char)byte; + (*body_length)++; + + return 1; +} + +static int addbyte(grpc_http_parser *parser, uint8_t byte) { + switch (parser->state) { + case GRPC_HTTP_FIRST_LINE: + case GRPC_HTTP_HEADERS: + if (parser->cur_line_length >= GRPC_HTTP_PARSER_MAX_HEADER_LENGTH) { + gpr_log(GPR_ERROR, "HTTP client max line length (%d) exceeded", + GRPC_HTTP_PARSER_MAX_HEADER_LENGTH); + return 0; + } + parser->cur_line[parser->cur_line_length] = byte; + parser->cur_line_length++; + if (parser->cur_line_length >= 2 && + parser->cur_line[parser->cur_line_length - 2] == '\r' && + parser->cur_line[parser->cur_line_length - 1] == '\n') { + return finish_line(parser); + } else { + return 1; + } + GPR_UNREACHABLE_CODE(return 0); + case GRPC_HTTP_BODY: + return addbyte_body(parser, byte); + } + GPR_UNREACHABLE_CODE(return 0); +} + +void grpc_http_parser_init(grpc_http_parser *parser) { + memset(parser, 0, sizeof(*parser)); + parser->state = GRPC_HTTP_FIRST_LINE; + parser->type = GRPC_HTTP_UNKNOWN; +} + +void grpc_http_parser_destroy(grpc_http_parser *parser) { + size_t i; + if (parser->type == GRPC_HTTP_RESPONSE) { + gpr_free(parser->http.response.body); + for (i = 0; i < parser->http.response.hdr_count; i++) { + gpr_free(parser->http.response.hdrs[i].key); + gpr_free(parser->http.response.hdrs[i].value); + } + gpr_free(parser->http.response.hdrs); + } else if (parser->type == GRPC_HTTP_REQUEST) { + gpr_free(parser->http.request.body); + for (i = 0; i < parser->http.request.hdr_count; i++) { + gpr_free(parser->http.request.hdrs[i].key); + gpr_free(parser->http.request.hdrs[i].value); + } + gpr_free(parser->http.request.hdrs); + gpr_free(parser->http.request.method); + gpr_free(parser->http.request.path); + } +} + +int grpc_http_parser_parse(grpc_http_parser *parser, gpr_slice slice) { + size_t i; + + for (i = 0; i < GPR_SLICE_LENGTH(slice); i++) { + if (!addbyte(parser, GPR_SLICE_START_PTR(slice)[i])) { + return 0; + } + } + + return 1; +} + +int grpc_http_parser_eof(grpc_http_parser *parser) { + return parser->state == GRPC_HTTP_BODY; +} diff --git a/src/core/lib/http/parser.h b/src/core/lib/http/parser.h new file mode 100644 index 0000000000..6a72174aa6 --- /dev/null +++ b/src/core/lib/http/parser.h @@ -0,0 +1,116 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_HTTP_PARSER_H +#define GRPC_CORE_LIB_HTTP_PARSER_H + +#include <grpc/support/port_platform.h> +#include <grpc/support/slice.h> + +/* Maximum length of a header string of the form 'Key: Value\r\n' */ +#define GRPC_HTTP_PARSER_MAX_HEADER_LENGTH 4096 + +/* A single header to be passed in a request */ +typedef struct grpc_http_header { + char *key; + char *value; +} grpc_http_header; + +typedef enum { + GRPC_HTTP_FIRST_LINE, + GRPC_HTTP_HEADERS, + GRPC_HTTP_BODY +} grpc_http_parser_state; + +typedef enum { + GRPC_HTTP_HTTP10, + GRPC_HTTP_HTTP11, + GRPC_HTTP_HTTP20, +} grpc_http_version; + +typedef enum { + GRPC_HTTP_RESPONSE, + GRPC_HTTP_REQUEST, + GRPC_HTTP_UNKNOWN +} grpc_http_type; + +/* A request */ +typedef struct grpc_http_request { + /* Method of the request (e.g. GET, POST) */ + char *method; + /* The path of the resource to fetch */ + char *path; + /* HTTP version to use */ + grpc_http_version version; + /* Headers attached to the request */ + size_t hdr_count; + grpc_http_header *hdrs; + /* Body: length and contents; contents are NOT null-terminated */ + size_t body_length; + char *body; +} grpc_http_request; + +/* A response */ +typedef struct grpc_http_response { + /* HTTP status code */ + int status; + /* Headers: count and key/values */ + size_t hdr_count; + grpc_http_header *hdrs; + /* Body: length and contents; contents are NOT null-terminated */ + size_t body_length; + char *body; +} grpc_http_response; + +typedef struct { + grpc_http_parser_state state; + grpc_http_type type; + + union { + grpc_http_response response; + grpc_http_request request; + } http; + size_t body_capacity; + size_t hdr_capacity; + + uint8_t cur_line[GRPC_HTTP_PARSER_MAX_HEADER_LENGTH]; + size_t cur_line_length; +} grpc_http_parser; + +void grpc_http_parser_init(grpc_http_parser *parser); +void grpc_http_parser_destroy(grpc_http_parser *parser); + +int grpc_http_parser_parse(grpc_http_parser *parser, gpr_slice slice); +int grpc_http_parser_eof(grpc_http_parser *parser); + +#endif /* GRPC_CORE_LIB_HTTP_PARSER_H */ diff --git a/src/core/lib/iomgr/closure.c b/src/core/lib/iomgr/closure.c new file mode 100644 index 0000000000..724ebc284a --- /dev/null +++ b/src/core/lib/iomgr/closure.c @@ -0,0 +1,98 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/iomgr/closure.h" + +#include <grpc/support/alloc.h> + +void grpc_closure_init(grpc_closure *closure, grpc_iomgr_cb_func cb, + void *cb_arg) { + closure->cb = cb; + closure->cb_arg = cb_arg; + closure->final_data = 0; +} + +void grpc_closure_list_add(grpc_closure_list *closure_list, + grpc_closure *closure, bool success) { + if (closure == NULL) return; + closure->final_data = (success != 0); + if (closure_list->head == NULL) { + closure_list->head = closure; + } else { + closure_list->tail->final_data |= (uintptr_t)closure; + } + closure_list->tail = closure; +} + +bool grpc_closure_list_empty(grpc_closure_list closure_list) { + return closure_list.head == NULL; +} + +void grpc_closure_list_move(grpc_closure_list *src, grpc_closure_list *dst) { + if (src->head == NULL) { + return; + } + if (dst->head == NULL) { + *dst = *src; + } else { + dst->tail->final_data |= (uintptr_t)src->head; + dst->tail = src->tail; + } + src->head = src->tail = NULL; +} + +typedef struct { + grpc_iomgr_cb_func cb; + void *cb_arg; + grpc_closure wrapper; +} wrapped_closure; + +static void closure_wrapper(grpc_exec_ctx *exec_ctx, void *arg, bool success) { + wrapped_closure *wc = arg; + grpc_iomgr_cb_func cb = wc->cb; + void *cb_arg = wc->cb_arg; + gpr_free(wc); + cb(exec_ctx, cb_arg, success); +} + +grpc_closure *grpc_closure_create(grpc_iomgr_cb_func cb, void *cb_arg) { + wrapped_closure *wc = gpr_malloc(sizeof(*wc)); + wc->cb = cb; + wc->cb_arg = cb_arg; + grpc_closure_init(&wc->wrapper, closure_wrapper, wc); + return &wc->wrapper; +} + +grpc_closure *grpc_closure_next(grpc_closure *closure) { + return (grpc_closure *)(closure->final_data & ~(uintptr_t)1); +} diff --git a/src/core/lib/iomgr/closure.h b/src/core/lib/iomgr/closure.h new file mode 100644 index 0000000000..2597cf1706 --- /dev/null +++ b/src/core/lib/iomgr/closure.h @@ -0,0 +1,98 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_CLOSURE_H +#define GRPC_CORE_LIB_IOMGR_CLOSURE_H + +#include <grpc/support/port_platform.h> +#include <stdbool.h> + +struct grpc_closure; +typedef struct grpc_closure grpc_closure; + +/* forward declaration for exec_ctx.h */ +struct grpc_exec_ctx; +typedef struct grpc_exec_ctx grpc_exec_ctx; + +typedef struct grpc_closure_list { + grpc_closure *head; + grpc_closure *tail; +} grpc_closure_list; + +/** gRPC Callback definition. + * + * \param arg Arbitrary input. + * \param success An indication on the state of the iomgr. On false, cleanup + * actions should be taken (eg, shutdown). */ +typedef void (*grpc_iomgr_cb_func)(grpc_exec_ctx *exec_ctx, void *arg, + bool success); + +/** A closure over a grpc_iomgr_cb_func. */ +struct grpc_closure { + /** Bound callback. */ + grpc_iomgr_cb_func cb; + + /** Arguments to be passed to "cb". */ + void *cb_arg; + + /** Once enqueued, contains in the lower bit the success of the closure, + and in the upper bits the pointer to the next closure in the list. + Before enqueing for execution, this is usable for scratch data. */ + uintptr_t final_data; +}; + +/** Initializes \a closure with \a cb and \a cb_arg. */ +void grpc_closure_init(grpc_closure *closure, grpc_iomgr_cb_func cb, + void *cb_arg); + +/* Create a heap allocated closure: try to avoid except for very rare events */ +grpc_closure *grpc_closure_create(grpc_iomgr_cb_func cb, void *cb_arg); + +#define GRPC_CLOSURE_LIST_INIT \ + { NULL, NULL } + +/** add \a closure to the end of \a list and set \a closure's success to \a + * success */ +void grpc_closure_list_add(grpc_closure_list *list, grpc_closure *closure, + bool success); + +/** append all closures from \a src to \a dst and empty \a src. */ +void grpc_closure_list_move(grpc_closure_list *src, grpc_closure_list *dst); + +/** return whether \a list is empty. */ +bool grpc_closure_list_empty(grpc_closure_list list); + +/** return the next pointer for a queued closure list */ +grpc_closure *grpc_closure_next(grpc_closure *closure); + +#endif /* GRPC_CORE_LIB_IOMGR_CLOSURE_H */ diff --git a/src/core/lib/iomgr/endpoint.c b/src/core/lib/iomgr/endpoint.c new file mode 100644 index 0000000000..576b5a6e5c --- /dev/null +++ b/src/core/lib/iomgr/endpoint.c @@ -0,0 +1,67 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/iomgr/endpoint.h" + +void grpc_endpoint_read(grpc_exec_ctx* exec_ctx, grpc_endpoint* ep, + gpr_slice_buffer* slices, grpc_closure* cb) { + ep->vtable->read(exec_ctx, ep, slices, cb); +} + +void grpc_endpoint_write(grpc_exec_ctx* exec_ctx, grpc_endpoint* ep, + gpr_slice_buffer* slices, grpc_closure* cb) { + ep->vtable->write(exec_ctx, ep, slices, cb); +} + +void grpc_endpoint_add_to_pollset(grpc_exec_ctx* exec_ctx, grpc_endpoint* ep, + grpc_pollset* pollset) { + ep->vtable->add_to_pollset(exec_ctx, ep, pollset); +} + +void grpc_endpoint_add_to_pollset_set(grpc_exec_ctx* exec_ctx, + grpc_endpoint* ep, + grpc_pollset_set* pollset_set) { + ep->vtable->add_to_pollset_set(exec_ctx, ep, pollset_set); +} + +void grpc_endpoint_shutdown(grpc_exec_ctx* exec_ctx, grpc_endpoint* ep) { + ep->vtable->shutdown(exec_ctx, ep); +} + +void grpc_endpoint_destroy(grpc_exec_ctx* exec_ctx, grpc_endpoint* ep) { + ep->vtable->destroy(exec_ctx, ep); +} + +char* grpc_endpoint_get_peer(grpc_endpoint* ep) { + return ep->vtable->get_peer(ep); +} diff --git a/src/core/lib/iomgr/endpoint.h b/src/core/lib/iomgr/endpoint.h new file mode 100644 index 0000000000..918e705fbd --- /dev/null +++ b/src/core/lib/iomgr/endpoint.h @@ -0,0 +1,102 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_ENDPOINT_H +#define GRPC_CORE_LIB_IOMGR_ENDPOINT_H + +#include <grpc/support/slice.h> +#include <grpc/support/slice_buffer.h> +#include <grpc/support/time.h> +#include "src/core/lib/iomgr/pollset.h" +#include "src/core/lib/iomgr/pollset_set.h" + +/* An endpoint caps a streaming channel between two communicating processes. + Examples may be: a tcp socket, <stdin+stdout>, or some shared memory. */ + +typedef struct grpc_endpoint grpc_endpoint; +typedef struct grpc_endpoint_vtable grpc_endpoint_vtable; + +struct grpc_endpoint_vtable { + void (*read)(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, + gpr_slice_buffer *slices, grpc_closure *cb); + void (*write)(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, + gpr_slice_buffer *slices, grpc_closure *cb); + void (*add_to_pollset)(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, + grpc_pollset *pollset); + void (*add_to_pollset_set)(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, + grpc_pollset_set *pollset); + void (*shutdown)(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep); + void (*destroy)(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep); + char *(*get_peer)(grpc_endpoint *ep); +}; + +/* When data is available on the connection, calls the callback with slices. + Callback success indicates that the endpoint can accept more reads, failure + indicates the endpoint is closed. + Valid slices may be placed into \a slices even on callback success == 0. */ +void grpc_endpoint_read(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, + gpr_slice_buffer *slices, grpc_closure *cb); + +char *grpc_endpoint_get_peer(grpc_endpoint *ep); + +/* Write slices out to the socket. + + If the connection is ready for more data after the end of the call, it + returns GRPC_ENDPOINT_DONE. + Otherwise it returns GRPC_ENDPOINT_PENDING and calls cb when the + connection is ready for more data. + \a slices may be mutated at will by the endpoint until cb is called. + No guarantee is made to the content of slices after a write EXCEPT that + it is a valid slice buffer. + */ +void grpc_endpoint_write(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, + gpr_slice_buffer *slices, grpc_closure *cb); + +/* Causes any pending read/write callbacks to run immediately with + success==0 */ +void grpc_endpoint_shutdown(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep); +void grpc_endpoint_destroy(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep); + +/* Add an endpoint to a pollset, so that when the pollset is polled, events from + this endpoint are considered */ +void grpc_endpoint_add_to_pollset(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, + grpc_pollset *pollset); +void grpc_endpoint_add_to_pollset_set(grpc_exec_ctx *exec_ctx, + grpc_endpoint *ep, + grpc_pollset_set *pollset_set); + +struct grpc_endpoint { + const grpc_endpoint_vtable *vtable; +}; + +#endif /* GRPC_CORE_LIB_IOMGR_ENDPOINT_H */ diff --git a/src/core/lib/iomgr/endpoint_pair.h b/src/core/lib/iomgr/endpoint_pair.h new file mode 100644 index 0000000000..bef8bb3518 --- /dev/null +++ b/src/core/lib/iomgr/endpoint_pair.h @@ -0,0 +1,47 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_ENDPOINT_PAIR_H +#define GRPC_CORE_LIB_IOMGR_ENDPOINT_PAIR_H + +#include "src/core/lib/iomgr/endpoint.h" + +typedef struct { + grpc_endpoint *client; + grpc_endpoint *server; +} grpc_endpoint_pair; + +grpc_endpoint_pair grpc_iomgr_create_endpoint_pair(const char *name, + size_t read_slice_size); + +#endif /* GRPC_CORE_LIB_IOMGR_ENDPOINT_PAIR_H */ diff --git a/src/core/lib/iomgr/endpoint_pair_posix.c b/src/core/lib/iomgr/endpoint_pair_posix.c new file mode 100644 index 0000000000..e0ce47c773 --- /dev/null +++ b/src/core/lib/iomgr/endpoint_pair_posix.c @@ -0,0 +1,83 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_SOCKET + +#include "src/core/lib/iomgr/endpoint_pair.h" +#include "src/core/lib/iomgr/socket_utils_posix.h" +#include "src/core/lib/iomgr/unix_sockets_posix.h" + +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> +#include "src/core/lib/iomgr/tcp_posix.h" +#include "src/core/lib/support/string.h" + +static void create_sockets(int sv[2]) { + int flags; + grpc_create_socketpair_if_unix(sv); + flags = fcntl(sv[0], F_GETFL, 0); + GPR_ASSERT(fcntl(sv[0], F_SETFL, flags | O_NONBLOCK) == 0); + flags = fcntl(sv[1], F_GETFL, 0); + GPR_ASSERT(fcntl(sv[1], F_SETFL, flags | O_NONBLOCK) == 0); + GPR_ASSERT(grpc_set_socket_no_sigpipe_if_possible(sv[0])); + GPR_ASSERT(grpc_set_socket_no_sigpipe_if_possible(sv[1])); +} + +grpc_endpoint_pair grpc_iomgr_create_endpoint_pair(const char *name, + size_t read_slice_size) { + int sv[2]; + grpc_endpoint_pair p; + char *final_name; + create_sockets(sv); + + gpr_asprintf(&final_name, "%s:client", name); + p.client = grpc_tcp_create(grpc_fd_create(sv[1], final_name), read_slice_size, + "socketpair-server"); + gpr_free(final_name); + gpr_asprintf(&final_name, "%s:server", name); + p.server = grpc_tcp_create(grpc_fd_create(sv[0], final_name), read_slice_size, + "socketpair-client"); + gpr_free(final_name); + return p; +} + +#endif diff --git a/src/core/lib/iomgr/endpoint_pair_windows.c b/src/core/lib/iomgr/endpoint_pair_windows.c new file mode 100644 index 0000000000..cba18db81f --- /dev/null +++ b/src/core/lib/iomgr/endpoint_pair_windows.c @@ -0,0 +1,97 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WINSOCK_SOCKET +#include "src/core/lib/iomgr/endpoint_pair.h" +#include "src/core/lib/iomgr/sockaddr_utils.h" + +#include <errno.h> +#include <fcntl.h> +#include <string.h> + +#include <grpc/support/log.h> +#include "src/core/lib/iomgr/socket_windows.h" +#include "src/core/lib/iomgr/tcp_windows.h" + +static void create_sockets(SOCKET sv[2]) { + SOCKET svr_sock = INVALID_SOCKET; + SOCKET lst_sock = INVALID_SOCKET; + SOCKET cli_sock = INVALID_SOCKET; + SOCKADDR_IN addr; + int addr_len = sizeof(addr); + + lst_sock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, + WSA_FLAG_OVERLAPPED); + GPR_ASSERT(lst_sock != INVALID_SOCKET); + + memset(&addr, 0, sizeof(addr)); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_family = AF_INET; + GPR_ASSERT(bind(lst_sock, (struct sockaddr *)&addr, sizeof(addr)) != + SOCKET_ERROR); + GPR_ASSERT(listen(lst_sock, SOMAXCONN) != SOCKET_ERROR); + GPR_ASSERT(getsockname(lst_sock, (struct sockaddr *)&addr, &addr_len) != + SOCKET_ERROR); + + cli_sock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, + WSA_FLAG_OVERLAPPED); + GPR_ASSERT(cli_sock != INVALID_SOCKET); + + GPR_ASSERT(WSAConnect(cli_sock, (struct sockaddr *)&addr, addr_len, NULL, + NULL, NULL, NULL) == 0); + svr_sock = accept(lst_sock, (struct sockaddr *)&addr, &addr_len); + GPR_ASSERT(svr_sock != INVALID_SOCKET); + + closesocket(lst_sock); + grpc_tcp_prepare_socket(cli_sock); + grpc_tcp_prepare_socket(svr_sock); + + sv[1] = cli_sock; + sv[0] = svr_sock; +} + +grpc_endpoint_pair grpc_iomgr_create_endpoint_pair(const char *name, + size_t read_slice_size) { + SOCKET sv[2]; + grpc_endpoint_pair p; + create_sockets(sv); + p.client = grpc_tcp_create(grpc_winsocket_create(sv[1], "endpoint:client"), + "endpoint:server"); + p.server = grpc_tcp_create(grpc_winsocket_create(sv[0], "endpoint:server"), + "endpoint:client"); + return p; +} + +#endif diff --git a/src/core/lib/iomgr/exec_ctx.c b/src/core/lib/iomgr/exec_ctx.c new file mode 100644 index 0000000000..1ed6da623a --- /dev/null +++ b/src/core/lib/iomgr/exec_ctx.c @@ -0,0 +1,151 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/iomgr/exec_ctx.h" + +#include <grpc/support/log.h> +#include <grpc/support/sync.h> +#include <grpc/support/thd.h> + +#include "src/core/lib/profiling/timers.h" + +#ifndef GRPC_EXECUTION_CONTEXT_SANITIZER +bool grpc_exec_ctx_flush(grpc_exec_ctx *exec_ctx) { + bool did_something = 0; + GPR_TIMER_BEGIN("grpc_exec_ctx_flush", 0); + while (!grpc_closure_list_empty(exec_ctx->closure_list)) { + grpc_closure *c = exec_ctx->closure_list.head; + exec_ctx->closure_list.head = exec_ctx->closure_list.tail = NULL; + while (c != NULL) { + bool success = (bool)(c->final_data & 1); + grpc_closure *next = (grpc_closure *)(c->final_data & ~(uintptr_t)1); + did_something = true; + GPR_TIMER_BEGIN("grpc_exec_ctx_flush.cb", 0); + c->cb(exec_ctx, c->cb_arg, success); + GPR_TIMER_END("grpc_exec_ctx_flush.cb", 0); + c = next; + } + } + GPR_TIMER_END("grpc_exec_ctx_flush", 0); + return did_something; +} + +void grpc_exec_ctx_finish(grpc_exec_ctx *exec_ctx) { + grpc_exec_ctx_flush(exec_ctx); +} + +void grpc_exec_ctx_enqueue(grpc_exec_ctx *exec_ctx, grpc_closure *closure, + bool success, + grpc_workqueue *offload_target_or_null) { + GPR_ASSERT(offload_target_or_null == NULL); + grpc_closure_list_add(&exec_ctx->closure_list, closure, success); +} + +void grpc_exec_ctx_enqueue_list(grpc_exec_ctx *exec_ctx, + grpc_closure_list *list, + grpc_workqueue *offload_target_or_null) { + GPR_ASSERT(offload_target_or_null == NULL); + grpc_closure_list_move(list, &exec_ctx->closure_list); +} + +void grpc_exec_ctx_global_init(void) {} +void grpc_exec_ctx_global_shutdown(void) {} +#else +static gpr_mu g_mu; +static gpr_cv g_cv; +static int g_threads = 0; + +static void run_closure(void *arg) { + grpc_closure *closure = arg; + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + closure->cb(&exec_ctx, closure->cb_arg, (closure->final_data & 1) != 0); + grpc_exec_ctx_finish(&exec_ctx); + gpr_mu_lock(&g_mu); + if (--g_threads == 0) { + gpr_cv_signal(&g_cv); + } + gpr_mu_unlock(&g_mu); +} + +static void start_closure(grpc_closure *closure) { + gpr_thd_id id; + gpr_mu_lock(&g_mu); + g_threads++; + gpr_mu_unlock(&g_mu); + gpr_thd_new(&id, run_closure, closure, NULL); +} + +bool grpc_exec_ctx_flush(grpc_exec_ctx *exec_ctx) { return false; } + +void grpc_exec_ctx_finish(grpc_exec_ctx *exec_ctx) {} + +void grpc_exec_ctx_enqueue(grpc_exec_ctx *exec_ctx, grpc_closure *closure, + bool success, + grpc_workqueue *offload_target_or_null) { + GPR_ASSERT(offload_target_or_null == NULL); + if (closure == NULL) return; + closure->final_data = success; + start_closure(closure); +} + +void grpc_exec_ctx_enqueue_list(grpc_exec_ctx *exec_ctx, + grpc_closure_list *list, + grpc_workqueue *offload_target_or_null) { + GPR_ASSERT(offload_target_or_null == NULL); + if (list == NULL) return; + grpc_closure *p = list->head; + while (p) { + grpc_closure *start = p; + p = grpc_closure_next(start); + start_closure(start); + } + grpc_closure_list r = GRPC_CLOSURE_LIST_INIT; + *list = r; +} + +void grpc_exec_ctx_global_init(void) { + gpr_mu_init(&g_mu); + gpr_cv_init(&g_cv); +} + +void grpc_exec_ctx_global_shutdown(void) { + gpr_mu_lock(&g_mu); + while (g_threads != 0) { + gpr_cv_wait(&g_cv, &g_mu, gpr_inf_future(GPR_CLOCK_REALTIME)); + } + gpr_mu_unlock(&g_mu); + + gpr_mu_destroy(&g_mu); + gpr_cv_destroy(&g_cv); +} +#endif diff --git a/src/core/lib/iomgr/exec_ctx.h b/src/core/lib/iomgr/exec_ctx.h new file mode 100644 index 0000000000..e62ea2dedf --- /dev/null +++ b/src/core/lib/iomgr/exec_ctx.h @@ -0,0 +1,98 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_EXEC_CTX_H +#define GRPC_CORE_LIB_IOMGR_EXEC_CTX_H + +#include "src/core/lib/iomgr/closure.h" + +/* #define GRPC_EXECUTION_CONTEXT_SANITIZER 1 */ + +/** A workqueue represents a list of work to be executed asynchronously. + Forward declared here to avoid a circular dependency with workqueue.h. */ +struct grpc_workqueue; +typedef struct grpc_workqueue grpc_workqueue; + +#ifndef GRPC_EXECUTION_CONTEXT_SANITIZER +/** Execution context. + * A bag of data that collects information along a callstack. + * Generally created at public API entry points, and passed down as + * pointer to child functions that manipulate it. + * + * Specific responsibilities (this may grow in the future): + * - track a list of work that needs to be delayed until the top of the + * call stack (this provides a convenient mechanism to run callbacks + * without worrying about locking issues) + * + * CONVENTIONS: + * Instance of this must ALWAYS be constructed on the stack, never + * heap allocated. Instances and pointers to them must always be called + * exec_ctx. Instances are always passed as the first argument + * to a function that takes it, and always as a pointer (grpc_exec_ctx + * is never copied). + */ +struct grpc_exec_ctx { + grpc_closure_list closure_list; +}; + +#define GRPC_EXEC_CTX_INIT \ + { GRPC_CLOSURE_LIST_INIT } +#else +struct grpc_exec_ctx { + int unused; +}; +#define GRPC_EXEC_CTX_INIT \ + { 0 } +#endif + +/** Flush any work that has been enqueued onto this grpc_exec_ctx. + * Caller must guarantee that no interfering locks are held. + * Returns true if work was performed, false otherwise. */ +bool grpc_exec_ctx_flush(grpc_exec_ctx *exec_ctx); +/** Finish any pending work for a grpc_exec_ctx. Must be called before + * the instance is destroyed, or work may be lost. */ +void grpc_exec_ctx_finish(grpc_exec_ctx *exec_ctx); +/** Add a closure to be executed at the next flush/finish point */ +void grpc_exec_ctx_enqueue(grpc_exec_ctx *exec_ctx, grpc_closure *closure, + bool success, + grpc_workqueue *offload_target_or_null); +/** Add a list of closures to be executed at the next flush/finish point. + * Leaves \a list empty. */ +void grpc_exec_ctx_enqueue_list(grpc_exec_ctx *exec_ctx, + grpc_closure_list *list, + grpc_workqueue *offload_target_or_null); + +void grpc_exec_ctx_global_init(void); +void grpc_exec_ctx_global_shutdown(void); + +#endif /* GRPC_CORE_LIB_IOMGR_EXEC_CTX_H */ diff --git a/src/core/lib/iomgr/executor.c b/src/core/lib/iomgr/executor.c new file mode 100644 index 0000000000..42a9db3cbb --- /dev/null +++ b/src/core/lib/iomgr/executor.c @@ -0,0 +1,143 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/iomgr/executor.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/sync.h> +#include <grpc/support/thd.h> +#include "src/core/lib/iomgr/exec_ctx.h" + +typedef struct grpc_executor_data { + int busy; /**< is the thread currently running? */ + int shutting_down; /**< has \a grpc_shutdown() been invoked? */ + int pending_join; /**< has the thread finished but not been joined? */ + grpc_closure_list closures; /**< collection of pending work */ + gpr_thd_id tid; /**< thread id of the thread, only valid if \a busy or \a + pending_join are true */ + gpr_thd_options options; + gpr_mu mu; +} grpc_executor; + +static grpc_executor g_executor; + +void grpc_executor_init() { + memset(&g_executor, 0, sizeof(grpc_executor)); + gpr_mu_init(&g_executor.mu); + g_executor.options = gpr_thd_options_default(); + gpr_thd_options_set_joinable(&g_executor.options); +} + +/* thread body */ +static void closure_exec_thread_func(void *ignored) { + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + while (1) { + gpr_mu_lock(&g_executor.mu); + if (g_executor.shutting_down != 0) { + gpr_mu_unlock(&g_executor.mu); + break; + } + if (grpc_closure_list_empty(g_executor.closures)) { + /* no more work, time to die */ + GPR_ASSERT(g_executor.busy == 1); + g_executor.busy = 0; + gpr_mu_unlock(&g_executor.mu); + break; + } else { + grpc_exec_ctx_enqueue_list(&exec_ctx, &g_executor.closures, NULL); + } + gpr_mu_unlock(&g_executor.mu); + grpc_exec_ctx_flush(&exec_ctx); + } + grpc_exec_ctx_finish(&exec_ctx); +} + +/* Spawn the thread if new work has arrived a no thread is up */ +static void maybe_spawn_locked() { + if (grpc_closure_list_empty(g_executor.closures) == 1) { + return; + } + if (g_executor.shutting_down == 1) { + return; + } + + if (g_executor.busy != 0) { + /* Thread still working. New work will be picked up by already running + * thread. Not spawning anything. */ + return; + } else if (g_executor.pending_join != 0) { + /* Pickup the remains of the previous incarnations of the thread. */ + gpr_thd_join(g_executor.tid); + g_executor.pending_join = 0; + } + + /* All previous instances of the thread should have been joined at this point. + * Spawn time! */ + g_executor.busy = 1; + gpr_thd_new(&g_executor.tid, closure_exec_thread_func, NULL, + &g_executor.options); + g_executor.pending_join = 1; +} + +void grpc_executor_enqueue(grpc_closure *closure, bool success) { + gpr_mu_lock(&g_executor.mu); + if (g_executor.shutting_down == 0) { + grpc_closure_list_add(&g_executor.closures, closure, success); + maybe_spawn_locked(); + } + gpr_mu_unlock(&g_executor.mu); +} + +void grpc_executor_shutdown() { + int pending_join; + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + + gpr_mu_lock(&g_executor.mu); + pending_join = g_executor.pending_join; + g_executor.shutting_down = 1; + gpr_mu_unlock(&g_executor.mu); + /* we can release the lock at this point despite the access to the closure + * list below because we aren't accepting new work */ + + /* Execute pending callbacks, some may be performing cleanups */ + grpc_exec_ctx_enqueue_list(&exec_ctx, &g_executor.closures, NULL); + grpc_exec_ctx_finish(&exec_ctx); + GPR_ASSERT(grpc_closure_list_empty(g_executor.closures)); + if (pending_join) { + gpr_thd_join(g_executor.tid); + } + gpr_mu_destroy(&g_executor.mu); +} diff --git a/src/core/lib/iomgr/executor.h b/src/core/lib/iomgr/executor.h new file mode 100644 index 0000000000..f1871416a0 --- /dev/null +++ b/src/core/lib/iomgr/executor.h @@ -0,0 +1,53 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_EXECUTOR_H +#define GRPC_CORE_LIB_IOMGR_EXECUTOR_H + +#include "src/core/lib/iomgr/closure.h" + +/** Initialize the global executor. + * + * This mechanism is meant to outsource work (grpc_closure instances) to a + * thread, for those cases where blocking isn't an option but there isn't a + * non-blocking solution available. */ +void grpc_executor_init(); + +/** Enqueue \a closure for its eventual execution of \a f(arg) on a separate + * thread */ +void grpc_executor_enqueue(grpc_closure *closure, bool success); + +/** Shutdown the executor, running all pending work as part of the call */ +void grpc_executor_shutdown(); + +#endif /* GRPC_CORE_LIB_IOMGR_EXECUTOR_H */ diff --git a/src/core/lib/iomgr/fd_posix.c b/src/core/lib/iomgr/fd_posix.c new file mode 100644 index 0000000000..72c924bdcb --- /dev/null +++ b/src/core/lib/iomgr/fd_posix.c @@ -0,0 +1,454 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_SOCKET + +#include "src/core/lib/iomgr/fd_posix.h" + +#include <assert.h> +#include <sys/socket.h> +#include <unistd.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> +#include <grpc/support/useful.h> + +#include "src/core/lib/iomgr/pollset_posix.h" + +#define CLOSURE_NOT_READY ((grpc_closure *)0) +#define CLOSURE_READY ((grpc_closure *)1) + +/* We need to keep a freelist not because of any concerns of malloc performance + * but instead so that implementations with multiple threads in (for example) + * epoll_wait deal with the race between pollset removal and incoming poll + * notifications. + * + * The problem is that the poller ultimately holds a reference to this + * object, so it is very difficult to know when is safe to free it, at least + * without some expensive synchronization. + * + * If we keep the object freelisted, in the worst case losing this race just + * becomes a spurious read notification on a reused fd. + */ +/* TODO(klempner): We could use some form of polling generation count to know + * when these are safe to free. */ +/* TODO(klempner): Consider disabling freelisting if we don't have multiple + * threads in poll on the same fd */ +/* TODO(klempner): Batch these allocations to reduce fragmentation */ +static grpc_fd *fd_freelist = NULL; +static gpr_mu fd_freelist_mu; + +static void freelist_fd(grpc_fd *fd) { + gpr_mu_lock(&fd_freelist_mu); + fd->freelist_next = fd_freelist; + fd_freelist = fd; + grpc_iomgr_unregister_object(&fd->iomgr_object); + gpr_mu_unlock(&fd_freelist_mu); +} + +static grpc_fd *alloc_fd(int fd) { + grpc_fd *r = NULL; + gpr_mu_lock(&fd_freelist_mu); + if (fd_freelist != NULL) { + r = fd_freelist; + fd_freelist = fd_freelist->freelist_next; + } + gpr_mu_unlock(&fd_freelist_mu); + if (r == NULL) { + r = gpr_malloc(sizeof(grpc_fd)); + gpr_mu_init(&r->mu); + } + + gpr_mu_lock(&r->mu); + r->shutdown = 0; + r->read_closure = CLOSURE_NOT_READY; + r->write_closure = CLOSURE_NOT_READY; + r->fd = fd; + r->inactive_watcher_root.next = r->inactive_watcher_root.prev = + &r->inactive_watcher_root; + r->freelist_next = NULL; + r->read_watcher = r->write_watcher = NULL; + r->on_done_closure = NULL; + r->closed = 0; + r->released = 0; + gpr_atm_rel_store(&r->refst, 1); + gpr_mu_unlock(&r->mu); + + return r; +} + +static void destroy(grpc_fd *fd) { + gpr_mu_destroy(&fd->mu); + gpr_free(fd); +} + +#ifdef GRPC_FD_REF_COUNT_DEBUG +#define REF_BY(fd, n, reason) ref_by(fd, n, reason, __FILE__, __LINE__) +#define UNREF_BY(fd, n, reason) unref_by(fd, n, reason, __FILE__, __LINE__) +static void ref_by(grpc_fd *fd, int n, const char *reason, const char *file, + int line) { + gpr_log(GPR_DEBUG, "FD %d %p ref %d %d -> %d [%s; %s:%d]", fd->fd, fd, n, + gpr_atm_no_barrier_load(&fd->refst), + gpr_atm_no_barrier_load(&fd->refst) + n, reason, file, line); +#else +#define REF_BY(fd, n, reason) ref_by(fd, n) +#define UNREF_BY(fd, n, reason) unref_by(fd, n) +static void ref_by(grpc_fd *fd, int n) { +#endif + GPR_ASSERT(gpr_atm_no_barrier_fetch_add(&fd->refst, n) > 0); +} + +#ifdef GRPC_FD_REF_COUNT_DEBUG +static void unref_by(grpc_fd *fd, int n, const char *reason, const char *file, + int line) { + gpr_atm old; + gpr_log(GPR_DEBUG, "FD %d %p unref %d %d -> %d [%s; %s:%d]", fd->fd, fd, n, + gpr_atm_no_barrier_load(&fd->refst), + gpr_atm_no_barrier_load(&fd->refst) - n, reason, file, line); +#else +static void unref_by(grpc_fd *fd, int n) { + gpr_atm old; +#endif + old = gpr_atm_full_fetch_add(&fd->refst, -n); + if (old == n) { + freelist_fd(fd); + } else { + GPR_ASSERT(old > n); + } +} + +void grpc_fd_global_init(void) { gpr_mu_init(&fd_freelist_mu); } + +void grpc_fd_global_shutdown(void) { + gpr_mu_lock(&fd_freelist_mu); + gpr_mu_unlock(&fd_freelist_mu); + while (fd_freelist != NULL) { + grpc_fd *fd = fd_freelist; + fd_freelist = fd_freelist->freelist_next; + destroy(fd); + } + gpr_mu_destroy(&fd_freelist_mu); +} + +grpc_fd *grpc_fd_create(int fd, const char *name) { + grpc_fd *r = alloc_fd(fd); + char *name2; + gpr_asprintf(&name2, "%s fd=%d", name, fd); + grpc_iomgr_register_object(&r->iomgr_object, name2); + gpr_free(name2); +#ifdef GRPC_FD_REF_COUNT_DEBUG + gpr_log(GPR_DEBUG, "FD %d %p create %s", fd, r, name); +#endif + return r; +} + +int grpc_fd_is_orphaned(grpc_fd *fd) { + return (gpr_atm_acq_load(&fd->refst) & 1) == 0; +} + +static void pollset_kick_locked(grpc_fd_watcher *watcher) { + gpr_mu_lock(&watcher->pollset->mu); + GPR_ASSERT(watcher->worker); + grpc_pollset_kick_ext(watcher->pollset, watcher->worker, + GRPC_POLLSET_REEVALUATE_POLLING_ON_WAKEUP); + gpr_mu_unlock(&watcher->pollset->mu); +} + +static void maybe_wake_one_watcher_locked(grpc_fd *fd) { + if (fd->inactive_watcher_root.next != &fd->inactive_watcher_root) { + pollset_kick_locked(fd->inactive_watcher_root.next); + } else if (fd->read_watcher) { + pollset_kick_locked(fd->read_watcher); + } else if (fd->write_watcher) { + pollset_kick_locked(fd->write_watcher); + } +} + +static void wake_all_watchers_locked(grpc_fd *fd) { + grpc_fd_watcher *watcher; + for (watcher = fd->inactive_watcher_root.next; + watcher != &fd->inactive_watcher_root; watcher = watcher->next) { + pollset_kick_locked(watcher); + } + if (fd->read_watcher) { + pollset_kick_locked(fd->read_watcher); + } + if (fd->write_watcher && fd->write_watcher != fd->read_watcher) { + pollset_kick_locked(fd->write_watcher); + } +} + +static int has_watchers(grpc_fd *fd) { + return fd->read_watcher != NULL || fd->write_watcher != NULL || + fd->inactive_watcher_root.next != &fd->inactive_watcher_root; +} + +static void close_fd_locked(grpc_exec_ctx *exec_ctx, grpc_fd *fd) { + fd->closed = 1; + if (!fd->released) { + close(fd->fd); + } else { + grpc_remove_fd_from_all_epoll_sets(fd->fd); + } + grpc_exec_ctx_enqueue(exec_ctx, fd->on_done_closure, true, NULL); +} + +int grpc_fd_wrapped_fd(grpc_fd *fd) { + if (fd->released || fd->closed) { + return -1; + } else { + return fd->fd; + } +} + +void grpc_fd_orphan(grpc_exec_ctx *exec_ctx, grpc_fd *fd, grpc_closure *on_done, + int *release_fd, const char *reason) { + fd->on_done_closure = on_done; + fd->released = release_fd != NULL; + if (!fd->released) { + shutdown(fd->fd, SHUT_RDWR); + } else { + *release_fd = fd->fd; + } + gpr_mu_lock(&fd->mu); + REF_BY(fd, 1, reason); /* remove active status, but keep referenced */ + if (!has_watchers(fd)) { + close_fd_locked(exec_ctx, fd); + } else { + wake_all_watchers_locked(fd); + } + gpr_mu_unlock(&fd->mu); + UNREF_BY(fd, 2, reason); /* drop the reference */ +} + +/* increment refcount by two to avoid changing the orphan bit */ +#ifdef GRPC_FD_REF_COUNT_DEBUG +void grpc_fd_ref(grpc_fd *fd, const char *reason, const char *file, int line) { + ref_by(fd, 2, reason, file, line); +} + +void grpc_fd_unref(grpc_fd *fd, const char *reason, const char *file, + int line) { + unref_by(fd, 2, reason, file, line); +} +#else +void grpc_fd_ref(grpc_fd *fd) { ref_by(fd, 2); } + +void grpc_fd_unref(grpc_fd *fd) { unref_by(fd, 2); } +#endif + +static void notify_on_locked(grpc_exec_ctx *exec_ctx, grpc_fd *fd, + grpc_closure **st, grpc_closure *closure) { + if (*st == CLOSURE_NOT_READY) { + /* not ready ==> switch to a waiting state by setting the closure */ + *st = closure; + } else if (*st == CLOSURE_READY) { + /* already ready ==> queue the closure to run immediately */ + *st = CLOSURE_NOT_READY; + grpc_exec_ctx_enqueue(exec_ctx, closure, !fd->shutdown, NULL); + maybe_wake_one_watcher_locked(fd); + } else { + /* upcallptr was set to a different closure. This is an error! */ + gpr_log(GPR_ERROR, + "User called a notify_on function with a previous callback still " + "pending"); + abort(); + } +} + +/* returns 1 if state becomes not ready */ +static int set_ready_locked(grpc_exec_ctx *exec_ctx, grpc_fd *fd, + grpc_closure **st) { + if (*st == CLOSURE_READY) { + /* duplicate ready ==> ignore */ + return 0; + } else if (*st == CLOSURE_NOT_READY) { + /* not ready, and not waiting ==> flag ready */ + *st = CLOSURE_READY; + return 0; + } else { + /* waiting ==> queue closure */ + grpc_exec_ctx_enqueue(exec_ctx, *st, !fd->shutdown, NULL); + *st = CLOSURE_NOT_READY; + return 1; + } +} + +static void set_ready(grpc_exec_ctx *exec_ctx, grpc_fd *fd, grpc_closure **st) { + /* only one set_ready can be active at once (but there may be a racing + notify_on) */ + gpr_mu_lock(&fd->mu); + set_ready_locked(exec_ctx, fd, st); + gpr_mu_unlock(&fd->mu); +} + +void grpc_fd_shutdown(grpc_exec_ctx *exec_ctx, grpc_fd *fd) { + gpr_mu_lock(&fd->mu); + GPR_ASSERT(!fd->shutdown); + fd->shutdown = 1; + set_ready_locked(exec_ctx, fd, &fd->read_closure); + set_ready_locked(exec_ctx, fd, &fd->write_closure); + gpr_mu_unlock(&fd->mu); +} + +void grpc_fd_notify_on_read(grpc_exec_ctx *exec_ctx, grpc_fd *fd, + grpc_closure *closure) { + gpr_mu_lock(&fd->mu); + notify_on_locked(exec_ctx, fd, &fd->read_closure, closure); + gpr_mu_unlock(&fd->mu); +} + +void grpc_fd_notify_on_write(grpc_exec_ctx *exec_ctx, grpc_fd *fd, + grpc_closure *closure) { + gpr_mu_lock(&fd->mu); + notify_on_locked(exec_ctx, fd, &fd->write_closure, closure); + gpr_mu_unlock(&fd->mu); +} + +uint32_t grpc_fd_begin_poll(grpc_fd *fd, grpc_pollset *pollset, + grpc_pollset_worker *worker, uint32_t read_mask, + uint32_t write_mask, grpc_fd_watcher *watcher) { + uint32_t mask = 0; + grpc_closure *cur; + int requested; + /* keep track of pollers that have requested our events, in case they change + */ + GRPC_FD_REF(fd, "poll"); + + gpr_mu_lock(&fd->mu); + + /* if we are shutdown, then don't add to the watcher set */ + if (fd->shutdown) { + watcher->fd = NULL; + watcher->pollset = NULL; + watcher->worker = NULL; + gpr_mu_unlock(&fd->mu); + GRPC_FD_UNREF(fd, "poll"); + return 0; + } + + /* if there is nobody polling for read, but we need to, then start doing so */ + cur = fd->read_closure; + requested = cur != CLOSURE_READY; + if (read_mask && fd->read_watcher == NULL && requested) { + fd->read_watcher = watcher; + mask |= read_mask; + } + /* if there is nobody polling for write, but we need to, then start doing so + */ + cur = fd->write_closure; + requested = cur != CLOSURE_READY; + if (write_mask && fd->write_watcher == NULL && requested) { + fd->write_watcher = watcher; + mask |= write_mask; + } + /* if not polling, remember this watcher in case we need someone to later */ + if (mask == 0 && worker != NULL) { + watcher->next = &fd->inactive_watcher_root; + watcher->prev = watcher->next->prev; + watcher->next->prev = watcher->prev->next = watcher; + } + watcher->pollset = pollset; + watcher->worker = worker; + watcher->fd = fd; + gpr_mu_unlock(&fd->mu); + + return mask; +} + +void grpc_fd_end_poll(grpc_exec_ctx *exec_ctx, grpc_fd_watcher *watcher, + int got_read, int got_write) { + int was_polling = 0; + int kick = 0; + grpc_fd *fd = watcher->fd; + + if (fd == NULL) { + return; + } + + gpr_mu_lock(&fd->mu); + + if (watcher == fd->read_watcher) { + /* remove read watcher, kick if we still need a read */ + was_polling = 1; + if (!got_read) { + kick = 1; + } + fd->read_watcher = NULL; + } + if (watcher == fd->write_watcher) { + /* remove write watcher, kick if we still need a write */ + was_polling = 1; + if (!got_write) { + kick = 1; + } + fd->write_watcher = NULL; + } + if (!was_polling && watcher->worker != NULL) { + /* remove from inactive list */ + watcher->next->prev = watcher->prev; + watcher->prev->next = watcher->next; + } + if (got_read) { + if (set_ready_locked(exec_ctx, fd, &fd->read_closure)) { + kick = 1; + } + } + if (got_write) { + if (set_ready_locked(exec_ctx, fd, &fd->write_closure)) { + kick = 1; + } + } + if (kick) { + maybe_wake_one_watcher_locked(fd); + } + if (grpc_fd_is_orphaned(fd) && !has_watchers(fd) && !fd->closed) { + close_fd_locked(exec_ctx, fd); + } + gpr_mu_unlock(&fd->mu); + + GRPC_FD_UNREF(fd, "poll"); +} + +void grpc_fd_become_readable(grpc_exec_ctx *exec_ctx, grpc_fd *fd) { + set_ready(exec_ctx, fd, &fd->read_closure); +} + +void grpc_fd_become_writable(grpc_exec_ctx *exec_ctx, grpc_fd *fd) { + set_ready(exec_ctx, fd, &fd->write_closure); +} + +#endif diff --git a/src/core/lib/iomgr/fd_posix.h b/src/core/lib/iomgr/fd_posix.h new file mode 100644 index 0000000000..69d09ef5e3 --- /dev/null +++ b/src/core/lib/iomgr/fd_posix.h @@ -0,0 +1,192 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_FD_POSIX_H +#define GRPC_CORE_LIB_IOMGR_FD_POSIX_H + +#include <grpc/support/atm.h> +#include <grpc/support/sync.h> +#include <grpc/support/time.h> +#include "src/core/lib/iomgr/iomgr_internal.h" +#include "src/core/lib/iomgr/pollset.h" + +typedef struct grpc_fd grpc_fd; + +typedef struct grpc_fd_watcher { + struct grpc_fd_watcher *next; + struct grpc_fd_watcher *prev; + grpc_pollset *pollset; + grpc_pollset_worker *worker; + grpc_fd *fd; +} grpc_fd_watcher; + +struct grpc_fd { + int fd; + /* refst format: + bit0: 1=active/0=orphaned + bit1-n: refcount + meaning that mostly we ref by two to avoid altering the orphaned bit, + and just unref by 1 when we're ready to flag the object as orphaned */ + gpr_atm refst; + + gpr_mu mu; + int shutdown; + int closed; + int released; + + /* The watcher list. + + The following watcher related fields are protected by watcher_mu. + + An fd_watcher is an ephemeral object created when an fd wants to + begin polling, and destroyed after the poll. + + It denotes the fd's interest in whether to read poll or write poll + or both or neither on this fd. + + If a watcher is asked to poll for reads or writes, the read_watcher + or write_watcher fields are set respectively. A watcher may be asked + to poll for both, in which case both fields will be set. + + read_watcher and write_watcher may be NULL if no watcher has been + asked to poll for reads or writes. + + If an fd_watcher is not asked to poll for reads or writes, it's added + to a linked list of inactive watchers, rooted at inactive_watcher_root. + If at a later time there becomes need of a poller to poll, one of + the inactive pollers may be kicked out of their poll loops to take + that responsibility. */ + grpc_fd_watcher inactive_watcher_root; + grpc_fd_watcher *read_watcher; + grpc_fd_watcher *write_watcher; + + grpc_closure *read_closure; + grpc_closure *write_closure; + + struct grpc_fd *freelist_next; + + grpc_closure *on_done_closure; + + grpc_iomgr_object iomgr_object; +}; + +/* Create a wrapped file descriptor. + Requires fd is a non-blocking file descriptor. + This takes ownership of closing fd. */ +grpc_fd *grpc_fd_create(int fd, const char *name); + +/* Return the wrapped fd, or -1 if it has been released or closed. */ +int grpc_fd_wrapped_fd(grpc_fd *fd); + +/* Releases fd to be asynchronously destroyed. + on_done is called when the underlying file descriptor is definitely close()d. + If on_done is NULL, no callback will be made. + If release_fd is not NULL, it's set to fd and fd will not be closed. + Requires: *fd initialized; no outstanding notify_on_read or + notify_on_write. + MUST NOT be called with a pollset lock taken */ +void grpc_fd_orphan(grpc_exec_ctx *exec_ctx, grpc_fd *fd, grpc_closure *on_done, + int *release_fd, const char *reason); + +/* Begin polling on an fd. + Registers that the given pollset is interested in this fd - so that if read + or writability interest changes, the pollset can be kicked to pick up that + new interest. + Return value is: + (fd_needs_read? read_mask : 0) | (fd_needs_write? write_mask : 0) + i.e. a combination of read_mask and write_mask determined by the fd's current + interest in said events. + Polling strategies that do not need to alter their behavior depending on the + fd's current interest (such as epoll) do not need to call this function. + MUST NOT be called with a pollset lock taken */ +uint32_t grpc_fd_begin_poll(grpc_fd *fd, grpc_pollset *pollset, + grpc_pollset_worker *worker, uint32_t read_mask, + uint32_t write_mask, grpc_fd_watcher *rec); +/* Complete polling previously started with grpc_fd_begin_poll + MUST NOT be called with a pollset lock taken + if got_read or got_write are 1, also does the become_{readable,writable} as + appropriate. */ +void grpc_fd_end_poll(grpc_exec_ctx *exec_ctx, grpc_fd_watcher *rec, + int got_read, int got_write); + +/* Return 1 if this fd is orphaned, 0 otherwise */ +int grpc_fd_is_orphaned(grpc_fd *fd); + +/* Cause any current callbacks to error out with GRPC_CALLBACK_CANCELLED. */ +void grpc_fd_shutdown(grpc_exec_ctx *exec_ctx, grpc_fd *fd); + +/* Register read interest, causing read_cb to be called once when fd becomes + readable, on deadline specified by deadline, or on shutdown triggered by + grpc_fd_shutdown. + read_cb will be called with read_cb_arg when *fd becomes readable. + read_cb is Called with status of GRPC_CALLBACK_SUCCESS if readable, + GRPC_CALLBACK_TIMED_OUT if the call timed out, + and CANCELLED if the call was cancelled. + + Requires:This method must not be called before the read_cb for any previous + call runs. Edge triggered events are used whenever they are supported by the + underlying platform. This means that users must drain fd in read_cb before + calling notify_on_read again. Users are also expected to handle spurious + events, i.e read_cb is called while nothing can be readable from fd */ +void grpc_fd_notify_on_read(grpc_exec_ctx *exec_ctx, grpc_fd *fd, + grpc_closure *closure); + +/* Exactly the same semantics as above, except based on writable events. */ +void grpc_fd_notify_on_write(grpc_exec_ctx *exec_ctx, grpc_fd *fd, + grpc_closure *closure); + +/* Notification from the poller to an fd that it has become readable or + writable. + If allow_synchronous_callback is 1, allow running the fd callback inline + in this callstack, otherwise register an asynchronous callback and return */ +void grpc_fd_become_readable(grpc_exec_ctx *exec_ctx, grpc_fd *fd); +void grpc_fd_become_writable(grpc_exec_ctx *exec_ctx, grpc_fd *fd); + +/* Reference counting for fds */ +/*#define GRPC_FD_REF_COUNT_DEBUG*/ +#ifdef GRPC_FD_REF_COUNT_DEBUG +void grpc_fd_ref(grpc_fd *fd, const char *reason, const char *file, int line); +void grpc_fd_unref(grpc_fd *fd, const char *reason, const char *file, int line); +#define GRPC_FD_REF(fd, reason) grpc_fd_ref(fd, reason, __FILE__, __LINE__) +#define GRPC_FD_UNREF(fd, reason) grpc_fd_unref(fd, reason, __FILE__, __LINE__) +#else +void grpc_fd_ref(grpc_fd *fd); +void grpc_fd_unref(grpc_fd *fd); +#define GRPC_FD_REF(fd, reason) grpc_fd_ref(fd) +#define GRPC_FD_UNREF(fd, reason) grpc_fd_unref(fd) +#endif + +void grpc_fd_global_init(void); +void grpc_fd_global_shutdown(void); + +#endif /* GRPC_CORE_LIB_IOMGR_FD_POSIX_H */ diff --git a/src/core/lib/iomgr/iocp_windows.c b/src/core/lib/iomgr/iocp_windows.c new file mode 100644 index 0000000000..682a32c0da --- /dev/null +++ b/src/core/lib/iomgr/iocp_windows.c @@ -0,0 +1,208 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WINSOCK_SOCKET + +#include <winsock2.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/log_win32.h> +#include <grpc/support/thd.h> + +#include "src/core/lib/iomgr/iocp_windows.h" +#include "src/core/lib/iomgr/iomgr_internal.h" +#include "src/core/lib/iomgr/socket_windows.h" +#include "src/core/lib/iomgr/timer.h" + +static ULONG g_iocp_kick_token; +static OVERLAPPED g_iocp_custom_overlap; + +static gpr_atm g_custom_events = 0; + +static HANDLE g_iocp; + +static DWORD deadline_to_millis_timeout(gpr_timespec deadline, + gpr_timespec now) { + gpr_timespec timeout; + static const int64_t max_spin_polling_us = 10; + if (gpr_time_cmp(deadline, gpr_inf_future(deadline.clock_type)) == 0) { + return INFINITE; + } + if (gpr_time_cmp(deadline, gpr_time_add(now, gpr_time_from_micros( + max_spin_polling_us, + GPR_TIMESPAN))) <= 0) { + return 0; + } + timeout = gpr_time_sub(deadline, now); + return (DWORD)gpr_time_to_millis(gpr_time_add( + timeout, gpr_time_from_nanos(GPR_NS_PER_MS - 1, GPR_TIMESPAN))); +} + +grpc_iocp_work_status grpc_iocp_work(grpc_exec_ctx *exec_ctx, + gpr_timespec deadline) { + BOOL success; + DWORD bytes = 0; + DWORD flags = 0; + ULONG_PTR completion_key; + LPOVERLAPPED overlapped; + grpc_winsocket *socket; + grpc_winsocket_callback_info *info; + grpc_closure *closure = NULL; + success = GetQueuedCompletionStatus( + g_iocp, &bytes, &completion_key, &overlapped, + deadline_to_millis_timeout(deadline, gpr_now(deadline.clock_type))); + if (success == 0 && overlapped == NULL) { + return GRPC_IOCP_WORK_TIMEOUT; + } + GPR_ASSERT(completion_key && overlapped); + if (overlapped == &g_iocp_custom_overlap) { + gpr_atm_full_fetch_add(&g_custom_events, -1); + if (completion_key == (ULONG_PTR)&g_iocp_kick_token) { + /* We were awoken from a kick. */ + return GRPC_IOCP_WORK_KICK; + } + gpr_log(GPR_ERROR, "Unknown custom completion key."); + abort(); + } + + socket = (grpc_winsocket *)completion_key; + if (overlapped == &socket->write_info.overlapped) { + info = &socket->write_info; + } else if (overlapped == &socket->read_info.overlapped) { + info = &socket->read_info; + } else { + gpr_log(GPR_ERROR, "Unknown IOCP operation"); + abort(); + } + success = WSAGetOverlappedResult(socket->socket, &info->overlapped, &bytes, + FALSE, &flags); + info->bytes_transfered = bytes; + info->wsa_error = success ? 0 : WSAGetLastError(); + GPR_ASSERT(overlapped == &info->overlapped); + GPR_ASSERT(!info->has_pending_iocp); + gpr_mu_lock(&socket->state_mu); + if (info->closure) { + closure = info->closure; + info->closure = NULL; + } else { + info->has_pending_iocp = 1; + } + gpr_mu_unlock(&socket->state_mu); + grpc_exec_ctx_enqueue(exec_ctx, closure, true, NULL); + return GRPC_IOCP_WORK_WORK; +} + +void grpc_iocp_init(void) { + g_iocp = + CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, (ULONG_PTR)NULL, 0); + GPR_ASSERT(g_iocp); +} + +void grpc_iocp_kick(void) { + BOOL success; + + gpr_atm_full_fetch_add(&g_custom_events, 1); + success = PostQueuedCompletionStatus(g_iocp, 0, (ULONG_PTR)&g_iocp_kick_token, + &g_iocp_custom_overlap); + GPR_ASSERT(success); +} + +void grpc_iocp_flush(void) { + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + grpc_iocp_work_status work_status; + + do { + work_status = grpc_iocp_work(&exec_ctx, gpr_inf_past(GPR_CLOCK_MONOTONIC)); + } while (work_status == GRPC_IOCP_WORK_KICK || + grpc_exec_ctx_flush(&exec_ctx)); +} + +void grpc_iocp_shutdown(void) { + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + while (gpr_atm_acq_load(&g_custom_events)) { + grpc_iocp_work(&exec_ctx, gpr_inf_future(GPR_CLOCK_MONOTONIC)); + grpc_exec_ctx_flush(&exec_ctx); + } + grpc_exec_ctx_finish(&exec_ctx); + GPR_ASSERT(CloseHandle(g_iocp)); +} + +void grpc_iocp_add_socket(grpc_winsocket *socket) { + HANDLE ret; + if (socket->added_to_iocp) return; + ret = CreateIoCompletionPort((HANDLE)socket->socket, g_iocp, + (uintptr_t)socket, 0); + if (!ret) { + char *utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, "Unable to add socket to iocp: %s", utf8_message); + gpr_free(utf8_message); + __debugbreak(); + abort(); + } + socket->added_to_iocp = 1; + GPR_ASSERT(ret == g_iocp); +} + +/* Calling notify_on_read or write means either of two things: + -) The IOCP already completed in the background, and we need to call + the callback now. + -) The IOCP hasn't completed yet, and we're queuing it for later. */ +static void socket_notify_on_iocp(grpc_exec_ctx *exec_ctx, + grpc_winsocket *socket, grpc_closure *closure, + grpc_winsocket_callback_info *info) { + GPR_ASSERT(info->closure == NULL); + gpr_mu_lock(&socket->state_mu); + if (info->has_pending_iocp) { + info->has_pending_iocp = 0; + grpc_exec_ctx_enqueue(exec_ctx, closure, true, NULL); + } else { + info->closure = closure; + } + gpr_mu_unlock(&socket->state_mu); +} + +void grpc_socket_notify_on_write(grpc_exec_ctx *exec_ctx, + grpc_winsocket *socket, + grpc_closure *closure) { + socket_notify_on_iocp(exec_ctx, socket, closure, &socket->write_info); +} + +void grpc_socket_notify_on_read(grpc_exec_ctx *exec_ctx, grpc_winsocket *socket, + grpc_closure *closure) { + socket_notify_on_iocp(exec_ctx, socket, closure, &socket->read_info); +} + +#endif /* GPR_WINSOCK_SOCKET */ diff --git a/src/core/lib/iomgr/iocp_windows.h b/src/core/lib/iomgr/iocp_windows.h new file mode 100644 index 0000000000..856c837fb4 --- /dev/null +++ b/src/core/lib/iomgr/iocp_windows.h @@ -0,0 +1,63 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_IOCP_WINDOWS_H +#define GRPC_CORE_LIB_IOMGR_IOCP_WINDOWS_H + +#include <grpc/support/sync.h> + +#include "src/core/lib/iomgr/socket_windows.h" + +typedef enum { + GRPC_IOCP_WORK_WORK, + GRPC_IOCP_WORK_TIMEOUT, + GRPC_IOCP_WORK_KICK +} grpc_iocp_work_status; + +grpc_iocp_work_status grpc_iocp_work(grpc_exec_ctx *exec_ctx, + gpr_timespec deadline); +void grpc_iocp_init(void); +void grpc_iocp_kick(void); +void grpc_iocp_flush(void); +void grpc_iocp_shutdown(void); +void grpc_iocp_add_socket(grpc_winsocket *); + +void grpc_socket_notify_on_write(grpc_exec_ctx *exec_ctx, + grpc_winsocket *winsocket, + grpc_closure *closure); + +void grpc_socket_notify_on_read(grpc_exec_ctx *exec_ctx, + grpc_winsocket *winsocket, + grpc_closure *closure); + +#endif /* GRPC_CORE_LIB_IOMGR_IOCP_WINDOWS_H */ diff --git a/src/core/lib/iomgr/iomgr.c b/src/core/lib/iomgr/iomgr.c new file mode 100644 index 0000000000..bb544c8280 --- /dev/null +++ b/src/core/lib/iomgr/iomgr.c @@ -0,0 +1,175 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/iomgr/iomgr.h" + +#include <stdlib.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> +#include <grpc/support/sync.h> +#include <grpc/support/thd.h> +#include <grpc/support/useful.h> + +#include "src/core/lib/iomgr/exec_ctx.h" +#include "src/core/lib/iomgr/iomgr_internal.h" +#include "src/core/lib/iomgr/timer.h" +#include "src/core/lib/support/env.h" +#include "src/core/lib/support/string.h" + +static gpr_mu g_mu; +static gpr_cv g_rcv; +static int g_shutdown; +static grpc_iomgr_object g_root_object; + +void grpc_iomgr_init(void) { + g_shutdown = 0; + gpr_mu_init(&g_mu); + gpr_cv_init(&g_rcv); + grpc_exec_ctx_global_init(); + grpc_timer_list_init(gpr_now(GPR_CLOCK_MONOTONIC)); + g_root_object.next = g_root_object.prev = &g_root_object; + g_root_object.name = "root"; + grpc_iomgr_platform_init(); + grpc_pollset_global_init(); +} + +static size_t count_objects(void) { + grpc_iomgr_object *obj; + size_t n = 0; + for (obj = g_root_object.next; obj != &g_root_object; obj = obj->next) { + n++; + } + return n; +} + +static void dump_objects(const char *kind) { + grpc_iomgr_object *obj; + for (obj = g_root_object.next; obj != &g_root_object; obj = obj->next) { + gpr_log(GPR_DEBUG, "%s OBJECT: %s %p", kind, obj->name, obj); + } +} + +void grpc_iomgr_shutdown(void) { + gpr_timespec shutdown_deadline = gpr_time_add( + gpr_now(GPR_CLOCK_REALTIME), gpr_time_from_seconds(10, GPR_TIMESPAN)); + gpr_timespec last_warning_time = gpr_now(GPR_CLOCK_REALTIME); + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + + grpc_iomgr_platform_flush(); + + gpr_mu_lock(&g_mu); + g_shutdown = 1; + while (g_root_object.next != &g_root_object) { + if (gpr_time_cmp( + gpr_time_sub(gpr_now(GPR_CLOCK_REALTIME), last_warning_time), + gpr_time_from_seconds(1, GPR_TIMESPAN)) >= 0) { + if (g_root_object.next != &g_root_object) { + gpr_log(GPR_DEBUG, "Waiting for %d iomgr objects to be destroyed", + count_objects()); + } + last_warning_time = gpr_now(GPR_CLOCK_REALTIME); + } + if (grpc_timer_check(&exec_ctx, gpr_inf_future(GPR_CLOCK_MONOTONIC), + NULL)) { + gpr_mu_unlock(&g_mu); + grpc_exec_ctx_flush(&exec_ctx); + gpr_mu_lock(&g_mu); + continue; + } + if (g_root_object.next != &g_root_object) { + gpr_timespec short_deadline = gpr_time_add( + gpr_now(GPR_CLOCK_REALTIME), gpr_time_from_millis(100, GPR_TIMESPAN)); + if (gpr_cv_wait(&g_rcv, &g_mu, short_deadline)) { + if (gpr_time_cmp(gpr_now(GPR_CLOCK_REALTIME), shutdown_deadline) > 0) { + if (g_root_object.next != &g_root_object) { + gpr_log(GPR_DEBUG, + "Failed to free %d iomgr objects before shutdown deadline: " + "memory leaks are likely", + count_objects()); + dump_objects("LEAKED"); + if (grpc_iomgr_abort_on_leaks()) { + abort(); + } + } + break; + } + } + } + } + gpr_mu_unlock(&g_mu); + + grpc_timer_list_shutdown(&exec_ctx); + grpc_exec_ctx_finish(&exec_ctx); + + /* ensure all threads have left g_mu */ + gpr_mu_lock(&g_mu); + gpr_mu_unlock(&g_mu); + + grpc_pollset_global_shutdown(); + grpc_iomgr_platform_shutdown(); + grpc_exec_ctx_global_shutdown(); + gpr_mu_destroy(&g_mu); + gpr_cv_destroy(&g_rcv); +} + +void grpc_iomgr_register_object(grpc_iomgr_object *obj, const char *name) { + obj->name = gpr_strdup(name); + gpr_mu_lock(&g_mu); + obj->next = &g_root_object; + obj->prev = g_root_object.prev; + obj->next->prev = obj->prev->next = obj; + gpr_mu_unlock(&g_mu); +} + +void grpc_iomgr_unregister_object(grpc_iomgr_object *obj) { + gpr_mu_lock(&g_mu); + obj->next->prev = obj->prev; + obj->prev->next = obj->next; + gpr_cv_signal(&g_rcv); + gpr_mu_unlock(&g_mu); + gpr_free(obj->name); +} + +bool grpc_iomgr_abort_on_leaks(void) { + char *env = gpr_getenv("GRPC_ABORT_ON_LEAKS"); + if (env == NULL) return false; + static const char *truthy[] = {"yes", "Yes", "YES", "true", + "True", "TRUE", "1"}; + for (size_t i = 0; i < GPR_ARRAY_SIZE(truthy); i++) { + if (0 == strcmp(env, truthy[i])) return true; + } + return false; +} diff --git a/src/core/lib/iomgr/iomgr.h b/src/core/lib/iomgr/iomgr.h new file mode 100644 index 0000000000..babf0a85b7 --- /dev/null +++ b/src/core/lib/iomgr/iomgr.h @@ -0,0 +1,43 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_IOMGR_H +#define GRPC_CORE_LIB_IOMGR_IOMGR_H + +/** Initializes the iomgr. */ +void grpc_iomgr_init(void); + +/** Signals the intention to shutdown the iomgr. */ +void grpc_iomgr_shutdown(void); + +#endif /* GRPC_CORE_LIB_IOMGR_IOMGR_H */ diff --git a/src/core/lib/iomgr/iomgr_internal.h b/src/core/lib/iomgr/iomgr_internal.h new file mode 100644 index 0000000000..0963630c61 --- /dev/null +++ b/src/core/lib/iomgr/iomgr_internal.h @@ -0,0 +1,62 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_IOMGR_INTERNAL_H +#define GRPC_CORE_LIB_IOMGR_IOMGR_INTERNAL_H + +#include <stdbool.h> + +#include <grpc/support/sync.h> +#include "src/core/lib/iomgr/iomgr.h" + +typedef struct grpc_iomgr_object { + char *name; + struct grpc_iomgr_object *next; + struct grpc_iomgr_object *prev; +} grpc_iomgr_object; + +void grpc_pollset_global_init(void); +void grpc_pollset_global_shutdown(void); + +void grpc_iomgr_register_object(grpc_iomgr_object *obj, const char *name); +void grpc_iomgr_unregister_object(grpc_iomgr_object *obj); + +void grpc_iomgr_platform_init(void); +/** flush any globally queued work from iomgr */ +void grpc_iomgr_platform_flush(void); +/** tear down all platform specific global iomgr structures */ +void grpc_iomgr_platform_shutdown(void); + +bool grpc_iomgr_abort_on_leaks(void); + +#endif /* GRPC_CORE_LIB_IOMGR_IOMGR_INTERNAL_H */ diff --git a/src/core/lib/iomgr/iomgr_posix.c b/src/core/lib/iomgr/iomgr_posix.c new file mode 100644 index 0000000000..e4990f7bce --- /dev/null +++ b/src/core/lib/iomgr/iomgr_posix.c @@ -0,0 +1,52 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_SOCKET + +#include "src/core/lib/debug/trace.h" +#include "src/core/lib/iomgr/fd_posix.h" +#include "src/core/lib/iomgr/iomgr_posix.h" +#include "src/core/lib/iomgr/tcp_posix.h" + +void grpc_iomgr_platform_init(void) { + grpc_fd_global_init(); + grpc_register_tracer("tcp", &grpc_tcp_trace); +} + +void grpc_iomgr_platform_flush(void) {} + +void grpc_iomgr_platform_shutdown(void) { grpc_fd_global_shutdown(); } + +#endif /* GRPC_POSIX_SOCKET */ diff --git a/src/core/lib/iomgr/iomgr_posix.h b/src/core/lib/iomgr/iomgr_posix.h new file mode 100644 index 0000000000..6a8996e403 --- /dev/null +++ b/src/core/lib/iomgr/iomgr_posix.h @@ -0,0 +1,39 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_IOMGR_POSIX_H +#define GRPC_CORE_LIB_IOMGR_IOMGR_POSIX_H + +#include "src/core/lib/iomgr/iomgr_internal.h" + +#endif /* GRPC_CORE_LIB_IOMGR_IOMGR_POSIX_H */ diff --git a/src/core/lib/iomgr/iomgr_windows.c b/src/core/lib/iomgr/iomgr_windows.c new file mode 100644 index 0000000000..af7e616394 --- /dev/null +++ b/src/core/lib/iomgr/iomgr_windows.c @@ -0,0 +1,73 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WINSOCK_SOCKET + +#include "src/core/lib/iomgr/sockaddr_win32.h" + +#include <grpc/support/log.h> + +#include "src/core/lib/iomgr/iocp_windows.h" +#include "src/core/lib/iomgr/iomgr.h" +#include "src/core/lib/iomgr/socket_windows.h" + +/* Windows' io manager is going to be fully designed using IO completion + ports. All of what we're doing here is basically make sure that + Windows sockets are initialized in and out. */ + +static void winsock_init(void) { + WSADATA wsaData; + int status = WSAStartup(MAKEWORD(2, 0), &wsaData); + GPR_ASSERT(status == 0); +} + +static void winsock_shutdown(void) { + int status = WSACleanup(); + GPR_ASSERT(status == 0); +} + +void grpc_iomgr_platform_init(void) { + winsock_init(); + grpc_iocp_init(); +} + +void grpc_iomgr_platform_flush(void) { grpc_iocp_flush(); } + +void grpc_iomgr_platform_shutdown(void) { + grpc_iocp_shutdown(); + winsock_shutdown(); +} + +#endif /* GRPC_WINSOCK_SOCKET */ diff --git a/src/core/lib/iomgr/pollset.h b/src/core/lib/iomgr/pollset.h new file mode 100644 index 0000000000..6156124862 --- /dev/null +++ b/src/core/lib/iomgr/pollset.h @@ -0,0 +1,94 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_POLLSET_H +#define GRPC_CORE_LIB_IOMGR_POLLSET_H + +#include <grpc/support/port_platform.h> +#include <grpc/support/sync.h> +#include <grpc/support/time.h> + +#include "src/core/lib/iomgr/exec_ctx.h" + +#define GRPC_POLLSET_KICK_BROADCAST ((grpc_pollset_worker *)1) + +/* A grpc_pollset is a set of file descriptors that a higher level item is + interested in. For example: + - a server will typically keep a pollset containing all connected channels, + so that it can find new calls to service + - a completion queue might keep a pollset with an entry for each transport + that is servicing a call that it's tracking */ + +typedef struct grpc_pollset grpc_pollset; +typedef struct grpc_pollset_worker grpc_pollset_worker; + +size_t grpc_pollset_size(void); +void grpc_pollset_init(grpc_pollset *pollset, gpr_mu **mu); +/* Begin shutting down the pollset, and call closure when done. + * pollset's mutex must be held */ +void grpc_pollset_shutdown(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset, + grpc_closure *closure); +/** Reset the pollset to its initial state (perhaps with some cached objects); + * must have been previously shutdown */ +void grpc_pollset_reset(grpc_pollset *pollset); +void grpc_pollset_destroy(grpc_pollset *pollset); + +/* Do some work on a pollset. + May involve invoking asynchronous callbacks, or actually polling file + descriptors. + Requires pollset's mutex locked. + May unlock its mutex during its execution. + + worker is a (platform-specific) handle that can be used to wake up + from grpc_pollset_work before any events are received and before the timeout + has expired. It is both initialized and destroyed by grpc_pollset_work. + Initialization of worker is guaranteed to occur BEFORE the + pollset's mutex is released for the first time by grpc_pollset_work + and it is guaranteed that it will not be released by grpc_pollset_work + AFTER worker has been destroyed. + + Tries not to block past deadline. + May call grpc_closure_list_run on grpc_closure_list, without holding the + pollset + lock */ +void grpc_pollset_work(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset, + grpc_pollset_worker **worker, gpr_timespec now, + gpr_timespec deadline); + +/* Break one polling thread out of polling work for this pollset. + If specific_worker is GRPC_POLLSET_KICK_BROADCAST, kick ALL the workers. + Otherwise, if specific_worker is non-NULL, then kick that worker. */ +void grpc_pollset_kick(grpc_pollset *pollset, + grpc_pollset_worker *specific_worker); + +#endif /* GRPC_CORE_LIB_IOMGR_POLLSET_H */ diff --git a/src/core/lib/iomgr/pollset_multipoller_with_epoll.c b/src/core/lib/iomgr/pollset_multipoller_with_epoll.c new file mode 100644 index 0000000000..fa1b0d2d84 --- /dev/null +++ b/src/core/lib/iomgr/pollset_multipoller_with_epoll.c @@ -0,0 +1,324 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_LINUX_MULTIPOLL_WITH_EPOLL + +#include <errno.h> +#include <poll.h> +#include <string.h> +#include <sys/epoll.h> +#include <unistd.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/useful.h> +#include "src/core/lib/iomgr/fd_posix.h" +#include "src/core/lib/iomgr/pollset_posix.h" +#include "src/core/lib/profiling/timers.h" +#include "src/core/lib/support/block_annotate.h" + +struct epoll_fd_list { + int *epoll_fds; + size_t count; + size_t capacity; +}; + +static struct epoll_fd_list epoll_fd_global_list; +static gpr_once init_epoll_fd_list_mu = GPR_ONCE_INIT; +static gpr_mu epoll_fd_list_mu; + +static void init_mu(void) { gpr_mu_init(&epoll_fd_list_mu); } + +static void add_epoll_fd_to_global_list(int epoll_fd) { + gpr_once_init(&init_epoll_fd_list_mu, init_mu); + + gpr_mu_lock(&epoll_fd_list_mu); + if (epoll_fd_global_list.count == epoll_fd_global_list.capacity) { + epoll_fd_global_list.capacity = + GPR_MAX((size_t)8, epoll_fd_global_list.capacity * 2); + epoll_fd_global_list.epoll_fds = + gpr_realloc(epoll_fd_global_list.epoll_fds, + epoll_fd_global_list.capacity * sizeof(int)); + } + epoll_fd_global_list.epoll_fds[epoll_fd_global_list.count++] = epoll_fd; + gpr_mu_unlock(&epoll_fd_list_mu); +} + +static void remove_epoll_fd_from_global_list(int epoll_fd) { + gpr_mu_lock(&epoll_fd_list_mu); + GPR_ASSERT(epoll_fd_global_list.count > 0); + for (size_t i = 0; i < epoll_fd_global_list.count; i++) { + if (epoll_fd == epoll_fd_global_list.epoll_fds[i]) { + epoll_fd_global_list.epoll_fds[i] = + epoll_fd_global_list.epoll_fds[--(epoll_fd_global_list.count)]; + break; + } + } + gpr_mu_unlock(&epoll_fd_list_mu); +} + +void grpc_remove_fd_from_all_epoll_sets(int fd) { + int err; + gpr_once_init(&init_epoll_fd_list_mu, init_mu); + gpr_mu_lock(&epoll_fd_list_mu); + if (epoll_fd_global_list.count == 0) { + gpr_mu_unlock(&epoll_fd_list_mu); + return; + } + for (size_t i = 0; i < epoll_fd_global_list.count; i++) { + err = epoll_ctl(epoll_fd_global_list.epoll_fds[i], EPOLL_CTL_DEL, fd, NULL); + if (err < 0 && errno != ENOENT) { + gpr_log(GPR_ERROR, "epoll_ctl del for %d failed: %s", fd, + strerror(errno)); + } + } + gpr_mu_unlock(&epoll_fd_list_mu); +} + +typedef struct { + grpc_pollset *pollset; + grpc_fd *fd; + grpc_closure closure; +} delayed_add; + +typedef struct { int epoll_fd; } pollset_hdr; + +static void finally_add_fd(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset, + grpc_fd *fd) { + pollset_hdr *h = pollset->data.ptr; + struct epoll_event ev; + int err; + grpc_fd_watcher watcher; + + /* We pretend to be polling whilst adding an fd to keep the fd from being + closed during the add. This may result in a spurious wakeup being assigned + to this pollset whilst adding, but that should be benign. */ + GPR_ASSERT(grpc_fd_begin_poll(fd, pollset, NULL, 0, 0, &watcher) == 0); + if (watcher.fd != NULL) { + ev.events = (uint32_t)(EPOLLIN | EPOLLOUT | EPOLLET); + ev.data.ptr = fd; + err = epoll_ctl(h->epoll_fd, EPOLL_CTL_ADD, fd->fd, &ev); + if (err < 0) { + /* FDs may be added to a pollset multiple times, so EEXIST is normal. */ + if (errno != EEXIST) { + gpr_log(GPR_ERROR, "epoll_ctl add for %d failed: %s", fd->fd, + strerror(errno)); + } + } + } + grpc_fd_end_poll(exec_ctx, &watcher, 0, 0); +} + +static void perform_delayed_add(grpc_exec_ctx *exec_ctx, void *arg, + bool iomgr_status) { + delayed_add *da = arg; + + if (!grpc_fd_is_orphaned(da->fd)) { + finally_add_fd(exec_ctx, da->pollset, da->fd); + } + + gpr_mu_lock(&da->pollset->mu); + da->pollset->in_flight_cbs--; + if (da->pollset->shutting_down) { + /* We don't care about this pollset anymore. */ + if (da->pollset->in_flight_cbs == 0 && !da->pollset->called_shutdown) { + da->pollset->called_shutdown = 1; + grpc_exec_ctx_enqueue(exec_ctx, da->pollset->shutdown_done, true, NULL); + } + } + gpr_mu_unlock(&da->pollset->mu); + + GRPC_FD_UNREF(da->fd, "delayed_add"); + + gpr_free(da); +} + +static void multipoll_with_epoll_pollset_add_fd(grpc_exec_ctx *exec_ctx, + grpc_pollset *pollset, + grpc_fd *fd, + int and_unlock_pollset) { + if (and_unlock_pollset) { + gpr_mu_unlock(&pollset->mu); + finally_add_fd(exec_ctx, pollset, fd); + } else { + delayed_add *da = gpr_malloc(sizeof(*da)); + da->pollset = pollset; + da->fd = fd; + GRPC_FD_REF(fd, "delayed_add"); + grpc_closure_init(&da->closure, perform_delayed_add, da); + pollset->in_flight_cbs++; + grpc_exec_ctx_enqueue(exec_ctx, &da->closure, true, NULL); + } +} + +/* TODO(klempner): We probably want to turn this down a bit */ +#define GRPC_EPOLL_MAX_EVENTS 1000 + +static void multipoll_with_epoll_pollset_maybe_work_and_unlock( + grpc_exec_ctx *exec_ctx, grpc_pollset *pollset, grpc_pollset_worker *worker, + gpr_timespec deadline, gpr_timespec now) { + struct epoll_event ep_ev[GRPC_EPOLL_MAX_EVENTS]; + int ep_rv; + int poll_rv; + pollset_hdr *h = pollset->data.ptr; + int timeout_ms; + struct pollfd pfds[2]; + + /* If you want to ignore epoll's ability to sanely handle parallel pollers, + * for a more apples-to-apples performance comparison with poll, add a + * if (pollset->counter != 0) { return 0; } + * here. + */ + + gpr_mu_unlock(&pollset->mu); + + timeout_ms = grpc_poll_deadline_to_millis_timeout(deadline, now); + + pfds[0].fd = GRPC_WAKEUP_FD_GET_READ_FD(&worker->wakeup_fd->fd); + pfds[0].events = POLLIN; + pfds[0].revents = 0; + pfds[1].fd = h->epoll_fd; + pfds[1].events = POLLIN; + pfds[1].revents = 0; + + /* TODO(vpai): Consider first doing a 0 timeout poll here to avoid + even going into the blocking annotation if possible */ + GPR_TIMER_BEGIN("poll", 0); + GRPC_SCHEDULING_START_BLOCKING_REGION; + poll_rv = grpc_poll_function(pfds, 2, timeout_ms); + GRPC_SCHEDULING_END_BLOCKING_REGION; + GPR_TIMER_END("poll", 0); + + if (poll_rv < 0) { + if (errno != EINTR) { + gpr_log(GPR_ERROR, "poll() failed: %s", strerror(errno)); + } + } else if (poll_rv == 0) { + /* do nothing */ + } else { + if (pfds[0].revents) { + grpc_wakeup_fd_consume_wakeup(&worker->wakeup_fd->fd); + } + if (pfds[1].revents) { + do { + /* The following epoll_wait never blocks; it has a timeout of 0 */ + ep_rv = epoll_wait(h->epoll_fd, ep_ev, GRPC_EPOLL_MAX_EVENTS, 0); + if (ep_rv < 0) { + if (errno != EINTR) { + gpr_log(GPR_ERROR, "epoll_wait() failed: %s", strerror(errno)); + } + } else { + int i; + for (i = 0; i < ep_rv; ++i) { + grpc_fd *fd = ep_ev[i].data.ptr; + /* TODO(klempner): We might want to consider making err and pri + * separate events */ + int cancel = ep_ev[i].events & (EPOLLERR | EPOLLHUP); + int read_ev = ep_ev[i].events & (EPOLLIN | EPOLLPRI); + int write_ev = ep_ev[i].events & EPOLLOUT; + if (fd == NULL) { + grpc_wakeup_fd_consume_wakeup(&grpc_global_wakeup_fd); + } else { + if (read_ev || cancel) { + grpc_fd_become_readable(exec_ctx, fd); + } + if (write_ev || cancel) { + grpc_fd_become_writable(exec_ctx, fd); + } + } + } + } + } while (ep_rv == GRPC_EPOLL_MAX_EVENTS); + } + } +} + +static void multipoll_with_epoll_pollset_finish_shutdown( + grpc_pollset *pollset) {} + +static void multipoll_with_epoll_pollset_destroy(grpc_pollset *pollset) { + pollset_hdr *h = pollset->data.ptr; + close(h->epoll_fd); + remove_epoll_fd_from_global_list(h->epoll_fd); + gpr_free(h); +} + +static const grpc_pollset_vtable multipoll_with_epoll_pollset = { + multipoll_with_epoll_pollset_add_fd, + multipoll_with_epoll_pollset_maybe_work_and_unlock, + multipoll_with_epoll_pollset_finish_shutdown, + multipoll_with_epoll_pollset_destroy}; + +static void epoll_become_multipoller(grpc_exec_ctx *exec_ctx, + grpc_pollset *pollset, grpc_fd **fds, + size_t nfds) { + size_t i; + pollset_hdr *h = gpr_malloc(sizeof(pollset_hdr)); + struct epoll_event ev; + int err; + + pollset->vtable = &multipoll_with_epoll_pollset; + pollset->data.ptr = h; + h->epoll_fd = epoll_create1(EPOLL_CLOEXEC); + if (h->epoll_fd < 0) { + /* TODO(klempner): Fall back to poll here, especially on ENOSYS */ + gpr_log(GPR_ERROR, "epoll_create1 failed: %s", strerror(errno)); + abort(); + } + add_epoll_fd_to_global_list(h->epoll_fd); + + ev.events = (uint32_t)(EPOLLIN | EPOLLET); + ev.data.ptr = NULL; + err = epoll_ctl(h->epoll_fd, EPOLL_CTL_ADD, + GRPC_WAKEUP_FD_GET_READ_FD(&grpc_global_wakeup_fd), &ev); + if (err < 0) { + gpr_log(GPR_ERROR, "epoll_ctl add for %d failed: %s", + GRPC_WAKEUP_FD_GET_READ_FD(&grpc_global_wakeup_fd), + strerror(errno)); + } + + for (i = 0; i < nfds; i++) { + multipoll_with_epoll_pollset_add_fd(exec_ctx, pollset, fds[i], 0); + } +} + +grpc_platform_become_multipoller_type grpc_platform_become_multipoller = + epoll_become_multipoller; + +#else /* GPR_LINUX_MULTIPOLL_WITH_EPOLL */ + +void grpc_remove_fd_from_all_epoll_sets(int fd) {} + +#endif /* GPR_LINUX_MULTIPOLL_WITH_EPOLL */ diff --git a/src/core/lib/iomgr/pollset_multipoller_with_poll_posix.c b/src/core/lib/iomgr/pollset_multipoller_with_poll_posix.c new file mode 100644 index 0000000000..9b33f6dbb2 --- /dev/null +++ b/src/core/lib/iomgr/pollset_multipoller_with_poll_posix.c @@ -0,0 +1,234 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_SOCKET + +#include "src/core/lib/iomgr/pollset_posix.h" + +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/useful.h> + +#include "src/core/lib/iomgr/fd_posix.h" +#include "src/core/lib/iomgr/iomgr_internal.h" +#include "src/core/lib/iomgr/pollset_posix.h" +#include "src/core/lib/support/block_annotate.h" + +typedef struct { + /* all polled fds */ + size_t fd_count; + size_t fd_capacity; + grpc_fd **fds; + /* fds that have been removed from the pollset explicitly */ + size_t del_count; + size_t del_capacity; + grpc_fd **dels; +} pollset_hdr; + +static void multipoll_with_poll_pollset_add_fd(grpc_exec_ctx *exec_ctx, + grpc_pollset *pollset, + grpc_fd *fd, + int and_unlock_pollset) { + size_t i; + pollset_hdr *h = pollset->data.ptr; + /* TODO(ctiller): this is O(num_fds^2); maybe switch to a hash set here */ + for (i = 0; i < h->fd_count; i++) { + if (h->fds[i] == fd) goto exit; + } + if (h->fd_count == h->fd_capacity) { + h->fd_capacity = GPR_MAX(h->fd_capacity + 8, h->fd_count * 3 / 2); + h->fds = gpr_realloc(h->fds, sizeof(grpc_fd *) * h->fd_capacity); + } + h->fds[h->fd_count++] = fd; + GRPC_FD_REF(fd, "multipoller"); +exit: + if (and_unlock_pollset) { + gpr_mu_unlock(&pollset->mu); + } +} + +static void multipoll_with_poll_pollset_maybe_work_and_unlock( + grpc_exec_ctx *exec_ctx, grpc_pollset *pollset, grpc_pollset_worker *worker, + gpr_timespec deadline, gpr_timespec now) { +#define POLLOUT_CHECK (POLLOUT | POLLHUP | POLLERR) +#define POLLIN_CHECK (POLLIN | POLLHUP | POLLERR) + + int timeout; + int r; + size_t i, j, fd_count; + nfds_t pfd_count; + pollset_hdr *h; + /* TODO(ctiller): inline some elements to avoid an allocation */ + grpc_fd_watcher *watchers; + struct pollfd *pfds; + + h = pollset->data.ptr; + timeout = grpc_poll_deadline_to_millis_timeout(deadline, now); + /* TODO(ctiller): perform just one malloc here if we exceed the inline case */ + pfds = gpr_malloc(sizeof(*pfds) * (h->fd_count + 2)); + watchers = gpr_malloc(sizeof(*watchers) * (h->fd_count + 2)); + fd_count = 0; + pfd_count = 2; + pfds[0].fd = GRPC_WAKEUP_FD_GET_READ_FD(&grpc_global_wakeup_fd); + pfds[0].events = POLLIN; + pfds[0].revents = 0; + pfds[1].fd = GRPC_WAKEUP_FD_GET_READ_FD(&worker->wakeup_fd->fd); + pfds[1].events = POLLIN; + pfds[1].revents = 0; + for (i = 0; i < h->fd_count; i++) { + int remove = grpc_fd_is_orphaned(h->fds[i]); + for (j = 0; !remove && j < h->del_count; j++) { + if (h->fds[i] == h->dels[j]) remove = 1; + } + if (remove) { + GRPC_FD_UNREF(h->fds[i], "multipoller"); + } else { + h->fds[fd_count++] = h->fds[i]; + watchers[pfd_count].fd = h->fds[i]; + GRPC_FD_REF(watchers[pfd_count].fd, "multipoller_start"); + pfds[pfd_count].fd = h->fds[i]->fd; + pfds[pfd_count].revents = 0; + pfd_count++; + } + } + for (j = 0; j < h->del_count; j++) { + GRPC_FD_UNREF(h->dels[j], "multipoller_del"); + } + h->del_count = 0; + h->fd_count = fd_count; + gpr_mu_unlock(&pollset->mu); + + for (i = 2; i < pfd_count; i++) { + grpc_fd *fd = watchers[i].fd; + pfds[i].events = (short)grpc_fd_begin_poll(fd, pollset, worker, POLLIN, + POLLOUT, &watchers[i]); + GRPC_FD_UNREF(fd, "multipoller_start"); + } + + /* TODO(vpai): Consider first doing a 0 timeout poll here to avoid + even going into the blocking annotation if possible */ + GRPC_SCHEDULING_START_BLOCKING_REGION; + r = grpc_poll_function(pfds, pfd_count, timeout); + GRPC_SCHEDULING_END_BLOCKING_REGION; + + if (r < 0) { + if (errno != EINTR) { + gpr_log(GPR_ERROR, "poll() failed: %s", strerror(errno)); + } + for (i = 2; i < pfd_count; i++) { + grpc_fd_end_poll(exec_ctx, &watchers[i], 0, 0); + } + } else if (r == 0) { + for (i = 2; i < pfd_count; i++) { + grpc_fd_end_poll(exec_ctx, &watchers[i], 0, 0); + } + } else { + if (pfds[0].revents & POLLIN_CHECK) { + grpc_wakeup_fd_consume_wakeup(&grpc_global_wakeup_fd); + } + if (pfds[1].revents & POLLIN_CHECK) { + grpc_wakeup_fd_consume_wakeup(&worker->wakeup_fd->fd); + } + for (i = 2; i < pfd_count; i++) { + if (watchers[i].fd == NULL) { + grpc_fd_end_poll(exec_ctx, &watchers[i], 0, 0); + continue; + } + grpc_fd_end_poll(exec_ctx, &watchers[i], pfds[i].revents & POLLIN_CHECK, + pfds[i].revents & POLLOUT_CHECK); + } + } + + gpr_free(pfds); + gpr_free(watchers); +} + +static void multipoll_with_poll_pollset_finish_shutdown(grpc_pollset *pollset) { + size_t i; + pollset_hdr *h = pollset->data.ptr; + for (i = 0; i < h->fd_count; i++) { + GRPC_FD_UNREF(h->fds[i], "multipoller"); + } + for (i = 0; i < h->del_count; i++) { + GRPC_FD_UNREF(h->dels[i], "multipoller_del"); + } + h->fd_count = 0; + h->del_count = 0; +} + +static void multipoll_with_poll_pollset_destroy(grpc_pollset *pollset) { + pollset_hdr *h = pollset->data.ptr; + multipoll_with_poll_pollset_finish_shutdown(pollset); + gpr_free(h->fds); + gpr_free(h->dels); + gpr_free(h); +} + +static const grpc_pollset_vtable multipoll_with_poll_pollset = { + multipoll_with_poll_pollset_add_fd, + multipoll_with_poll_pollset_maybe_work_and_unlock, + multipoll_with_poll_pollset_finish_shutdown, + multipoll_with_poll_pollset_destroy}; + +void grpc_poll_become_multipoller(grpc_exec_ctx *exec_ctx, + grpc_pollset *pollset, grpc_fd **fds, + size_t nfds) { + size_t i; + pollset_hdr *h = gpr_malloc(sizeof(pollset_hdr)); + pollset->vtable = &multipoll_with_poll_pollset; + pollset->data.ptr = h; + h->fd_count = nfds; + h->fd_capacity = nfds; + h->fds = gpr_malloc(nfds * sizeof(grpc_fd *)); + h->del_count = 0; + h->del_capacity = 0; + h->dels = NULL; + for (i = 0; i < nfds; i++) { + h->fds[i] = fds[i]; + GRPC_FD_REF(fds[i], "multipoller"); + } +} + +#endif /* GPR_POSIX_SOCKET */ + +#ifdef GPR_POSIX_MULTIPOLL_WITH_POLL +grpc_platform_become_multipoller_type grpc_platform_become_multipoller = + grpc_poll_become_multipoller; +#endif diff --git a/src/core/lib/iomgr/pollset_posix.c b/src/core/lib/iomgr/pollset_posix.c new file mode 100644 index 0000000000..259c7bc194 --- /dev/null +++ b/src/core/lib/iomgr/pollset_posix.c @@ -0,0 +1,633 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_SOCKET + +#include "src/core/lib/iomgr/pollset_posix.h" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/thd.h> +#include <grpc/support/tls.h> +#include <grpc/support/useful.h> +#include "src/core/lib/iomgr/fd_posix.h" +#include "src/core/lib/iomgr/iomgr_internal.h" +#include "src/core/lib/iomgr/socket_utils_posix.h" +#include "src/core/lib/profiling/timers.h" +#include "src/core/lib/support/block_annotate.h" + +GPR_TLS_DECL(g_current_thread_poller); +GPR_TLS_DECL(g_current_thread_worker); + +/** Default poll() function - a pointer so that it can be overridden by some + * tests */ +grpc_poll_function_type grpc_poll_function = poll; + +/** The alarm system needs to be able to wakeup 'some poller' sometimes + * (specifically when a new alarm needs to be triggered earlier than the next + * alarm 'epoch'). + * This wakeup_fd gives us something to alert on when such a case occurs. */ +grpc_wakeup_fd grpc_global_wakeup_fd; + +static void remove_worker(grpc_pollset *p, grpc_pollset_worker *worker) { + worker->prev->next = worker->next; + worker->next->prev = worker->prev; +} + +int grpc_pollset_has_workers(grpc_pollset *p) { + return p->root_worker.next != &p->root_worker; +} + +static grpc_pollset_worker *pop_front_worker(grpc_pollset *p) { + if (grpc_pollset_has_workers(p)) { + grpc_pollset_worker *w = p->root_worker.next; + remove_worker(p, w); + return w; + } else { + return NULL; + } +} + +static void push_back_worker(grpc_pollset *p, grpc_pollset_worker *worker) { + worker->next = &p->root_worker; + worker->prev = worker->next->prev; + worker->prev->next = worker->next->prev = worker; +} + +static void push_front_worker(grpc_pollset *p, grpc_pollset_worker *worker) { + worker->prev = &p->root_worker; + worker->next = worker->prev->next; + worker->prev->next = worker->next->prev = worker; +} + +size_t grpc_pollset_size(void) { return sizeof(grpc_pollset); } + +void grpc_pollset_kick_ext(grpc_pollset *p, + grpc_pollset_worker *specific_worker, + uint32_t flags) { + GPR_TIMER_BEGIN("grpc_pollset_kick_ext", 0); + + /* pollset->mu already held */ + if (specific_worker != NULL) { + if (specific_worker == GRPC_POLLSET_KICK_BROADCAST) { + GPR_TIMER_BEGIN("grpc_pollset_kick_ext.broadcast", 0); + GPR_ASSERT((flags & GRPC_POLLSET_REEVALUATE_POLLING_ON_WAKEUP) == 0); + for (specific_worker = p->root_worker.next; + specific_worker != &p->root_worker; + specific_worker = specific_worker->next) { + grpc_wakeup_fd_wakeup(&specific_worker->wakeup_fd->fd); + } + p->kicked_without_pollers = 1; + GPR_TIMER_END("grpc_pollset_kick_ext.broadcast", 0); + } else if (gpr_tls_get(&g_current_thread_worker) != + (intptr_t)specific_worker) { + GPR_TIMER_MARK("different_thread_worker", 0); + if ((flags & GRPC_POLLSET_REEVALUATE_POLLING_ON_WAKEUP) != 0) { + specific_worker->reevaluate_polling_on_wakeup = 1; + } + specific_worker->kicked_specifically = 1; + grpc_wakeup_fd_wakeup(&specific_worker->wakeup_fd->fd); + } else if ((flags & GRPC_POLLSET_CAN_KICK_SELF) != 0) { + GPR_TIMER_MARK("kick_yoself", 0); + if ((flags & GRPC_POLLSET_REEVALUATE_POLLING_ON_WAKEUP) != 0) { + specific_worker->reevaluate_polling_on_wakeup = 1; + } + specific_worker->kicked_specifically = 1; + grpc_wakeup_fd_wakeup(&specific_worker->wakeup_fd->fd); + } + } else if (gpr_tls_get(&g_current_thread_poller) != (intptr_t)p) { + GPR_ASSERT((flags & GRPC_POLLSET_REEVALUATE_POLLING_ON_WAKEUP) == 0); + GPR_TIMER_MARK("kick_anonymous", 0); + specific_worker = pop_front_worker(p); + if (specific_worker != NULL) { + if (gpr_tls_get(&g_current_thread_worker) == (intptr_t)specific_worker) { + GPR_TIMER_MARK("kick_anonymous_not_self", 0); + push_back_worker(p, specific_worker); + specific_worker = pop_front_worker(p); + if ((flags & GRPC_POLLSET_CAN_KICK_SELF) == 0 && + gpr_tls_get(&g_current_thread_worker) == + (intptr_t)specific_worker) { + push_back_worker(p, specific_worker); + specific_worker = NULL; + } + } + if (specific_worker != NULL) { + GPR_TIMER_MARK("finally_kick", 0); + push_back_worker(p, specific_worker); + grpc_wakeup_fd_wakeup(&specific_worker->wakeup_fd->fd); + } + } else { + GPR_TIMER_MARK("kicked_no_pollers", 0); + p->kicked_without_pollers = 1; + } + } + + GPR_TIMER_END("grpc_pollset_kick_ext", 0); +} + +void grpc_pollset_kick(grpc_pollset *p, grpc_pollset_worker *specific_worker) { + grpc_pollset_kick_ext(p, specific_worker, 0); +} + +/* global state management */ + +void grpc_pollset_global_init(void) { + gpr_tls_init(&g_current_thread_poller); + gpr_tls_init(&g_current_thread_worker); + grpc_wakeup_fd_global_init(); + grpc_wakeup_fd_init(&grpc_global_wakeup_fd); +} + +void grpc_pollset_global_shutdown(void) { + grpc_wakeup_fd_destroy(&grpc_global_wakeup_fd); + gpr_tls_destroy(&g_current_thread_poller); + gpr_tls_destroy(&g_current_thread_worker); + grpc_wakeup_fd_global_destroy(); +} + +void grpc_kick_poller(void) { grpc_wakeup_fd_wakeup(&grpc_global_wakeup_fd); } + +/* main interface */ + +static void become_basic_pollset(grpc_pollset *pollset, grpc_fd *fd_or_null); + +void grpc_pollset_init(grpc_pollset *pollset, gpr_mu **mu) { + gpr_mu_init(&pollset->mu); + *mu = &pollset->mu; + pollset->root_worker.next = pollset->root_worker.prev = &pollset->root_worker; + pollset->in_flight_cbs = 0; + pollset->shutting_down = 0; + pollset->called_shutdown = 0; + pollset->kicked_without_pollers = 0; + pollset->idle_jobs.head = pollset->idle_jobs.tail = NULL; + pollset->local_wakeup_cache = NULL; + pollset->kicked_without_pollers = 0; + become_basic_pollset(pollset, NULL); +} + +void grpc_pollset_destroy(grpc_pollset *pollset) { + GPR_ASSERT(pollset->in_flight_cbs == 0); + GPR_ASSERT(!grpc_pollset_has_workers(pollset)); + GPR_ASSERT(pollset->idle_jobs.head == pollset->idle_jobs.tail); + pollset->vtable->destroy(pollset); + while (pollset->local_wakeup_cache) { + grpc_cached_wakeup_fd *next = pollset->local_wakeup_cache->next; + grpc_wakeup_fd_destroy(&pollset->local_wakeup_cache->fd); + gpr_free(pollset->local_wakeup_cache); + pollset->local_wakeup_cache = next; + } +} + +void grpc_pollset_reset(grpc_pollset *pollset) { + GPR_ASSERT(pollset->shutting_down); + GPR_ASSERT(pollset->in_flight_cbs == 0); + GPR_ASSERT(!grpc_pollset_has_workers(pollset)); + GPR_ASSERT(pollset->idle_jobs.head == pollset->idle_jobs.tail); + pollset->vtable->destroy(pollset); + pollset->shutting_down = 0; + pollset->called_shutdown = 0; + pollset->kicked_without_pollers = 0; + become_basic_pollset(pollset, NULL); +} + +void grpc_pollset_add_fd(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset, + grpc_fd *fd) { + gpr_mu_lock(&pollset->mu); + pollset->vtable->add_fd(exec_ctx, pollset, fd, 1); +/* the following (enabled only in debug) will reacquire and then release + our lock - meaning that if the unlocking flag passed to add_fd above is + not respected, the code will deadlock (in a way that we have a chance of + debugging) */ +#ifndef NDEBUG + gpr_mu_lock(&pollset->mu); + gpr_mu_unlock(&pollset->mu); +#endif +} + +static void finish_shutdown(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset) { + GPR_ASSERT(grpc_closure_list_empty(pollset->idle_jobs)); + pollset->vtable->finish_shutdown(pollset); + grpc_exec_ctx_enqueue(exec_ctx, pollset->shutdown_done, true, NULL); +} + +void grpc_pollset_work(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset, + grpc_pollset_worker **worker_hdl, gpr_timespec now, + gpr_timespec deadline) { + grpc_pollset_worker worker; + *worker_hdl = &worker; + + /* pollset->mu already held */ + int added_worker = 0; + int locked = 1; + int queued_work = 0; + int keep_polling = 0; + GPR_TIMER_BEGIN("grpc_pollset_work", 0); + /* this must happen before we (potentially) drop pollset->mu */ + worker.next = worker.prev = NULL; + worker.reevaluate_polling_on_wakeup = 0; + if (pollset->local_wakeup_cache != NULL) { + worker.wakeup_fd = pollset->local_wakeup_cache; + pollset->local_wakeup_cache = worker.wakeup_fd->next; + } else { + worker.wakeup_fd = gpr_malloc(sizeof(*worker.wakeup_fd)); + grpc_wakeup_fd_init(&worker.wakeup_fd->fd); + } + worker.kicked_specifically = 0; + /* If there's work waiting for the pollset to be idle, and the + pollset is idle, then do that work */ + if (!grpc_pollset_has_workers(pollset) && + !grpc_closure_list_empty(pollset->idle_jobs)) { + GPR_TIMER_MARK("grpc_pollset_work.idle_jobs", 0); + grpc_exec_ctx_enqueue_list(exec_ctx, &pollset->idle_jobs, NULL); + goto done; + } + /* If we're shutting down then we don't execute any extended work */ + if (pollset->shutting_down) { + GPR_TIMER_MARK("grpc_pollset_work.shutting_down", 0); + goto done; + } + /* Give do_promote priority so we don't starve it out */ + if (pollset->in_flight_cbs) { + GPR_TIMER_MARK("grpc_pollset_work.in_flight_cbs", 0); + gpr_mu_unlock(&pollset->mu); + locked = 0; + goto done; + } + /* Start polling, and keep doing so while we're being asked to + re-evaluate our pollers (this allows poll() based pollers to + ensure they don't miss wakeups) */ + keep_polling = 1; + while (keep_polling) { + keep_polling = 0; + if (!pollset->kicked_without_pollers) { + if (!added_worker) { + push_front_worker(pollset, &worker); + added_worker = 1; + gpr_tls_set(&g_current_thread_worker, (intptr_t)&worker); + } + gpr_tls_set(&g_current_thread_poller, (intptr_t)pollset); + GPR_TIMER_BEGIN("maybe_work_and_unlock", 0); + pollset->vtable->maybe_work_and_unlock(exec_ctx, pollset, &worker, + deadline, now); + GPR_TIMER_END("maybe_work_and_unlock", 0); + locked = 0; + gpr_tls_set(&g_current_thread_poller, 0); + } else { + GPR_TIMER_MARK("grpc_pollset_work.kicked_without_pollers", 0); + pollset->kicked_without_pollers = 0; + } + /* Finished execution - start cleaning up. + Note that we may arrive here from outside the enclosing while() loop. + In that case we won't loop though as we haven't added worker to the + worker list, which means nobody could ask us to re-evaluate polling). */ + done: + if (!locked) { + queued_work |= grpc_exec_ctx_flush(exec_ctx); + gpr_mu_lock(&pollset->mu); + locked = 1; + } + /* If we're forced to re-evaluate polling (via grpc_pollset_kick with + GRPC_POLLSET_REEVALUATE_POLLING_ON_WAKEUP) then we land here and force + a loop */ + if (worker.reevaluate_polling_on_wakeup) { + worker.reevaluate_polling_on_wakeup = 0; + pollset->kicked_without_pollers = 0; + if (queued_work || worker.kicked_specifically) { + /* If there's queued work on the list, then set the deadline to be + immediate so we get back out of the polling loop quickly */ + deadline = gpr_inf_past(GPR_CLOCK_MONOTONIC); + } + keep_polling = 1; + } + } + if (added_worker) { + remove_worker(pollset, &worker); + gpr_tls_set(&g_current_thread_worker, 0); + } + /* release wakeup fd to the local pool */ + worker.wakeup_fd->next = pollset->local_wakeup_cache; + pollset->local_wakeup_cache = worker.wakeup_fd; + /* check shutdown conditions */ + if (pollset->shutting_down) { + if (grpc_pollset_has_workers(pollset)) { + grpc_pollset_kick(pollset, NULL); + } else if (!pollset->called_shutdown && pollset->in_flight_cbs == 0) { + pollset->called_shutdown = 1; + gpr_mu_unlock(&pollset->mu); + finish_shutdown(exec_ctx, pollset); + grpc_exec_ctx_flush(exec_ctx); + /* Continuing to access pollset here is safe -- it is the caller's + * responsibility to not destroy when it has outstanding calls to + * grpc_pollset_work. + * TODO(dklempner): Can we refactor the shutdown logic to avoid this? */ + gpr_mu_lock(&pollset->mu); + } else if (!grpc_closure_list_empty(pollset->idle_jobs)) { + grpc_exec_ctx_enqueue_list(exec_ctx, &pollset->idle_jobs, NULL); + gpr_mu_unlock(&pollset->mu); + grpc_exec_ctx_flush(exec_ctx); + gpr_mu_lock(&pollset->mu); + } + } + *worker_hdl = NULL; + GPR_TIMER_END("grpc_pollset_work", 0); +} + +void grpc_pollset_shutdown(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset, + grpc_closure *closure) { + GPR_ASSERT(!pollset->shutting_down); + pollset->shutting_down = 1; + pollset->shutdown_done = closure; + grpc_pollset_kick(pollset, GRPC_POLLSET_KICK_BROADCAST); + if (!grpc_pollset_has_workers(pollset)) { + grpc_exec_ctx_enqueue_list(exec_ctx, &pollset->idle_jobs, NULL); + } + if (!pollset->called_shutdown && pollset->in_flight_cbs == 0 && + !grpc_pollset_has_workers(pollset)) { + pollset->called_shutdown = 1; + finish_shutdown(exec_ctx, pollset); + } +} + +int grpc_poll_deadline_to_millis_timeout(gpr_timespec deadline, + gpr_timespec now) { + gpr_timespec timeout; + static const int64_t max_spin_polling_us = 10; + if (gpr_time_cmp(deadline, gpr_inf_future(deadline.clock_type)) == 0) { + return -1; + } + if (gpr_time_cmp(deadline, gpr_time_add(now, gpr_time_from_micros( + max_spin_polling_us, + GPR_TIMESPAN))) <= 0) { + return 0; + } + timeout = gpr_time_sub(deadline, now); + return gpr_time_to_millis(gpr_time_add( + timeout, gpr_time_from_nanos(GPR_NS_PER_MS - 1, GPR_TIMESPAN))); +} + +/* + * basic_pollset - a vtable that provides polling for zero or one file + * descriptor via poll() + */ + +typedef struct grpc_unary_promote_args { + const grpc_pollset_vtable *original_vtable; + grpc_pollset *pollset; + grpc_fd *fd; + grpc_closure promotion_closure; +} grpc_unary_promote_args; + +static void basic_do_promote(grpc_exec_ctx *exec_ctx, void *args, + bool success) { + grpc_unary_promote_args *up_args = args; + const grpc_pollset_vtable *original_vtable = up_args->original_vtable; + grpc_pollset *pollset = up_args->pollset; + grpc_fd *fd = up_args->fd; + + /* + * This is quite tricky. There are a number of cases to keep in mind here: + * 1. fd may have been orphaned + * 2. The pollset may no longer be a unary poller (and we can't let case #1 + * leak to other pollset types!) + * 3. pollset's fd (which may have changed) may have been orphaned + * 4. The pollset may be shutting down. + */ + + gpr_mu_lock(&pollset->mu); + /* First we need to ensure that nobody is polling concurrently */ + GPR_ASSERT(!grpc_pollset_has_workers(pollset)); + + gpr_free(up_args); + /* At this point the pollset may no longer be a unary poller. In that case + * we should just call the right add function and be done. */ + /* TODO(klempner): If we're not careful this could cause infinite recursion. + * That's not a problem for now because empty_pollset has a trivial poller + * and we don't have any mechanism to unbecome multipoller. */ + pollset->in_flight_cbs--; + if (pollset->shutting_down) { + /* We don't care about this pollset anymore. */ + if (pollset->in_flight_cbs == 0 && !pollset->called_shutdown) { + pollset->called_shutdown = 1; + finish_shutdown(exec_ctx, pollset); + } + } else if (grpc_fd_is_orphaned(fd)) { + /* Don't try to add it to anything, we'll drop our ref on it below */ + } else if (pollset->vtable != original_vtable) { + pollset->vtable->add_fd(exec_ctx, pollset, fd, 0); + } else if (fd != pollset->data.ptr) { + grpc_fd *fds[2]; + fds[0] = pollset->data.ptr; + fds[1] = fd; + + if (fds[0] && !grpc_fd_is_orphaned(fds[0])) { + grpc_platform_become_multipoller(exec_ctx, pollset, fds, + GPR_ARRAY_SIZE(fds)); + GRPC_FD_UNREF(fds[0], "basicpoll"); + } else { + /* old fd is orphaned and we haven't cleaned it up until now, so remain a + * unary poller */ + /* Note that it is possible that fds[1] is also orphaned at this point. + * That's okay, we'll correct it at the next add or poll. */ + if (fds[0]) GRPC_FD_UNREF(fds[0], "basicpoll"); + pollset->data.ptr = fd; + GRPC_FD_REF(fd, "basicpoll"); + } + } + + gpr_mu_unlock(&pollset->mu); + + /* Matching ref in basic_pollset_add_fd */ + GRPC_FD_UNREF(fd, "basicpoll_add"); +} + +static void basic_pollset_add_fd(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset, + grpc_fd *fd, int and_unlock_pollset) { + grpc_unary_promote_args *up_args; + GPR_ASSERT(fd); + if (fd == pollset->data.ptr) goto exit; + + if (!grpc_pollset_has_workers(pollset)) { + /* Fast path -- no in flight cbs */ + /* TODO(klempner): Comment this out and fix any test failures or establish + * they are due to timing issues */ + grpc_fd *fds[2]; + fds[0] = pollset->data.ptr; + fds[1] = fd; + + if (fds[0] == NULL) { + pollset->data.ptr = fd; + GRPC_FD_REF(fd, "basicpoll"); + } else if (!grpc_fd_is_orphaned(fds[0])) { + grpc_platform_become_multipoller(exec_ctx, pollset, fds, + GPR_ARRAY_SIZE(fds)); + GRPC_FD_UNREF(fds[0], "basicpoll"); + } else { + /* old fd is orphaned and we haven't cleaned it up until now, so remain a + * unary poller */ + GRPC_FD_UNREF(fds[0], "basicpoll"); + pollset->data.ptr = fd; + GRPC_FD_REF(fd, "basicpoll"); + } + goto exit; + } + + /* Now we need to promote. This needs to happen when we're not polling. Since + * this may be called from poll, the wait needs to happen asynchronously. */ + GRPC_FD_REF(fd, "basicpoll_add"); + pollset->in_flight_cbs++; + up_args = gpr_malloc(sizeof(*up_args)); + up_args->fd = fd; + up_args->original_vtable = pollset->vtable; + up_args->pollset = pollset; + up_args->promotion_closure.cb = basic_do_promote; + up_args->promotion_closure.cb_arg = up_args; + + grpc_closure_list_add(&pollset->idle_jobs, &up_args->promotion_closure, 1); + grpc_pollset_kick(pollset, GRPC_POLLSET_KICK_BROADCAST); + +exit: + if (and_unlock_pollset) { + gpr_mu_unlock(&pollset->mu); + } +} + +static void basic_pollset_maybe_work_and_unlock(grpc_exec_ctx *exec_ctx, + grpc_pollset *pollset, + grpc_pollset_worker *worker, + gpr_timespec deadline, + gpr_timespec now) { +#define POLLOUT_CHECK (POLLOUT | POLLHUP | POLLERR) +#define POLLIN_CHECK (POLLIN | POLLHUP | POLLERR) + + struct pollfd pfd[3]; + grpc_fd *fd; + grpc_fd_watcher fd_watcher; + int timeout; + int r; + nfds_t nfds; + + fd = pollset->data.ptr; + if (fd && grpc_fd_is_orphaned(fd)) { + GRPC_FD_UNREF(fd, "basicpoll"); + fd = pollset->data.ptr = NULL; + } + timeout = grpc_poll_deadline_to_millis_timeout(deadline, now); + pfd[0].fd = GRPC_WAKEUP_FD_GET_READ_FD(&grpc_global_wakeup_fd); + pfd[0].events = POLLIN; + pfd[0].revents = 0; + pfd[1].fd = GRPC_WAKEUP_FD_GET_READ_FD(&worker->wakeup_fd->fd); + pfd[1].events = POLLIN; + pfd[1].revents = 0; + nfds = 2; + if (fd) { + pfd[2].fd = fd->fd; + pfd[2].revents = 0; + GRPC_FD_REF(fd, "basicpoll_begin"); + gpr_mu_unlock(&pollset->mu); + pfd[2].events = (short)grpc_fd_begin_poll(fd, pollset, worker, POLLIN, + POLLOUT, &fd_watcher); + if (pfd[2].events != 0) { + nfds++; + } + } else { + gpr_mu_unlock(&pollset->mu); + } + + /* TODO(vpai): Consider first doing a 0 timeout poll here to avoid + even going into the blocking annotation if possible */ + /* poll fd count (argument 2) is shortened by one if we have no events + to poll on - such that it only includes the kicker */ + GPR_TIMER_BEGIN("poll", 0); + GRPC_SCHEDULING_START_BLOCKING_REGION; + r = grpc_poll_function(pfd, nfds, timeout); + GRPC_SCHEDULING_END_BLOCKING_REGION; + GPR_TIMER_END("poll", 0); + + if (r < 0) { + if (errno != EINTR) { + gpr_log(GPR_ERROR, "poll() failed: %s", strerror(errno)); + } + if (fd) { + grpc_fd_end_poll(exec_ctx, &fd_watcher, 0, 0); + } + } else if (r == 0) { + if (fd) { + grpc_fd_end_poll(exec_ctx, &fd_watcher, 0, 0); + } + } else { + if (pfd[0].revents & POLLIN_CHECK) { + grpc_wakeup_fd_consume_wakeup(&grpc_global_wakeup_fd); + } + if (pfd[1].revents & POLLIN_CHECK) { + grpc_wakeup_fd_consume_wakeup(&worker->wakeup_fd->fd); + } + if (nfds > 2) { + grpc_fd_end_poll(exec_ctx, &fd_watcher, pfd[2].revents & POLLIN_CHECK, + pfd[2].revents & POLLOUT_CHECK); + } else if (fd) { + grpc_fd_end_poll(exec_ctx, &fd_watcher, 0, 0); + } + } + + if (fd) { + GRPC_FD_UNREF(fd, "basicpoll_begin"); + } +} + +static void basic_pollset_destroy(grpc_pollset *pollset) { + if (pollset->data.ptr != NULL) { + GRPC_FD_UNREF(pollset->data.ptr, "basicpoll"); + pollset->data.ptr = NULL; + } +} + +static const grpc_pollset_vtable basic_pollset = { + basic_pollset_add_fd, basic_pollset_maybe_work_and_unlock, + basic_pollset_destroy, basic_pollset_destroy}; + +static void become_basic_pollset(grpc_pollset *pollset, grpc_fd *fd_or_null) { + pollset->vtable = &basic_pollset; + pollset->data.ptr = fd_or_null; + if (fd_or_null != NULL) { + GRPC_FD_REF(fd_or_null, "basicpoll"); + } +} + +#endif /* GPR_POSIX_POLLSET */ diff --git a/src/core/lib/iomgr/pollset_posix.h b/src/core/lib/iomgr/pollset_posix.h new file mode 100644 index 0000000000..7d8e9fc279 --- /dev/null +++ b/src/core/lib/iomgr/pollset_posix.h @@ -0,0 +1,153 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_POLLSET_POSIX_H +#define GRPC_CORE_LIB_IOMGR_POLLSET_POSIX_H + +#include <poll.h> + +#include <grpc/support/sync.h> + +#include "src/core/lib/iomgr/exec_ctx.h" +#include "src/core/lib/iomgr/iomgr.h" +#include "src/core/lib/iomgr/pollset.h" +#include "src/core/lib/iomgr/wakeup_fd_posix.h" + +typedef struct grpc_pollset_vtable grpc_pollset_vtable; + +/* forward declare only in this file to avoid leaking impl details via + pollset.h; real users of grpc_fd should always include 'fd_posix.h' and not + use the struct tag */ +struct grpc_fd; + +typedef struct grpc_cached_wakeup_fd { + grpc_wakeup_fd fd; + struct grpc_cached_wakeup_fd *next; +} grpc_cached_wakeup_fd; + +struct grpc_pollset_worker { + grpc_cached_wakeup_fd *wakeup_fd; + int reevaluate_polling_on_wakeup; + int kicked_specifically; + struct grpc_pollset_worker *next; + struct grpc_pollset_worker *prev; +}; + +struct grpc_pollset { + /* pollsets under posix can mutate representation as fds are added and + removed. + For example, we may choose a poll() based implementation on linux for + few fds, and an epoll() based implementation for many fds */ + const grpc_pollset_vtable *vtable; + gpr_mu mu; + grpc_pollset_worker root_worker; + int in_flight_cbs; + int shutting_down; + int called_shutdown; + int kicked_without_pollers; + grpc_closure *shutdown_done; + grpc_closure_list idle_jobs; + union { + int fd; + void *ptr; + } data; + /* Local cache of eventfds for workers */ + grpc_cached_wakeup_fd *local_wakeup_cache; +}; + +struct grpc_pollset_vtable { + void (*add_fd)(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset, + struct grpc_fd *fd, int and_unlock_pollset); + void (*maybe_work_and_unlock)(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset, + grpc_pollset_worker *worker, + gpr_timespec deadline, gpr_timespec now); + void (*finish_shutdown)(grpc_pollset *pollset); + void (*destroy)(grpc_pollset *pollset); +}; + +/* Add an fd to a pollset */ +void grpc_pollset_add_fd(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset, + struct grpc_fd *fd); + +/* Returns the fd to listen on for kicks */ +int grpc_kick_read_fd(grpc_pollset *p); +/* Call after polling has been kicked to leave the kicked state */ +void grpc_kick_drain(grpc_pollset *p); + +/* Convert a timespec to milliseconds: + - very small or negative poll times are clamped to zero to do a + non-blocking poll (which becomes spin polling) + - other small values are rounded up to one millisecond + - longer than a millisecond polls are rounded up to the next nearest + millisecond to avoid spinning + - infinite timeouts are converted to -1 */ +int grpc_poll_deadline_to_millis_timeout(gpr_timespec deadline, + gpr_timespec now); + +/* Allow kick to wakeup the currently polling worker */ +#define GRPC_POLLSET_CAN_KICK_SELF 1 +/* Force the wakee to repoll when awoken */ +#define GRPC_POLLSET_REEVALUATE_POLLING_ON_WAKEUP 2 +/* As per grpc_pollset_kick, with an extended set of flags (defined above) + -- mostly for fd_posix's use. */ +void grpc_pollset_kick_ext(grpc_pollset *p, + grpc_pollset_worker *specific_worker, + uint32_t flags); + +/* turn a pollset into a multipoller: platform specific */ +typedef void (*grpc_platform_become_multipoller_type)(grpc_exec_ctx *exec_ctx, + grpc_pollset *pollset, + struct grpc_fd **fds, + size_t fd_count); +extern grpc_platform_become_multipoller_type grpc_platform_become_multipoller; + +void grpc_poll_become_multipoller(grpc_exec_ctx *exec_ctx, + grpc_pollset *pollset, struct grpc_fd **fds, + size_t fd_count); + +/* Return 1 if the pollset has active threads in grpc_pollset_work (pollset must + * be locked) */ +int grpc_pollset_has_workers(grpc_pollset *pollset); + +void grpc_remove_fd_from_all_epoll_sets(int fd); + +/* override to allow tests to hook poll() usage */ +/* NOTE: Any changes to grpc_poll_function must take place when the gRPC + is certainly not doing any polling anywhere. + Otherwise, there might be a race between changing the variable and actually + doing a polling operation */ +typedef int (*grpc_poll_function_type)(struct pollfd *, nfds_t, int); +extern grpc_poll_function_type grpc_poll_function; +extern grpc_wakeup_fd grpc_global_wakeup_fd; + +#endif /* GRPC_CORE_LIB_IOMGR_POLLSET_POSIX_H */ diff --git a/src/core/lib/iomgr/pollset_set.h b/src/core/lib/iomgr/pollset_set.h new file mode 100644 index 0000000000..fb29d692d7 --- /dev/null +++ b/src/core/lib/iomgr/pollset_set.h @@ -0,0 +1,61 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_POLLSET_SET_H +#define GRPC_CORE_LIB_IOMGR_POLLSET_SET_H + +#include "src/core/lib/iomgr/pollset.h" + +/* A grpc_pollset_set is a set of pollsets that are interested in an + action. Adding a pollset to a pollset_set automatically adds any + fd's (etc) that have been registered with the set_set to that pollset. + Registering fd's automatically adds them to all current pollsets. */ + +typedef struct grpc_pollset_set grpc_pollset_set; + +grpc_pollset_set *grpc_pollset_set_create(void); +void grpc_pollset_set_destroy(grpc_pollset_set *pollset_set); +void grpc_pollset_set_add_pollset(grpc_exec_ctx *exec_ctx, + grpc_pollset_set *pollset_set, + grpc_pollset *pollset); +void grpc_pollset_set_del_pollset(grpc_exec_ctx *exec_ctx, + grpc_pollset_set *pollset_set, + grpc_pollset *pollset); +void grpc_pollset_set_add_pollset_set(grpc_exec_ctx *exec_ctx, + grpc_pollset_set *bag, + grpc_pollset_set *item); +void grpc_pollset_set_del_pollset_set(grpc_exec_ctx *exec_ctx, + grpc_pollset_set *bag, + grpc_pollset_set *item); + +#endif /* GRPC_CORE_LIB_IOMGR_POLLSET_SET_H */ diff --git a/src/core/lib/iomgr/pollset_set_posix.c b/src/core/lib/iomgr/pollset_set_posix.c new file mode 100644 index 0000000000..d6142f9b6b --- /dev/null +++ b/src/core/lib/iomgr/pollset_set_posix.c @@ -0,0 +1,202 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_SOCKET + +#include <stdlib.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/useful.h> + +#include "src/core/lib/iomgr/pollset_posix.h" +#include "src/core/lib/iomgr/pollset_set_posix.h" + +struct grpc_pollset_set { + gpr_mu mu; + + size_t pollset_count; + size_t pollset_capacity; + grpc_pollset **pollsets; + + size_t pollset_set_count; + size_t pollset_set_capacity; + struct grpc_pollset_set **pollset_sets; + + size_t fd_count; + size_t fd_capacity; + grpc_fd **fds; +}; + +grpc_pollset_set *grpc_pollset_set_create(void) { + grpc_pollset_set *pollset_set = gpr_malloc(sizeof(*pollset_set)); + memset(pollset_set, 0, sizeof(*pollset_set)); + gpr_mu_init(&pollset_set->mu); + return pollset_set; +} + +void grpc_pollset_set_destroy(grpc_pollset_set *pollset_set) { + size_t i; + gpr_mu_destroy(&pollset_set->mu); + for (i = 0; i < pollset_set->fd_count; i++) { + GRPC_FD_UNREF(pollset_set->fds[i], "pollset_set"); + } + gpr_free(pollset_set->pollsets); + gpr_free(pollset_set->pollset_sets); + gpr_free(pollset_set->fds); + gpr_free(pollset_set); +} + +void grpc_pollset_set_add_pollset(grpc_exec_ctx *exec_ctx, + grpc_pollset_set *pollset_set, + grpc_pollset *pollset) { + size_t i, j; + gpr_mu_lock(&pollset_set->mu); + if (pollset_set->pollset_count == pollset_set->pollset_capacity) { + pollset_set->pollset_capacity = + GPR_MAX(8, 2 * pollset_set->pollset_capacity); + pollset_set->pollsets = + gpr_realloc(pollset_set->pollsets, pollset_set->pollset_capacity * + sizeof(*pollset_set->pollsets)); + } + pollset_set->pollsets[pollset_set->pollset_count++] = pollset; + for (i = 0, j = 0; i < pollset_set->fd_count; i++) { + if (grpc_fd_is_orphaned(pollset_set->fds[i])) { + GRPC_FD_UNREF(pollset_set->fds[i], "pollset_set"); + } else { + grpc_pollset_add_fd(exec_ctx, pollset, pollset_set->fds[i]); + pollset_set->fds[j++] = pollset_set->fds[i]; + } + } + pollset_set->fd_count = j; + gpr_mu_unlock(&pollset_set->mu); +} + +void grpc_pollset_set_del_pollset(grpc_exec_ctx *exec_ctx, + grpc_pollset_set *pollset_set, + grpc_pollset *pollset) { + size_t i; + gpr_mu_lock(&pollset_set->mu); + for (i = 0; i < pollset_set->pollset_count; i++) { + if (pollset_set->pollsets[i] == pollset) { + pollset_set->pollset_count--; + GPR_SWAP(grpc_pollset *, pollset_set->pollsets[i], + pollset_set->pollsets[pollset_set->pollset_count]); + break; + } + } + gpr_mu_unlock(&pollset_set->mu); +} + +void grpc_pollset_set_add_pollset_set(grpc_exec_ctx *exec_ctx, + grpc_pollset_set *bag, + grpc_pollset_set *item) { + size_t i, j; + gpr_mu_lock(&bag->mu); + if (bag->pollset_set_count == bag->pollset_set_capacity) { + bag->pollset_set_capacity = GPR_MAX(8, 2 * bag->pollset_set_capacity); + bag->pollset_sets = + gpr_realloc(bag->pollset_sets, + bag->pollset_set_capacity * sizeof(*bag->pollset_sets)); + } + bag->pollset_sets[bag->pollset_set_count++] = item; + for (i = 0, j = 0; i < bag->fd_count; i++) { + if (grpc_fd_is_orphaned(bag->fds[i])) { + GRPC_FD_UNREF(bag->fds[i], "pollset_set"); + } else { + grpc_pollset_set_add_fd(exec_ctx, item, bag->fds[i]); + bag->fds[j++] = bag->fds[i]; + } + } + bag->fd_count = j; + gpr_mu_unlock(&bag->mu); +} + +void grpc_pollset_set_del_pollset_set(grpc_exec_ctx *exec_ctx, + grpc_pollset_set *bag, + grpc_pollset_set *item) { + size_t i; + gpr_mu_lock(&bag->mu); + for (i = 0; i < bag->pollset_set_count; i++) { + if (bag->pollset_sets[i] == item) { + bag->pollset_set_count--; + GPR_SWAP(grpc_pollset_set *, bag->pollset_sets[i], + bag->pollset_sets[bag->pollset_set_count]); + break; + } + } + gpr_mu_unlock(&bag->mu); +} + +void grpc_pollset_set_add_fd(grpc_exec_ctx *exec_ctx, + grpc_pollset_set *pollset_set, grpc_fd *fd) { + size_t i; + gpr_mu_lock(&pollset_set->mu); + if (pollset_set->fd_count == pollset_set->fd_capacity) { + pollset_set->fd_capacity = GPR_MAX(8, 2 * pollset_set->fd_capacity); + pollset_set->fds = gpr_realloc( + pollset_set->fds, pollset_set->fd_capacity * sizeof(*pollset_set->fds)); + } + GRPC_FD_REF(fd, "pollset_set"); + pollset_set->fds[pollset_set->fd_count++] = fd; + for (i = 0; i < pollset_set->pollset_count; i++) { + grpc_pollset_add_fd(exec_ctx, pollset_set->pollsets[i], fd); + } + for (i = 0; i < pollset_set->pollset_set_count; i++) { + grpc_pollset_set_add_fd(exec_ctx, pollset_set->pollset_sets[i], fd); + } + gpr_mu_unlock(&pollset_set->mu); +} + +void grpc_pollset_set_del_fd(grpc_exec_ctx *exec_ctx, + grpc_pollset_set *pollset_set, grpc_fd *fd) { + size_t i; + gpr_mu_lock(&pollset_set->mu); + for (i = 0; i < pollset_set->fd_count; i++) { + if (pollset_set->fds[i] == fd) { + pollset_set->fd_count--; + GPR_SWAP(grpc_fd *, pollset_set->fds[i], + pollset_set->fds[pollset_set->fd_count]); + GRPC_FD_UNREF(fd, "pollset_set"); + break; + } + } + for (i = 0; i < pollset_set->pollset_set_count; i++) { + grpc_pollset_set_del_fd(exec_ctx, pollset_set->pollset_sets[i], fd); + } + gpr_mu_unlock(&pollset_set->mu); +} + +#endif /* GPR_POSIX_SOCKET */ diff --git a/src/core/lib/iomgr/pollset_set_posix.h b/src/core/lib/iomgr/pollset_set_posix.h new file mode 100644 index 0000000000..4e6b063c6f --- /dev/null +++ b/src/core/lib/iomgr/pollset_set_posix.h @@ -0,0 +1,45 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_POLLSET_SET_POSIX_H +#define GRPC_CORE_LIB_IOMGR_POLLSET_SET_POSIX_H + +#include "src/core/lib/iomgr/fd_posix.h" +#include "src/core/lib/iomgr/pollset_set.h" + +void grpc_pollset_set_add_fd(grpc_exec_ctx *exec_ctx, + grpc_pollset_set *pollset_set, grpc_fd *fd); +void grpc_pollset_set_del_fd(grpc_exec_ctx *exec_ctx, + grpc_pollset_set *pollset_set, grpc_fd *fd); + +#endif /* GRPC_CORE_LIB_IOMGR_POLLSET_SET_POSIX_H */ diff --git a/src/core/lib/iomgr/pollset_set_windows.c b/src/core/lib/iomgr/pollset_set_windows.c new file mode 100644 index 0000000000..0b14e446ae --- /dev/null +++ b/src/core/lib/iomgr/pollset_set_windows.c @@ -0,0 +1,60 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WINSOCK_SOCKET + +#include "src/core/lib/iomgr/pollset_set_windows.h" + +grpc_pollset_set* grpc_pollset_set_create(pollset_set) { return NULL; } + +void grpc_pollset_set_destroy(grpc_pollset_set* pollset_set) {} + +void grpc_pollset_set_add_pollset(grpc_exec_ctx* exec_ctx, + grpc_pollset_set* pollset_set, + grpc_pollset* pollset) {} + +void grpc_pollset_set_del_pollset(grpc_exec_ctx* exec_ctx, + grpc_pollset_set* pollset_set, + grpc_pollset* pollset) {} + +void grpc_pollset_set_add_pollset_set(grpc_exec_ctx* exec_ctx, + grpc_pollset_set* bag, + grpc_pollset_set* item) {} + +void grpc_pollset_set_del_pollset_set(grpc_exec_ctx* exec_ctx, + grpc_pollset_set* bag, + grpc_pollset_set* item) {} + +#endif /* GPR_WINSOCK_SOCKET */ diff --git a/src/core/lib/iomgr/pollset_set_windows.h b/src/core/lib/iomgr/pollset_set_windows.h new file mode 100644 index 0000000000..7c2cea23de --- /dev/null +++ b/src/core/lib/iomgr/pollset_set_windows.h @@ -0,0 +1,39 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_POLLSET_SET_WINDOWS_H +#define GRPC_CORE_LIB_IOMGR_POLLSET_SET_WINDOWS_H + +#include "src/core/lib/iomgr/pollset_set.h" + +#endif /* GRPC_CORE_LIB_IOMGR_POLLSET_SET_WINDOWS_H */ diff --git a/src/core/lib/iomgr/pollset_windows.c b/src/core/lib/iomgr/pollset_windows.c new file mode 100644 index 0000000000..6b339127a8 --- /dev/null +++ b/src/core/lib/iomgr/pollset_windows.c @@ -0,0 +1,240 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WINSOCK_SOCKET + +#include <grpc/support/log.h> +#include <grpc/support/thd.h> + +#include "src/core/lib/iomgr/iocp_windows.h" +#include "src/core/lib/iomgr/iomgr_internal.h" +#include "src/core/lib/iomgr/pollset.h" +#include "src/core/lib/iomgr/pollset_windows.h" + +gpr_mu grpc_polling_mu; +static grpc_pollset_worker *g_active_poller; +static grpc_pollset_worker g_global_root_worker; + +void grpc_pollset_global_init() { + gpr_mu_init(&grpc_polling_mu); + g_active_poller = NULL; + g_global_root_worker.links[GRPC_POLLSET_WORKER_LINK_GLOBAL].next = + g_global_root_worker.links[GRPC_POLLSET_WORKER_LINK_GLOBAL].prev = + &g_global_root_worker; +} + +void grpc_pollset_global_shutdown() { gpr_mu_destroy(&grpc_polling_mu); } + +static void remove_worker(grpc_pollset_worker *worker, + grpc_pollset_worker_link_type type) { + worker->links[type].prev->links[type].next = worker->links[type].next; + worker->links[type].next->links[type].prev = worker->links[type].prev; + worker->links[type].next = worker->links[type].prev = worker; +} + +static int has_workers(grpc_pollset_worker *root, + grpc_pollset_worker_link_type type) { + return root->links[type].next != root; +} + +static grpc_pollset_worker *pop_front_worker( + grpc_pollset_worker *root, grpc_pollset_worker_link_type type) { + if (has_workers(root, type)) { + grpc_pollset_worker *w = root->links[type].next; + remove_worker(w, type); + return w; + } else { + return NULL; + } +} + +static void push_front_worker(grpc_pollset_worker *root, + grpc_pollset_worker_link_type type, + grpc_pollset_worker *worker) { + worker->links[type].prev = root; + worker->links[type].next = worker->links[type].prev->links[type].next; + worker->links[type].prev->links[type].next = + worker->links[type].next->links[type].prev = worker; +} + +size_t grpc_pollset_size(void) { return sizeof(grpc_pollset); } + +/* There isn't really any such thing as a pollset under Windows, due to the + nature of the IO completion ports. We're still going to provide a minimal + set of features for the sake of the rest of grpc. But grpc_pollset_work + won't actually do any polling, and return as quickly as possible. */ + +void grpc_pollset_init(grpc_pollset *pollset, gpr_mu **mu) { + *mu = &grpc_polling_mu; + memset(pollset, 0, sizeof(*pollset)); + pollset->root_worker.links[GRPC_POLLSET_WORKER_LINK_POLLSET].next = + pollset->root_worker.links[GRPC_POLLSET_WORKER_LINK_POLLSET].prev = + &pollset->root_worker; +} + +void grpc_pollset_shutdown(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset, + grpc_closure *closure) { + pollset->shutting_down = 1; + grpc_pollset_kick(pollset, GRPC_POLLSET_KICK_BROADCAST); + if (!pollset->is_iocp_worker) { + grpc_exec_ctx_enqueue(exec_ctx, closure, true, NULL); + } else { + pollset->on_shutdown = closure; + } +} + +void grpc_pollset_destroy(grpc_pollset *pollset) {} + +void grpc_pollset_reset(grpc_pollset *pollset) { + GPR_ASSERT(pollset->shutting_down); + GPR_ASSERT( + !has_workers(&pollset->root_worker, GRPC_POLLSET_WORKER_LINK_POLLSET)); + pollset->shutting_down = 0; + pollset->is_iocp_worker = 0; + pollset->kicked_without_pollers = 0; + pollset->on_shutdown = NULL; +} + +void grpc_pollset_work(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset, + grpc_pollset_worker **worker_hdl, gpr_timespec now, + gpr_timespec deadline) { + grpc_pollset_worker worker; + *worker_hdl = &worker; + + int added_worker = 0; + worker.links[GRPC_POLLSET_WORKER_LINK_POLLSET].next = + worker.links[GRPC_POLLSET_WORKER_LINK_POLLSET].prev = + worker.links[GRPC_POLLSET_WORKER_LINK_GLOBAL].next = + worker.links[GRPC_POLLSET_WORKER_LINK_GLOBAL].prev = NULL; + worker.kicked = 0; + worker.pollset = pollset; + gpr_cv_init(&worker.cv); + if (!pollset->kicked_without_pollers && !pollset->shutting_down) { + if (g_active_poller == NULL) { + grpc_pollset_worker *next_worker; + /* become poller */ + pollset->is_iocp_worker = 1; + g_active_poller = &worker; + gpr_mu_unlock(&grpc_polling_mu); + grpc_iocp_work(exec_ctx, deadline); + grpc_exec_ctx_flush(exec_ctx); + gpr_mu_lock(&grpc_polling_mu); + pollset->is_iocp_worker = 0; + g_active_poller = NULL; + /* try to get a worker from this pollsets worker list */ + next_worker = pop_front_worker(&pollset->root_worker, + GRPC_POLLSET_WORKER_LINK_POLLSET); + if (next_worker == NULL) { + /* try to get a worker from the global list */ + next_worker = pop_front_worker(&g_global_root_worker, + GRPC_POLLSET_WORKER_LINK_GLOBAL); + } + if (next_worker != NULL) { + next_worker->kicked = 1; + gpr_cv_signal(&next_worker->cv); + } + + if (pollset->shutting_down && pollset->on_shutdown != NULL) { + grpc_exec_ctx_enqueue(exec_ctx, pollset->on_shutdown, true, NULL); + pollset->on_shutdown = NULL; + } + goto done; + } + push_front_worker(&g_global_root_worker, GRPC_POLLSET_WORKER_LINK_GLOBAL, + &worker); + push_front_worker(&pollset->root_worker, GRPC_POLLSET_WORKER_LINK_POLLSET, + &worker); + added_worker = 1; + while (!worker.kicked) { + if (gpr_cv_wait(&worker.cv, &grpc_polling_mu, deadline)) { + break; + } + } + } else { + pollset->kicked_without_pollers = 0; + } +done: + if (!grpc_closure_list_empty(exec_ctx->closure_list)) { + gpr_mu_unlock(&grpc_polling_mu); + grpc_exec_ctx_flush(exec_ctx); + gpr_mu_lock(&grpc_polling_mu); + } + if (added_worker) { + remove_worker(&worker, GRPC_POLLSET_WORKER_LINK_GLOBAL); + remove_worker(&worker, GRPC_POLLSET_WORKER_LINK_POLLSET); + } + gpr_cv_destroy(&worker.cv); + *worker_hdl = NULL; +} + +void grpc_pollset_kick(grpc_pollset *p, grpc_pollset_worker *specific_worker) { + if (specific_worker != NULL) { + if (specific_worker == GRPC_POLLSET_KICK_BROADCAST) { + for (specific_worker = + p->root_worker.links[GRPC_POLLSET_WORKER_LINK_POLLSET].next; + specific_worker != &p->root_worker; + specific_worker = + specific_worker->links[GRPC_POLLSET_WORKER_LINK_POLLSET].next) { + specific_worker->kicked = 1; + gpr_cv_signal(&specific_worker->cv); + } + p->kicked_without_pollers = 1; + if (p->is_iocp_worker) { + grpc_iocp_kick(); + } + } else { + if (p->is_iocp_worker && g_active_poller == specific_worker) { + grpc_iocp_kick(); + } else { + specific_worker->kicked = 1; + gpr_cv_signal(&specific_worker->cv); + } + } + } else { + specific_worker = + pop_front_worker(&p->root_worker, GRPC_POLLSET_WORKER_LINK_POLLSET); + if (specific_worker != NULL) { + grpc_pollset_kick(p, specific_worker); + } else if (p->is_iocp_worker) { + grpc_iocp_kick(); + } else { + p->kicked_without_pollers = 1; + } + } +} + +void grpc_kick_poller(void) { grpc_iocp_kick(); } + +#endif /* GPR_WINSOCK_SOCKET */ diff --git a/src/core/lib/iomgr/pollset_windows.h b/src/core/lib/iomgr/pollset_windows.h new file mode 100644 index 0000000000..fa9553ffea --- /dev/null +++ b/src/core/lib/iomgr/pollset_windows.h @@ -0,0 +1,75 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_POLLSET_WINDOWS_H +#define GRPC_CORE_LIB_IOMGR_POLLSET_WINDOWS_H + +#include <grpc/support/sync.h> + +#include "src/core/lib/iomgr/socket_windows.h" + +/* There isn't really any such thing as a pollset under Windows, due to the + nature of the IO completion ports. A Windows "pollset" is merely a mutex + used to synchronize with the IOCP, and workers are condition variables + used to block threads until work is ready. */ + +typedef enum { + GRPC_POLLSET_WORKER_LINK_POLLSET = 0, + GRPC_POLLSET_WORKER_LINK_GLOBAL, + GRPC_POLLSET_WORKER_LINK_TYPES +} grpc_pollset_worker_link_type; + +typedef struct grpc_pollset_worker_link { + struct grpc_pollset_worker *next; + struct grpc_pollset_worker *prev; +} grpc_pollset_worker_link; + +struct grpc_pollset; +typedef struct grpc_pollset grpc_pollset; + +typedef struct grpc_pollset_worker { + gpr_cv cv; + int kicked; + struct grpc_pollset *pollset; + grpc_pollset_worker_link links[GRPC_POLLSET_WORKER_LINK_TYPES]; +} grpc_pollset_worker; + +struct grpc_pollset { + int shutting_down; + int kicked_without_pollers; + int is_iocp_worker; + grpc_pollset_worker root_worker; + grpc_closure *on_shutdown; +}; + +#endif /* GRPC_CORE_LIB_IOMGR_POLLSET_WINDOWS_H */ diff --git a/src/core/lib/iomgr/resolve_address.h b/src/core/lib/iomgr/resolve_address.h new file mode 100644 index 0000000000..f748288685 --- /dev/null +++ b/src/core/lib/iomgr/resolve_address.h @@ -0,0 +1,72 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_RESOLVE_ADDRESS_H +#define GRPC_CORE_LIB_IOMGR_RESOLVE_ADDRESS_H + +#include <stddef.h> +#include "src/core/lib/iomgr/exec_ctx.h" +#include "src/core/lib/iomgr/iomgr.h" + +#define GRPC_MAX_SOCKADDR_SIZE 128 + +typedef struct { + char addr[GRPC_MAX_SOCKADDR_SIZE]; + size_t len; +} grpc_resolved_address; + +typedef struct { + size_t naddrs; + grpc_resolved_address *addrs; +} grpc_resolved_addresses; + +/* Async result callback: + On success: addresses is the result, and the callee must call + grpc_resolved_addresses_destroy when it's done with them + On failure: addresses is NULL */ +typedef void (*grpc_resolve_cb)(grpc_exec_ctx *exec_ctx, void *arg, + grpc_resolved_addresses *addresses); +/* Asynchronously resolve addr. Use default_port if a port isn't designated + in addr, otherwise use the port in addr. */ +/* TODO(ctiller): add a timeout here */ +void grpc_resolve_address(const char *addr, const char *default_port, + grpc_resolve_cb cb, void *arg); +/* Destroy resolved addresses */ +void grpc_resolved_addresses_destroy(grpc_resolved_addresses *addresses); + +/* Resolve addr in a blocking fashion. Returns NULL on failure. On success, + result must be freed with grpc_resolved_addresses_destroy. */ +extern grpc_resolved_addresses *(*grpc_blocking_resolve_address)( + const char *name, const char *default_port); + +#endif /* GRPC_CORE_LIB_IOMGR_RESOLVE_ADDRESS_H */ diff --git a/src/core/lib/iomgr/resolve_address_posix.c b/src/core/lib/iomgr/resolve_address_posix.c new file mode 100644 index 0000000000..ebecb39c16 --- /dev/null +++ b/src/core/lib/iomgr/resolve_address_posix.c @@ -0,0 +1,178 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> +#ifdef GPR_POSIX_SOCKET + +#include "src/core/lib/iomgr/resolve_address.h" +#include "src/core/lib/iomgr/sockaddr.h" + +#include <string.h> +#include <sys/types.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/host_port.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> +#include <grpc/support/thd.h> +#include <grpc/support/time.h> +#include <grpc/support/useful.h> +#include "src/core/lib/iomgr/executor.h" +#include "src/core/lib/iomgr/iomgr_internal.h" +#include "src/core/lib/iomgr/sockaddr_utils.h" +#include "src/core/lib/iomgr/unix_sockets_posix.h" +#include "src/core/lib/support/block_annotate.h" +#include "src/core/lib/support/string.h" + +typedef struct { + char *name; + char *default_port; + grpc_resolve_cb cb; + grpc_closure request_closure; + void *arg; +} request; + +static grpc_resolved_addresses *blocking_resolve_address_impl( + const char *name, const char *default_port) { + struct addrinfo hints; + struct addrinfo *result = NULL, *resp; + char *host; + char *port; + int s; + size_t i; + grpc_resolved_addresses *addrs = NULL; + + if (name[0] == 'u' && name[1] == 'n' && name[2] == 'i' && name[3] == 'x' && + name[4] == ':' && name[5] != 0) { + return grpc_resolve_unix_domain_address(name + 5); + } + + /* parse name, splitting it into host and port parts */ + gpr_split_host_port(name, &host, &port); + if (host == NULL) { + gpr_log(GPR_ERROR, "unparseable host:port: '%s'", name); + goto done; + } + if (port == NULL) { + if (default_port == NULL) { + gpr_log(GPR_ERROR, "no port in name '%s'", name); + goto done; + } + port = gpr_strdup(default_port); + } + + /* Call getaddrinfo */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; /* ipv4 or ipv6 */ + hints.ai_socktype = SOCK_STREAM; /* stream socket */ + hints.ai_flags = AI_PASSIVE; /* for wildcard IP address */ + + GRPC_SCHEDULING_START_BLOCKING_REGION; + s = getaddrinfo(host, port, &hints, &result); + GRPC_SCHEDULING_END_BLOCKING_REGION; + + if (s != 0) { + /* Retry if well-known service name is recognized */ + char *svc[][2] = {{"http", "80"}, {"https", "443"}}; + for (i = 0; i < GPR_ARRAY_SIZE(svc); i++) { + if (strcmp(port, svc[i][0]) == 0) { + GRPC_SCHEDULING_START_BLOCKING_REGION; + s = getaddrinfo(host, svc[i][1], &hints, &result); + GRPC_SCHEDULING_END_BLOCKING_REGION; + break; + } + } + } + + if (s != 0) { + gpr_log(GPR_ERROR, "getaddrinfo: %s", gai_strerror(s)); + goto done; + } + + /* Success path: set addrs non-NULL, fill it in */ + addrs = gpr_malloc(sizeof(grpc_resolved_addresses)); + addrs->naddrs = 0; + for (resp = result; resp != NULL; resp = resp->ai_next) { + addrs->naddrs++; + } + addrs->addrs = gpr_malloc(sizeof(grpc_resolved_address) * addrs->naddrs); + i = 0; + for (resp = result; resp != NULL; resp = resp->ai_next) { + memcpy(&addrs->addrs[i].addr, resp->ai_addr, resp->ai_addrlen); + addrs->addrs[i].len = resp->ai_addrlen; + i++; + } + +done: + gpr_free(host); + gpr_free(port); + if (result) { + freeaddrinfo(result); + } + return addrs; +} + +grpc_resolved_addresses *(*grpc_blocking_resolve_address)( + const char *name, const char *default_port) = blocking_resolve_address_impl; + +/* Callback to be passed to grpc_executor to asynch-ify + * grpc_blocking_resolve_address */ +static void do_request_thread(grpc_exec_ctx *exec_ctx, void *rp, bool success) { + request *r = rp; + grpc_resolved_addresses *resolved = + grpc_blocking_resolve_address(r->name, r->default_port); + void *arg = r->arg; + grpc_resolve_cb cb = r->cb; + gpr_free(r->name); + gpr_free(r->default_port); + cb(exec_ctx, arg, resolved); + gpr_free(r); +} + +void grpc_resolved_addresses_destroy(grpc_resolved_addresses *addrs) { + gpr_free(addrs->addrs); + gpr_free(addrs); +} + +void grpc_resolve_address(const char *name, const char *default_port, + grpc_resolve_cb cb, void *arg) { + request *r = gpr_malloc(sizeof(request)); + grpc_closure_init(&r->request_closure, do_request_thread, r); + r->name = gpr_strdup(name); + r->default_port = gpr_strdup(default_port); + r->cb = cb; + r->arg = arg; + grpc_executor_enqueue(&r->request_closure, 1); +} + +#endif diff --git a/src/core/lib/iomgr/resolve_address_windows.c b/src/core/lib/iomgr/resolve_address_windows.c new file mode 100644 index 0000000000..bde1f1b7f7 --- /dev/null +++ b/src/core/lib/iomgr/resolve_address_windows.c @@ -0,0 +1,169 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> +#ifdef GPR_WINSOCK_SOCKET + +#include "src/core/lib/iomgr/resolve_address.h" +#include "src/core/lib/iomgr/sockaddr.h" + +#include <string.h> +#include <sys/types.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/host_port.h> +#include <grpc/support/log.h> +#include <grpc/support/log_win32.h> +#include <grpc/support/string_util.h> +#include <grpc/support/thd.h> +#include <grpc/support/time.h> +#include "src/core/lib/iomgr/executor.h" +#include "src/core/lib/iomgr/iomgr_internal.h" +#include "src/core/lib/iomgr/sockaddr_utils.h" +#include "src/core/lib/support/block_annotate.h" +#include "src/core/lib/support/string.h" + +typedef struct { + char *name; + char *default_port; + grpc_resolve_cb cb; + grpc_closure request_closure; + void *arg; +} request; + +static grpc_resolved_addresses *blocking_resolve_address_impl( + const char *name, const char *default_port) { + struct addrinfo hints; + struct addrinfo *result = NULL, *resp; + char *host; + char *port; + int s; + size_t i; + grpc_resolved_addresses *addrs = NULL; + + /* parse name, splitting it into host and port parts */ + gpr_split_host_port(name, &host, &port); + if (host == NULL) { + gpr_log(GPR_ERROR, "unparseable host:port: '%s'", name); + goto done; + } + if (port == NULL) { + if (default_port == NULL) { + gpr_log(GPR_ERROR, "no port in name '%s'", name); + goto done; + } + port = gpr_strdup(default_port); + } + + /* Call getaddrinfo */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; /* ipv4 or ipv6 */ + hints.ai_socktype = SOCK_STREAM; /* stream socket */ + hints.ai_flags = AI_PASSIVE; /* for wildcard IP address */ + + GRPC_SCHEDULING_START_BLOCKING_REGION; + s = getaddrinfo(host, port, &hints, &result); + GRPC_SCHEDULING_END_BLOCKING_REGION; + if (s != 0) { + char *error_message = gpr_format_message(s); + gpr_log(GPR_ERROR, "getaddrinfo: %s", error_message); + gpr_free(error_message); + goto done; + } + + /* Success path: set addrs non-NULL, fill it in */ + addrs = gpr_malloc(sizeof(grpc_resolved_addresses)); + addrs->naddrs = 0; + for (resp = result; resp != NULL; resp = resp->ai_next) { + addrs->naddrs++; + } + addrs->addrs = gpr_malloc(sizeof(grpc_resolved_address) * addrs->naddrs); + i = 0; + for (resp = result; resp != NULL; resp = resp->ai_next) { + memcpy(&addrs->addrs[i].addr, resp->ai_addr, resp->ai_addrlen); + addrs->addrs[i].len = resp->ai_addrlen; + i++; + } + + { + for (i = 0; i < addrs->naddrs; i++) { + char *buf; + grpc_sockaddr_to_string(&buf, (struct sockaddr *)&addrs->addrs[i].addr, + 0); + gpr_free(buf); + } + } + +done: + gpr_free(host); + gpr_free(port); + if (result) { + freeaddrinfo(result); + } + return addrs; +} + +grpc_resolved_addresses *(*grpc_blocking_resolve_address)( + const char *name, const char *default_port) = blocking_resolve_address_impl; + +/* Callback to be passed to grpc_executor to asynch-ify + * grpc_blocking_resolve_address */ +static void do_request_thread(grpc_exec_ctx *exec_ctx, void *rp, bool success) { + request *r = rp; + grpc_resolved_addresses *resolved = + grpc_blocking_resolve_address(r->name, r->default_port); + void *arg = r->arg; + grpc_resolve_cb cb = r->cb; + gpr_free(r->name); + gpr_free(r->default_port); + cb(exec_ctx, arg, resolved); + gpr_free(r); +} + +void grpc_resolved_addresses_destroy(grpc_resolved_addresses *addrs) { + gpr_free(addrs->addrs); + gpr_free(addrs); +} + +void grpc_resolve_address(const char *name, const char *default_port, + grpc_resolve_cb cb, void *arg) { + request *r = gpr_malloc(sizeof(request)); + grpc_closure_init(&r->request_closure, do_request_thread, r); + r->name = gpr_strdup(name); + r->default_port = gpr_strdup(default_port); + r->cb = cb; + r->arg = arg; + grpc_executor_enqueue(&r->request_closure, 1); +} + +#endif diff --git a/src/core/lib/iomgr/sockaddr.h b/src/core/lib/iomgr/sockaddr.h new file mode 100644 index 0000000000..66a930ee6a --- /dev/null +++ b/src/core/lib/iomgr/sockaddr.h @@ -0,0 +1,47 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_SOCKADDR_H +#define GRPC_CORE_LIB_IOMGR_SOCKADDR_H + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WIN32 +#include "src/core/lib/iomgr/sockaddr_win32.h" +#endif + +#ifdef GPR_POSIX_SOCKETADDR +#include "src/core/lib/iomgr/sockaddr_posix.h" +#endif + +#endif /* GRPC_CORE_LIB_IOMGR_SOCKADDR_H */ diff --git a/src/core/lib/iomgr/sockaddr_posix.h b/src/core/lib/iomgr/sockaddr_posix.h new file mode 100644 index 0000000000..79a7467c5d --- /dev/null +++ b/src/core/lib/iomgr/sockaddr_posix.h @@ -0,0 +1,44 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_SOCKADDR_POSIX_H +#define GRPC_CORE_LIB_IOMGR_SOCKADDR_POSIX_H + +#include <arpa/inet.h> +#include <netdb.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#endif /* GRPC_CORE_LIB_IOMGR_SOCKADDR_POSIX_H */ diff --git a/src/core/lib/iomgr/sockaddr_utils.c b/src/core/lib/iomgr/sockaddr_utils.c new file mode 100644 index 0000000000..127d95c618 --- /dev/null +++ b/src/core/lib/iomgr/sockaddr_utils.c @@ -0,0 +1,227 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/iomgr/sockaddr_utils.h" + +#include <errno.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/host_port.h> +#include <grpc/support/log.h> +#include <grpc/support/port_platform.h> +#include <grpc/support/string_util.h> + +#include "src/core/lib/iomgr/unix_sockets_posix.h" +#include "src/core/lib/support/string.h" + +static const uint8_t kV4MappedPrefix[] = {0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0xff, 0xff}; + +int grpc_sockaddr_is_v4mapped(const struct sockaddr *addr, + struct sockaddr_in *addr4_out) { + GPR_ASSERT(addr != (struct sockaddr *)addr4_out); + if (addr->sa_family == AF_INET6) { + const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addr; + if (memcmp(addr6->sin6_addr.s6_addr, kV4MappedPrefix, + sizeof(kV4MappedPrefix)) == 0) { + if (addr4_out != NULL) { + /* Normalize ::ffff:0.0.0.0/96 to IPv4. */ + memset(addr4_out, 0, sizeof(*addr4_out)); + addr4_out->sin_family = AF_INET; + /* s6_addr32 would be nice, but it's non-standard. */ + memcpy(&addr4_out->sin_addr, &addr6->sin6_addr.s6_addr[12], 4); + addr4_out->sin_port = addr6->sin6_port; + } + return 1; + } + } + return 0; +} + +int grpc_sockaddr_to_v4mapped(const struct sockaddr *addr, + struct sockaddr_in6 *addr6_out) { + GPR_ASSERT(addr != (struct sockaddr *)addr6_out); + if (addr->sa_family == AF_INET) { + const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addr; + memset(addr6_out, 0, sizeof(*addr6_out)); + addr6_out->sin6_family = AF_INET6; + memcpy(&addr6_out->sin6_addr.s6_addr[0], kV4MappedPrefix, 12); + memcpy(&addr6_out->sin6_addr.s6_addr[12], &addr4->sin_addr, 4); + addr6_out->sin6_port = addr4->sin_port; + return 1; + } + return 0; +} + +int grpc_sockaddr_is_wildcard(const struct sockaddr *addr, int *port_out) { + struct sockaddr_in addr4_normalized; + if (grpc_sockaddr_is_v4mapped(addr, &addr4_normalized)) { + addr = (struct sockaddr *)&addr4_normalized; + } + if (addr->sa_family == AF_INET) { + /* Check for 0.0.0.0 */ + const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addr; + if (addr4->sin_addr.s_addr != 0) { + return 0; + } + *port_out = ntohs(addr4->sin_port); + return 1; + } else if (addr->sa_family == AF_INET6) { + /* Check for :: */ + const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addr; + int i; + for (i = 0; i < 16; i++) { + if (addr6->sin6_addr.s6_addr[i] != 0) { + return 0; + } + } + *port_out = ntohs(addr6->sin6_port); + return 1; + } else { + return 0; + } +} + +void grpc_sockaddr_make_wildcards(int port, struct sockaddr_in *wild4_out, + struct sockaddr_in6 *wild6_out) { + grpc_sockaddr_make_wildcard4(port, wild4_out); + grpc_sockaddr_make_wildcard6(port, wild6_out); +} + +void grpc_sockaddr_make_wildcard4(int port, struct sockaddr_in *wild_out) { + GPR_ASSERT(port >= 0 && port < 65536); + memset(wild_out, 0, sizeof(*wild_out)); + wild_out->sin_family = AF_INET; + wild_out->sin_port = htons((uint16_t)port); +} + +void grpc_sockaddr_make_wildcard6(int port, struct sockaddr_in6 *wild_out) { + GPR_ASSERT(port >= 0 && port < 65536); + memset(wild_out, 0, sizeof(*wild_out)); + wild_out->sin6_family = AF_INET6; + wild_out->sin6_port = htons((uint16_t)port); +} + +int grpc_sockaddr_to_string(char **out, const struct sockaddr *addr, + int normalize) { + const int save_errno = errno; + struct sockaddr_in addr_normalized; + char ntop_buf[INET6_ADDRSTRLEN]; + const void *ip = NULL; + int port; + int ret; + + *out = NULL; + if (normalize && grpc_sockaddr_is_v4mapped(addr, &addr_normalized)) { + addr = (const struct sockaddr *)&addr_normalized; + } + if (addr->sa_family == AF_INET) { + const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addr; + ip = &addr4->sin_addr; + port = ntohs(addr4->sin_port); + } else if (addr->sa_family == AF_INET6) { + const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addr; + ip = &addr6->sin6_addr; + port = ntohs(addr6->sin6_port); + } + /* Windows inet_ntop wants a mutable ip pointer */ + if (ip != NULL && + inet_ntop(addr->sa_family, (void *)ip, ntop_buf, sizeof(ntop_buf)) != + NULL) { + ret = gpr_join_host_port(out, ntop_buf, port); + } else { + ret = gpr_asprintf(out, "(sockaddr family=%d)", addr->sa_family); + } + /* This is probably redundant, but we wouldn't want to log the wrong error. */ + errno = save_errno; + return ret; +} + +char *grpc_sockaddr_to_uri(const struct sockaddr *addr) { + char *temp; + char *result; + struct sockaddr_in addr_normalized; + + if (grpc_sockaddr_is_v4mapped(addr, &addr_normalized)) { + addr = (const struct sockaddr *)&addr_normalized; + } + + switch (addr->sa_family) { + case AF_INET: + grpc_sockaddr_to_string(&temp, addr, 0); + gpr_asprintf(&result, "ipv4:%s", temp); + gpr_free(temp); + return result; + case AF_INET6: + grpc_sockaddr_to_string(&temp, addr, 0); + gpr_asprintf(&result, "ipv6:%s", temp); + gpr_free(temp); + return result; + default: + return grpc_sockaddr_to_uri_unix_if_possible(addr); + } +} + +int grpc_sockaddr_get_port(const struct sockaddr *addr) { + switch (addr->sa_family) { + case AF_INET: + return ntohs(((struct sockaddr_in *)addr)->sin_port); + case AF_INET6: + return ntohs(((struct sockaddr_in6 *)addr)->sin6_port); + default: + if (grpc_is_unix_socket(addr)) { + return 1; + } + gpr_log(GPR_ERROR, "Unknown socket family %d in grpc_sockaddr_get_port", + addr->sa_family); + return 0; + } +} + +int grpc_sockaddr_set_port(const struct sockaddr *addr, int port) { + switch (addr->sa_family) { + case AF_INET: + GPR_ASSERT(port >= 0 && port < 65536); + ((struct sockaddr_in *)addr)->sin_port = htons((uint16_t)port); + return 1; + case AF_INET6: + GPR_ASSERT(port >= 0 && port < 65536); + ((struct sockaddr_in6 *)addr)->sin6_port = htons((uint16_t)port); + return 1; + default: + gpr_log(GPR_ERROR, "Unknown socket family %d in grpc_sockaddr_set_port", + addr->sa_family); + return 0; + } +} diff --git a/src/core/lib/iomgr/sockaddr_utils.h b/src/core/lib/iomgr/sockaddr_utils.h new file mode 100644 index 0000000000..20a3e3bec3 --- /dev/null +++ b/src/core/lib/iomgr/sockaddr_utils.h @@ -0,0 +1,89 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_SOCKADDR_UTILS_H +#define GRPC_CORE_LIB_IOMGR_SOCKADDR_UTILS_H + +#include "src/core/lib/iomgr/sockaddr.h" + +/* Returns true if addr is an IPv4-mapped IPv6 address within the + ::ffff:0.0.0.0/96 range, or false otherwise. + + If addr4_out is non-NULL, the inner IPv4 address will be copied here when + returning true. */ +int grpc_sockaddr_is_v4mapped(const struct sockaddr *addr, + struct sockaddr_in *addr4_out); + +/* If addr is an AF_INET address, writes the corresponding ::ffff:0.0.0.0/96 + address to addr6_out and returns true. Otherwise returns false. */ +int grpc_sockaddr_to_v4mapped(const struct sockaddr *addr, + struct sockaddr_in6 *addr6_out); + +/* If addr is ::, 0.0.0.0, or ::ffff:0.0.0.0, writes the port number to + *port_out (if not NULL) and returns true, otherwise returns false. */ +int grpc_sockaddr_is_wildcard(const struct sockaddr *addr, int *port_out); + +/* Writes 0.0.0.0:port and [::]:port to separate sockaddrs. */ +void grpc_sockaddr_make_wildcards(int port, struct sockaddr_in *wild4_out, + struct sockaddr_in6 *wild6_out); + +/* Writes 0.0.0.0:port. */ +void grpc_sockaddr_make_wildcard4(int port, struct sockaddr_in *wild_out); + +/* Writes [::]:port. */ +void grpc_sockaddr_make_wildcard6(int port, struct sockaddr_in6 *wild_out); + +/* Return the IP port number of a sockaddr */ +int grpc_sockaddr_get_port(const struct sockaddr *addr); + +/* Set IP port number of a sockaddr */ +int grpc_sockaddr_set_port(const struct sockaddr *addr, int port); + +/* Converts a sockaddr into a newly-allocated human-readable string. + + Currently, only the AF_INET and AF_INET6 families are recognized. + If the normalize flag is enabled, ::ffff:0.0.0.0/96 IPv6 addresses are + displayed as plain IPv4. + + Usage is similar to gpr_asprintf: returns the number of bytes written + (excluding the final '\0'), and *out points to a string which must later be + destroyed using gpr_free(). + + In the unlikely event of an error, returns -1 and sets *out to NULL. + The existing value of errno is always preserved. */ +int grpc_sockaddr_to_string(char **out, const struct sockaddr *addr, + int normalize); + +char *grpc_sockaddr_to_uri(const struct sockaddr *addr); + +#endif /* GRPC_CORE_LIB_IOMGR_SOCKADDR_UTILS_H */ diff --git a/src/core/lib/iomgr/sockaddr_win32.h b/src/core/lib/iomgr/sockaddr_win32.h new file mode 100644 index 0000000000..2dd7111240 --- /dev/null +++ b/src/core/lib/iomgr/sockaddr_win32.h @@ -0,0 +1,43 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_SOCKADDR_WIN32_H +#define GRPC_CORE_LIB_IOMGR_SOCKADDR_WIN32_H + +#include <winsock2.h> +#include <ws2tcpip.h> + +// must be included after the above +#include <mswsock.h> + +#endif /* GRPC_CORE_LIB_IOMGR_SOCKADDR_WIN32_H */ diff --git a/src/core/lib/iomgr/socket_utils_common_posix.c b/src/core/lib/iomgr/socket_utils_common_posix.c new file mode 100644 index 0000000000..9dbc2784e4 --- /dev/null +++ b/src/core/lib/iomgr/socket_utils_common_posix.c @@ -0,0 +1,208 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_SOCKET + +#include "src/core/lib/iomgr/socket_utils_posix.h" + +#include <arpa/inet.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include <grpc/support/host_port.h> +#include <grpc/support/log.h> +#include <grpc/support/port_platform.h> +#include <grpc/support/sync.h> +#include "src/core/lib/iomgr/sockaddr_utils.h" +#include "src/core/lib/support/string.h" + +/* set a socket to non blocking mode */ +int grpc_set_socket_nonblocking(int fd, int non_blocking) { + int oldflags = fcntl(fd, F_GETFL, 0); + if (oldflags < 0) { + return 0; + } + + if (non_blocking) { + oldflags |= O_NONBLOCK; + } else { + oldflags &= ~O_NONBLOCK; + } + + if (fcntl(fd, F_SETFL, oldflags) != 0) { + return 0; + } + + return 1; +} + +int grpc_set_socket_no_sigpipe_if_possible(int fd) { +#ifdef GPR_HAVE_SO_NOSIGPIPE + int val = 1; + int newval; + socklen_t intlen = sizeof(newval); + return 0 == setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &val, sizeof(val)) && + 0 == getsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &newval, &intlen) && + (newval != 0) == val; +#else + return 1; +#endif +} + +/* set a socket to close on exec */ +int grpc_set_socket_cloexec(int fd, int close_on_exec) { + int oldflags = fcntl(fd, F_GETFD, 0); + if (oldflags < 0) { + return 0; + } + + if (close_on_exec) { + oldflags |= FD_CLOEXEC; + } else { + oldflags &= ~FD_CLOEXEC; + } + + if (fcntl(fd, F_SETFD, oldflags) != 0) { + return 0; + } + + return 1; +} + +/* set a socket to reuse old addresses */ +int grpc_set_socket_reuse_addr(int fd, int reuse) { + int val = (reuse != 0); + int newval; + socklen_t intlen = sizeof(newval); + return 0 == setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) && + 0 == getsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &newval, &intlen) && + (newval != 0) == val; +} + +/* disable nagle */ +int grpc_set_socket_low_latency(int fd, int low_latency) { + int val = (low_latency != 0); + int newval; + socklen_t intlen = sizeof(newval); + return 0 == setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) && + 0 == getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &newval, &intlen) && + (newval != 0) == val; +} + +static gpr_once g_probe_ipv6_once = GPR_ONCE_INIT; +static int g_ipv6_loopback_available; + +static void probe_ipv6_once(void) { + int fd = socket(AF_INET6, SOCK_STREAM, 0); + g_ipv6_loopback_available = 0; + if (fd < 0) { + gpr_log(GPR_INFO, "Disabling AF_INET6 sockets because socket() failed."); + } else { + struct sockaddr_in6 addr; + memset(&addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; + addr.sin6_addr.s6_addr[15] = 1; /* [::1]:0 */ + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) == 0) { + g_ipv6_loopback_available = 1; + } else { + gpr_log(GPR_INFO, + "Disabling AF_INET6 sockets because ::1 is not available."); + } + close(fd); + } +} + +int grpc_ipv6_loopback_available(void) { + gpr_once_init(&g_probe_ipv6_once, probe_ipv6_once); + return g_ipv6_loopback_available; +} + +/* This should be 0 in production, but it may be enabled for testing or + debugging purposes, to simulate an environment where IPv6 sockets can't + also speak IPv4. */ +int grpc_forbid_dualstack_sockets_for_testing = 0; + +static int set_socket_dualstack(int fd) { + if (!grpc_forbid_dualstack_sockets_for_testing) { + const int off = 0; + return 0 == setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off)); + } else { + /* Force an IPv6-only socket, for testing purposes. */ + const int on = 1; + setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); + return 0; + } +} + +int grpc_create_dualstack_socket(const struct sockaddr *addr, int type, + int protocol, grpc_dualstack_mode *dsmode) { + int family = addr->sa_family; + if (family == AF_INET6) { + int fd; + if (grpc_ipv6_loopback_available()) { + fd = socket(family, type, protocol); + } else { + fd = -1; + errno = EAFNOSUPPORT; + } + /* Check if we've got a valid dualstack socket. */ + if (fd >= 0 && set_socket_dualstack(fd)) { + *dsmode = GRPC_DSMODE_DUALSTACK; + return fd; + } + /* If this isn't an IPv4 address, then return whatever we've got. */ + if (!grpc_sockaddr_is_v4mapped(addr, NULL)) { + *dsmode = GRPC_DSMODE_IPV6; + return fd; + } + /* Fall back to AF_INET. */ + if (fd >= 0) { + close(fd); + } + family = AF_INET; + } + *dsmode = family == AF_INET ? GRPC_DSMODE_IPV4 : GRPC_DSMODE_NONE; + return socket(family, type, protocol); +} + +#endif diff --git a/src/core/lib/iomgr/socket_utils_linux.c b/src/core/lib/iomgr/socket_utils_linux.c new file mode 100644 index 0000000000..e7dfe892ca --- /dev/null +++ b/src/core/lib/iomgr/socket_utils_linux.c @@ -0,0 +1,51 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_LINUX_SOCKETUTILS + +#include "src/core/lib/iomgr/socket_utils_posix.h" + +#include <sys/socket.h> +#include <sys/types.h> + +int grpc_accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, + int nonblock, int cloexec) { + int flags = 0; + flags |= nonblock ? SOCK_NONBLOCK : 0; + flags |= cloexec ? SOCK_CLOEXEC : 0; + return accept4(sockfd, addr, addrlen, flags); +} + +#endif diff --git a/src/core/lib/iomgr/socket_utils_posix.c b/src/core/lib/iomgr/socket_utils_posix.c new file mode 100644 index 0000000000..b2fa00c5c1 --- /dev/null +++ b/src/core/lib/iomgr/socket_utils_posix.c @@ -0,0 +1,70 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_SOCKETUTILS + +#include "src/core/lib/iomgr/socket_utils_posix.h" + +#include <fcntl.h> +#include <sys/socket.h> +#include <unistd.h> + +#include <grpc/support/log.h> + +int grpc_accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, + int nonblock, int cloexec) { + int fd, flags; + + fd = accept(sockfd, addr, addrlen); + if (fd >= 0) { + if (nonblock) { + flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) goto close_and_error; + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) != 0) goto close_and_error; + } + if (cloexec) { + flags = fcntl(fd, F_GETFD, 0); + if (flags < 0) goto close_and_error; + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) != 0) goto close_and_error; + } + } + return fd; + +close_and_error: + close(fd); + return -1; +} + +#endif /* GPR_POSIX_SOCKETUTILS */ diff --git a/src/core/lib/iomgr/socket_utils_posix.h b/src/core/lib/iomgr/socket_utils_posix.h new file mode 100644 index 0000000000..063f298d72 --- /dev/null +++ b/src/core/lib/iomgr/socket_utils_posix.h @@ -0,0 +1,113 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_SOCKET_UTILS_POSIX_H +#define GRPC_CORE_LIB_IOMGR_SOCKET_UTILS_POSIX_H + +#include <sys/socket.h> +#include <unistd.h> + +/* a wrapper for accept or accept4 */ +int grpc_accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, + int nonblock, int cloexec); + +/* set a socket to non blocking mode */ +int grpc_set_socket_nonblocking(int fd, int non_blocking); + +/* set a socket to close on exec */ +int grpc_set_socket_cloexec(int fd, int close_on_exec); + +/* set a socket to reuse old addresses */ +int grpc_set_socket_reuse_addr(int fd, int reuse); + +/* disable nagle */ +int grpc_set_socket_low_latency(int fd, int low_latency); + +/* Returns true if this system can create AF_INET6 sockets bound to ::1. + The value is probed once, and cached for the life of the process. + + This is more restrictive than checking for socket(AF_INET6) to succeed, + because Linux with "net.ipv6.conf.all.disable_ipv6 = 1" is able to create + and bind IPv6 sockets, but cannot connect to a getsockname() of [::]:port + without a valid loopback interface. Rather than expose this half-broken + state to library users, we turn off IPv6 sockets. */ +int grpc_ipv6_loopback_available(void); + +/* Tries to set SO_NOSIGPIPE if available on this platform. + Returns 1 on success, 0 on failure. + If SO_NO_SIGPIPE is not available, returns 1. */ +int grpc_set_socket_no_sigpipe_if_possible(int fd); + +/* An enum to keep track of IPv4/IPv6 socket modes. + + Currently, this information is only used when a socket is first created, but + in the future we may wish to store it alongside the fd. This would let calls + like sendto() know which family to use without asking the kernel first. */ +typedef enum grpc_dualstack_mode { + /* Uninitialized, or a non-IP socket. */ + GRPC_DSMODE_NONE, + /* AF_INET only. */ + GRPC_DSMODE_IPV4, + /* AF_INET6 only, because IPV6_V6ONLY could not be cleared. */ + GRPC_DSMODE_IPV6, + /* AF_INET6, which also supports ::ffff-mapped IPv4 addresses. */ + GRPC_DSMODE_DUALSTACK +} grpc_dualstack_mode; + +/* Only tests should use this flag. */ +extern int grpc_forbid_dualstack_sockets_for_testing; + +/* Creates a new socket for connecting to (or listening on) an address. + + If addr is AF_INET6, this creates an IPv6 socket first. If that fails, + and addr is within ::ffff:0.0.0.0/96, then it automatically falls back to + an IPv4 socket. + + If addr is AF_INET, AF_UNIX, or anything else, then this is similar to + calling socket() directly. + + Returns an fd on success, otherwise returns -1 with errno set to the result + of a failed socket() call. + + The *dsmode output indicates which address family was actually created. + The recommended way to use this is: + - First convert to IPv6 using grpc_sockaddr_to_v4mapped(). + - Create the socket. + - If *dsmode is IPV4, use grpc_sockaddr_is_v4mapped() to convert back to + IPv4, so that bind() or connect() see the correct family. + Also, it's important to distinguish between DUALSTACK and IPV6 when + listening on the [::] wildcard address. */ +int grpc_create_dualstack_socket(const struct sockaddr *addr, int type, + int protocol, grpc_dualstack_mode *dsmode); + +#endif /* GRPC_CORE_LIB_IOMGR_SOCKET_UTILS_POSIX_H */ diff --git a/src/core/lib/iomgr/socket_windows.c b/src/core/lib/iomgr/socket_windows.c new file mode 100644 index 0000000000..1023a6d4f8 --- /dev/null +++ b/src/core/lib/iomgr/socket_windows.c @@ -0,0 +1,100 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WINSOCK_SOCKET + +#include <winsock2.h> + +// must be included after winsock2.h +#include <mswsock.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/log_win32.h> +#include <grpc/support/string_util.h> + +#include "src/core/lib/iomgr/iocp_windows.h" +#include "src/core/lib/iomgr/iomgr_internal.h" +#include "src/core/lib/iomgr/pollset.h" +#include "src/core/lib/iomgr/pollset_windows.h" +#include "src/core/lib/iomgr/socket_windows.h" + +grpc_winsocket *grpc_winsocket_create(SOCKET socket, const char *name) { + char *final_name; + grpc_winsocket *r = gpr_malloc(sizeof(grpc_winsocket)); + memset(r, 0, sizeof(grpc_winsocket)); + r->socket = socket; + gpr_mu_init(&r->state_mu); + gpr_asprintf(&final_name, "%s:socket=0x%p", name, r); + grpc_iomgr_register_object(&r->iomgr_object, final_name); + gpr_free(final_name); + grpc_iocp_add_socket(r); + return r; +} + +/* Schedule a shutdown of the socket operations. Will call the pending + operations to abort them. We need to do that this way because of the + various callsites of that function, which happens to be in various + mutex hold states, and that'd be unsafe to call them directly. */ +void grpc_winsocket_shutdown(grpc_winsocket *winsocket) { + /* Grab the function pointer for DisconnectEx for that specific socket. + It may change depending on the interface. */ + int status; + GUID guid = WSAID_DISCONNECTEX; + LPFN_DISCONNECTEX DisconnectEx; + DWORD ioctl_num_bytes; + + status = WSAIoctl(winsocket->socket, SIO_GET_EXTENSION_FUNCTION_POINTER, + &guid, sizeof(guid), &DisconnectEx, sizeof(DisconnectEx), + &ioctl_num_bytes, NULL, NULL); + + if (status == 0) { + DisconnectEx(winsocket->socket, NULL, 0, 0); + } else { + char *utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, "Unable to retrieve DisconnectEx pointer : %s", + utf8_message); + gpr_free(utf8_message); + } + closesocket(winsocket->socket); +} + +void grpc_winsocket_destroy(grpc_winsocket *winsocket) { + grpc_iomgr_unregister_object(&winsocket->iomgr_object); + gpr_mu_destroy(&winsocket->state_mu); + gpr_free(winsocket); +} + +#endif /* GPR_WINSOCK_SOCKET */ diff --git a/src/core/lib/iomgr/socket_windows.h b/src/core/lib/iomgr/socket_windows.h new file mode 100644 index 0000000000..74447896c9 --- /dev/null +++ b/src/core/lib/iomgr/socket_windows.h @@ -0,0 +1,111 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_SOCKET_WINDOWS_H +#define GRPC_CORE_LIB_IOMGR_SOCKET_WINDOWS_H + +#include <grpc/support/port_platform.h> +#include <winsock2.h> + +#include <grpc/support/atm.h> +#include <grpc/support/sync.h> + +#include "src/core/lib/iomgr/exec_ctx.h" +#include "src/core/lib/iomgr/iomgr_internal.h" + +/* This holds the data for an outstanding read or write on a socket. + The mutex to protect the concurrent access to that data is the one + inside the winsocket wrapper. */ +typedef struct grpc_winsocket_callback_info { + /* This is supposed to be a WSAOVERLAPPED, but in order to get that + definition, we need to include ws2tcpip.h, which needs to be included + from the top, otherwise it'll clash with a previous inclusion of + windows.h that in turns includes winsock.h. If anyone knows a way + to do it properly, feel free to send a patch. */ + OVERLAPPED overlapped; + /* The callback information for the pending operation. May be empty if the + caller hasn't registered a callback yet. */ + grpc_closure *closure; + /* A boolean to describe if the IO Completion Port got a notification for + that operation. This will happen if the operation completed before the + called had time to register a callback. We could avoid that behavior + altogether by forcing the caller to always register its callback before + proceeding queue an operation, but it is frequent for an IO Completion + Port to trigger quickly. This way we avoid a context switch for calling + the callback. We also simplify the read / write operations to avoid having + to hold a mutex for a long amount of time. */ + int has_pending_iocp; + /* The results of the overlapped operation. */ + DWORD bytes_transfered; + int wsa_error; +} grpc_winsocket_callback_info; + +/* This is a wrapper to a Windows socket. A socket can have one outstanding + read, and one outstanding write. Doing an asynchronous accept means waiting + for a read operation. Doing an asynchronous connect means waiting for a + write operation. These are completely arbitrary ties between the operation + and the kind of event, because we can have one overlapped per pending + operation, whichever its nature is. So we could have more dedicated pending + operation callbacks for connect and listen. But given the scope of listen + and accept, we don't need to go to that extent and waste memory. Also, this + is closer to what happens in posix world. */ +typedef struct grpc_winsocket { + SOCKET socket; + + grpc_winsocket_callback_info write_info; + grpc_winsocket_callback_info read_info; + + gpr_mu state_mu; + + /* You can't add the same socket twice to the same IO Completion Port. + This prevents that. */ + int added_to_iocp; + + grpc_closure shutdown_closure; + + /* A label for iomgr to track outstanding objects */ + grpc_iomgr_object iomgr_object; +} grpc_winsocket; + +/* Create a wrapped windows handle. This takes ownership of it, meaning that + it will be responsible for closing it. */ +grpc_winsocket *grpc_winsocket_create(SOCKET socket, const char *name); + +/* Initiate an asynchronous shutdown of the socket. Will call off any pending + operation to cancel them. */ +void grpc_winsocket_shutdown(grpc_winsocket *socket); + +/* Destroy a socket. Should only be called if there's no pending operation. */ +void grpc_winsocket_destroy(grpc_winsocket *socket); + +#endif /* GRPC_CORE_LIB_IOMGR_SOCKET_WINDOWS_H */ diff --git a/src/core/lib/iomgr/tcp_client.h b/src/core/lib/iomgr/tcp_client.h new file mode 100644 index 0000000000..6bbe26445a --- /dev/null +++ b/src/core/lib/iomgr/tcp_client.h @@ -0,0 +1,53 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_TCP_CLIENT_H +#define GRPC_CORE_LIB_IOMGR_TCP_CLIENT_H + +#include <grpc/support/time.h> +#include "src/core/lib/iomgr/endpoint.h" +#include "src/core/lib/iomgr/pollset_set.h" +#include "src/core/lib/iomgr/sockaddr.h" + +/* Asynchronously connect to an address (specified as (addr, len)), and call + cb with arg and the completed connection when done (or call cb with arg and + NULL on failure). + interested_parties points to a set of pollsets that would be interested + in this connection being established (in order to continue their work) */ +void grpc_tcp_client_connect(grpc_exec_ctx *exec_ctx, grpc_closure *on_connect, + grpc_endpoint **endpoint, + grpc_pollset_set *interested_parties, + const struct sockaddr *addr, size_t addr_len, + gpr_timespec deadline); + +#endif /* GRPC_CORE_LIB_IOMGR_TCP_CLIENT_H */ diff --git a/src/core/lib/iomgr/tcp_client_posix.c b/src/core/lib/iomgr/tcp_client_posix.c new file mode 100644 index 0000000000..b8ef643298 --- /dev/null +++ b/src/core/lib/iomgr/tcp_client_posix.c @@ -0,0 +1,306 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_SOCKET + +#include "src/core/lib/iomgr/tcp_client.h" + +#include <errno.h> +#include <netinet/in.h> +#include <string.h> +#include <unistd.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> +#include <grpc/support/time.h> + +#include "src/core/lib/iomgr/iomgr_posix.h" +#include "src/core/lib/iomgr/pollset_posix.h" +#include "src/core/lib/iomgr/pollset_set_posix.h" +#include "src/core/lib/iomgr/sockaddr_utils.h" +#include "src/core/lib/iomgr/socket_utils_posix.h" +#include "src/core/lib/iomgr/tcp_posix.h" +#include "src/core/lib/iomgr/timer.h" +#include "src/core/lib/iomgr/unix_sockets_posix.h" +#include "src/core/lib/support/string.h" + +extern int grpc_tcp_trace; + +typedef struct { + gpr_mu mu; + grpc_fd *fd; + gpr_timespec deadline; + grpc_timer alarm; + int refs; + grpc_closure write_closure; + grpc_pollset_set *interested_parties; + char *addr_str; + grpc_endpoint **ep; + grpc_closure *closure; +} async_connect; + +static int prepare_socket(const struct sockaddr *addr, int fd) { + if (fd < 0) { + goto error; + } + + if (!grpc_set_socket_nonblocking(fd, 1) || !grpc_set_socket_cloexec(fd, 1) || + (!grpc_is_unix_socket(addr) && !grpc_set_socket_low_latency(fd, 1)) || + !grpc_set_socket_no_sigpipe_if_possible(fd)) { + gpr_log(GPR_ERROR, "Unable to configure socket %d: %s", fd, + strerror(errno)); + goto error; + } + return 1; + +error: + if (fd >= 0) { + close(fd); + } + return 0; +} + +static void tc_on_alarm(grpc_exec_ctx *exec_ctx, void *acp, bool success) { + int done; + async_connect *ac = acp; + if (grpc_tcp_trace) { + gpr_log(GPR_DEBUG, "CLIENT_CONNECT: %s: on_alarm: success=%d", ac->addr_str, + success); + } + gpr_mu_lock(&ac->mu); + if (ac->fd != NULL) { + grpc_fd_shutdown(exec_ctx, ac->fd); + } + done = (--ac->refs == 0); + gpr_mu_unlock(&ac->mu); + if (done) { + gpr_mu_destroy(&ac->mu); + gpr_free(ac->addr_str); + gpr_free(ac); + } +} + +static void on_writable(grpc_exec_ctx *exec_ctx, void *acp, bool success) { + async_connect *ac = acp; + int so_error = 0; + socklen_t so_error_size; + int err; + int done; + grpc_endpoint **ep = ac->ep; + grpc_closure *closure = ac->closure; + grpc_fd *fd; + + if (grpc_tcp_trace) { + gpr_log(GPR_DEBUG, "CLIENT_CONNECT: %s: on_writable: success=%d", + ac->addr_str, success); + } + + gpr_mu_lock(&ac->mu); + GPR_ASSERT(ac->fd); + fd = ac->fd; + ac->fd = NULL; + gpr_mu_unlock(&ac->mu); + + grpc_timer_cancel(exec_ctx, &ac->alarm); + + gpr_mu_lock(&ac->mu); + if (success) { + do { + so_error_size = sizeof(so_error); + err = getsockopt(fd->fd, SOL_SOCKET, SO_ERROR, &so_error, &so_error_size); + } while (err < 0 && errno == EINTR); + if (err < 0) { + gpr_log(GPR_ERROR, "failed to connect to '%s': getsockopt(ERROR): %s", + ac->addr_str, strerror(errno)); + goto finish; + } else if (so_error != 0) { + if (so_error == ENOBUFS) { + /* We will get one of these errors if we have run out of + memory in the kernel for the data structures allocated + when you connect a socket. If this happens it is very + likely that if we wait a little bit then try again the + connection will work (since other programs or this + program will close their network connections and free up + memory). This does _not_ indicate that there is anything + wrong with the server we are connecting to, this is a + local problem. + + If you are looking at this code, then chances are that + your program or another program on the same computer + opened too many network connections. The "easy" fix: + don't do that! */ + gpr_log(GPR_ERROR, "kernel out of buffers"); + gpr_mu_unlock(&ac->mu); + grpc_fd_notify_on_write(exec_ctx, fd, &ac->write_closure); + return; + } else { + switch (so_error) { + case ECONNREFUSED: + gpr_log( + GPR_ERROR, + "failed to connect to '%s': socket error: connection refused", + ac->addr_str); + break; + default: + gpr_log(GPR_ERROR, "failed to connect to '%s': socket error: %d", + ac->addr_str, so_error); + break; + } + goto finish; + } + } else { + grpc_pollset_set_del_fd(exec_ctx, ac->interested_parties, fd); + *ep = grpc_tcp_create(fd, GRPC_TCP_DEFAULT_READ_SLICE_SIZE, ac->addr_str); + fd = NULL; + goto finish; + } + } else { + gpr_log(GPR_ERROR, "failed to connect to '%s': timeout occurred", + ac->addr_str); + goto finish; + } + + GPR_UNREACHABLE_CODE(return ); + +finish: + if (fd != NULL) { + grpc_pollset_set_del_fd(exec_ctx, ac->interested_parties, fd); + grpc_fd_orphan(exec_ctx, fd, NULL, NULL, "tcp_client_orphan"); + fd = NULL; + } + done = (--ac->refs == 0); + gpr_mu_unlock(&ac->mu); + if (done) { + gpr_mu_destroy(&ac->mu); + gpr_free(ac->addr_str); + gpr_free(ac); + } + grpc_exec_ctx_enqueue(exec_ctx, closure, *ep != NULL, NULL); +} + +void grpc_tcp_client_connect(grpc_exec_ctx *exec_ctx, grpc_closure *closure, + grpc_endpoint **ep, + grpc_pollset_set *interested_parties, + const struct sockaddr *addr, size_t addr_len, + gpr_timespec deadline) { + int fd; + grpc_dualstack_mode dsmode; + int err; + async_connect *ac; + struct sockaddr_in6 addr6_v4mapped; + struct sockaddr_in addr4_copy; + grpc_fd *fdobj; + char *name; + char *addr_str; + + *ep = NULL; + + /* Use dualstack sockets where available. */ + if (grpc_sockaddr_to_v4mapped(addr, &addr6_v4mapped)) { + addr = (const struct sockaddr *)&addr6_v4mapped; + addr_len = sizeof(addr6_v4mapped); + } + + fd = grpc_create_dualstack_socket(addr, SOCK_STREAM, 0, &dsmode); + if (fd < 0) { + gpr_log(GPR_ERROR, "Unable to create socket: %s", strerror(errno)); + } + if (dsmode == GRPC_DSMODE_IPV4) { + /* If we got an AF_INET socket, map the address back to IPv4. */ + GPR_ASSERT(grpc_sockaddr_is_v4mapped(addr, &addr4_copy)); + addr = (struct sockaddr *)&addr4_copy; + addr_len = sizeof(addr4_copy); + } + if (!prepare_socket(addr, fd)) { + grpc_exec_ctx_enqueue(exec_ctx, closure, false, NULL); + return; + } + + do { + GPR_ASSERT(addr_len < ~(socklen_t)0); + err = connect(fd, addr, (socklen_t)addr_len); + } while (err < 0 && errno == EINTR); + + addr_str = grpc_sockaddr_to_uri(addr); + gpr_asprintf(&name, "tcp-client:%s", addr_str); + + fdobj = grpc_fd_create(fd, name); + + if (err >= 0) { + *ep = grpc_tcp_create(fdobj, GRPC_TCP_DEFAULT_READ_SLICE_SIZE, addr_str); + grpc_exec_ctx_enqueue(exec_ctx, closure, true, NULL); + goto done; + } + + if (errno != EWOULDBLOCK && errno != EINPROGRESS) { + gpr_log(GPR_ERROR, "connect error to '%s': %s", addr_str, strerror(errno)); + grpc_fd_orphan(exec_ctx, fdobj, NULL, NULL, "tcp_client_connect_error"); + grpc_exec_ctx_enqueue(exec_ctx, closure, false, NULL); + goto done; + } + + grpc_pollset_set_add_fd(exec_ctx, interested_parties, fdobj); + + ac = gpr_malloc(sizeof(async_connect)); + ac->closure = closure; + ac->ep = ep; + ac->fd = fdobj; + ac->interested_parties = interested_parties; + ac->addr_str = addr_str; + addr_str = NULL; + gpr_mu_init(&ac->mu); + ac->refs = 2; + ac->write_closure.cb = on_writable; + ac->write_closure.cb_arg = ac; + + if (grpc_tcp_trace) { + gpr_log(GPR_DEBUG, "CLIENT_CONNECT: %s: asynchronously connecting", + ac->addr_str); + } + + gpr_mu_lock(&ac->mu); + grpc_timer_init(exec_ctx, &ac->alarm, + gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC), + tc_on_alarm, ac, gpr_now(GPR_CLOCK_MONOTONIC)); + grpc_fd_notify_on_write(exec_ctx, ac->fd, &ac->write_closure); + gpr_mu_unlock(&ac->mu); + +done: + gpr_free(name); + gpr_free(addr_str); +} + +#endif diff --git a/src/core/lib/iomgr/tcp_client_windows.c b/src/core/lib/iomgr/tcp_client_windows.c new file mode 100644 index 0000000000..86b8d58975 --- /dev/null +++ b/src/core/lib/iomgr/tcp_client_windows.c @@ -0,0 +1,221 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WINSOCK_SOCKET + +#include "src/core/lib/iomgr/sockaddr_win32.h" + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/log_win32.h> +#include <grpc/support/slice_buffer.h> +#include <grpc/support/useful.h> + +#include "src/core/lib/iomgr/iocp_windows.h" +#include "src/core/lib/iomgr/sockaddr.h" +#include "src/core/lib/iomgr/sockaddr_utils.h" +#include "src/core/lib/iomgr/socket_windows.h" +#include "src/core/lib/iomgr/tcp_client.h" +#include "src/core/lib/iomgr/tcp_windows.h" +#include "src/core/lib/iomgr/timer.h" + +typedef struct { + grpc_closure *on_done; + gpr_mu mu; + grpc_winsocket *socket; + gpr_timespec deadline; + grpc_timer alarm; + char *addr_name; + int refs; + grpc_closure on_connect; + grpc_endpoint **endpoint; +} async_connect; + +static void async_connect_unlock_and_cleanup(async_connect *ac) { + int done = (--ac->refs == 0); + gpr_mu_unlock(&ac->mu); + if (done) { + if (ac->socket != NULL) grpc_winsocket_destroy(ac->socket); + gpr_mu_destroy(&ac->mu); + gpr_free(ac->addr_name); + gpr_free(ac); + } +} + +static void on_alarm(grpc_exec_ctx *exec_ctx, void *acp, bool occured) { + async_connect *ac = acp; + gpr_mu_lock(&ac->mu); + /* If the alarm didn't occur, it got cancelled. */ + if (ac->socket != NULL && occured) { + grpc_winsocket_shutdown(ac->socket); + } + async_connect_unlock_and_cleanup(ac); +} + +static void on_connect(grpc_exec_ctx *exec_ctx, void *acp, bool from_iocp) { + async_connect *ac = acp; + SOCKET sock = ac->socket->socket; + grpc_endpoint **ep = ac->endpoint; + grpc_winsocket_callback_info *info = &ac->socket->write_info; + grpc_closure *on_done = ac->on_done; + + grpc_timer_cancel(exec_ctx, &ac->alarm); + + gpr_mu_lock(&ac->mu); + + if (from_iocp) { + DWORD transfered_bytes = 0; + DWORD flags; + BOOL wsa_success = WSAGetOverlappedResult(sock, &info->overlapped, + &transfered_bytes, FALSE, &flags); + GPR_ASSERT(transfered_bytes == 0); + if (!wsa_success) { + char *utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, "on_connect error: %s", utf8_message); + gpr_free(utf8_message); + } else { + *ep = grpc_tcp_create(ac->socket, ac->addr_name); + ac->socket = NULL; + } + } + + async_connect_unlock_and_cleanup(ac); + /* If the connection was aborted, the callback was already called when + the deadline was met. */ + on_done->cb(exec_ctx, on_done->cb_arg, *ep != NULL); +} + +/* Tries to issue one async connection, then schedules both an IOCP + notification request for the connection, and one timeout alert. */ +void grpc_tcp_client_connect(grpc_exec_ctx *exec_ctx, grpc_closure *on_done, + grpc_endpoint **endpoint, + grpc_pollset_set *interested_parties, + const struct sockaddr *addr, size_t addr_len, + gpr_timespec deadline) { + SOCKET sock = INVALID_SOCKET; + BOOL success; + int status; + struct sockaddr_in6 addr6_v4mapped; + struct sockaddr_in6 local_address; + async_connect *ac; + grpc_winsocket *socket = NULL; + LPFN_CONNECTEX ConnectEx; + GUID guid = WSAID_CONNECTEX; + DWORD ioctl_num_bytes; + const char *message = NULL; + char *utf8_message; + grpc_winsocket_callback_info *info; + + *endpoint = NULL; + + /* Use dualstack sockets where available. */ + if (grpc_sockaddr_to_v4mapped(addr, &addr6_v4mapped)) { + addr = (const struct sockaddr *)&addr6_v4mapped; + addr_len = sizeof(addr6_v4mapped); + } + + sock = WSASocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP, NULL, 0, + WSA_FLAG_OVERLAPPED); + if (sock == INVALID_SOCKET) { + message = "Unable to create socket: %s"; + goto failure; + } + + if (!grpc_tcp_prepare_socket(sock)) { + message = "Unable to set socket options: %s"; + goto failure; + } + + /* Grab the function pointer for ConnectEx for that specific socket. + It may change depending on the interface. */ + status = + WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), + &ConnectEx, sizeof(ConnectEx), &ioctl_num_bytes, NULL, NULL); + + if (status != 0) { + message = "Unable to retrieve ConnectEx pointer: %s"; + goto failure; + } + + grpc_sockaddr_make_wildcard6(0, &local_address); + + status = bind(sock, (struct sockaddr *)&local_address, sizeof(local_address)); + if (status != 0) { + message = "Unable to bind socket: %s"; + goto failure; + } + + socket = grpc_winsocket_create(sock, "client"); + info = &socket->write_info; + success = + ConnectEx(sock, addr, (int)addr_len, NULL, 0, NULL, &info->overlapped); + + /* It wouldn't be unusual to get a success immediately. But we'll still get + an IOCP notification, so let's ignore it. */ + if (!success) { + int error = WSAGetLastError(); + if (error != ERROR_IO_PENDING) { + message = "ConnectEx failed: %s"; + goto failure; + } + } + + ac = gpr_malloc(sizeof(async_connect)); + ac->on_done = on_done; + ac->socket = socket; + gpr_mu_init(&ac->mu); + ac->refs = 2; + ac->addr_name = grpc_sockaddr_to_uri(addr); + ac->endpoint = endpoint; + grpc_closure_init(&ac->on_connect, on_connect, ac); + + grpc_timer_init(exec_ctx, &ac->alarm, deadline, on_alarm, ac, + gpr_now(GPR_CLOCK_MONOTONIC)); + grpc_socket_notify_on_write(exec_ctx, socket, &ac->on_connect); + return; + +failure: + utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, message, utf8_message); + gpr_free(utf8_message); + if (socket != NULL) { + grpc_winsocket_destroy(socket); + } else if (sock != INVALID_SOCKET) { + closesocket(sock); + } + grpc_exec_ctx_enqueue(exec_ctx, on_done, false, NULL); +} + +#endif /* GPR_WINSOCK_SOCKET */ diff --git a/src/core/lib/iomgr/tcp_posix.c b/src/core/lib/iomgr/tcp_posix.c new file mode 100644 index 0000000000..1898d96901 --- /dev/null +++ b/src/core/lib/iomgr/tcp_posix.c @@ -0,0 +1,493 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_SOCKET + +#include "src/core/lib/iomgr/tcp_posix.h" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/slice.h> +#include <grpc/support/string_util.h> +#include <grpc/support/sync.h> +#include <grpc/support/time.h> + +#include "src/core/lib/debug/trace.h" +#include "src/core/lib/iomgr/pollset_posix.h" +#include "src/core/lib/iomgr/pollset_set_posix.h" +#include "src/core/lib/profiling/timers.h" +#include "src/core/lib/support/string.h" + +#ifdef GPR_HAVE_MSG_NOSIGNAL +#define SENDMSG_FLAGS MSG_NOSIGNAL +#else +#define SENDMSG_FLAGS 0 +#endif + +#ifdef GPR_MSG_IOVLEN_TYPE +typedef GPR_MSG_IOVLEN_TYPE msg_iovlen_type; +#else +typedef size_t msg_iovlen_type; +#endif + +int grpc_tcp_trace = 0; + +typedef struct { + grpc_endpoint base; + grpc_fd *em_fd; + int fd; + int finished_edge; + msg_iovlen_type iov_size; /* Number of slices to allocate per read attempt */ + size_t slice_size; + gpr_refcount refcount; + + /* garbage after the last read */ + gpr_slice_buffer last_read_buffer; + + gpr_slice_buffer *incoming_buffer; + gpr_slice_buffer *outgoing_buffer; + /** slice within outgoing_buffer to write next */ + size_t outgoing_slice_idx; + /** byte within outgoing_buffer->slices[outgoing_slice_idx] to write next */ + size_t outgoing_byte_idx; + + grpc_closure *read_cb; + grpc_closure *write_cb; + grpc_closure *release_fd_cb; + int *release_fd; + + grpc_closure read_closure; + grpc_closure write_closure; + + char *peer_string; +} grpc_tcp; + +static void tcp_handle_read(grpc_exec_ctx *exec_ctx, void *arg /* grpc_tcp */, + bool success); +static void tcp_handle_write(grpc_exec_ctx *exec_ctx, void *arg /* grpc_tcp */, + bool success); + +static void tcp_shutdown(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep) { + grpc_tcp *tcp = (grpc_tcp *)ep; + grpc_fd_shutdown(exec_ctx, tcp->em_fd); +} + +static void tcp_free(grpc_exec_ctx *exec_ctx, grpc_tcp *tcp) { + grpc_fd_orphan(exec_ctx, tcp->em_fd, tcp->release_fd_cb, tcp->release_fd, + "tcp_unref_orphan"); + gpr_slice_buffer_destroy(&tcp->last_read_buffer); + gpr_free(tcp->peer_string); + gpr_free(tcp); +} + +/*#define GRPC_TCP_REFCOUNT_DEBUG*/ +#ifdef GRPC_TCP_REFCOUNT_DEBUG +#define TCP_UNREF(cl, tcp, reason) \ + tcp_unref((cl), (tcp), (reason), __FILE__, __LINE__) +#define TCP_REF(tcp, reason) tcp_ref((tcp), (reason), __FILE__, __LINE__) +static void tcp_unref(grpc_exec_ctx *exec_ctx, grpc_tcp *tcp, + const char *reason, const char *file, int line) { + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, "TCP unref %p : %s %d -> %d", tcp, + reason, tcp->refcount.count, tcp->refcount.count - 1); + if (gpr_unref(&tcp->refcount)) { + tcp_free(exec_ctx, tcp); + } +} + +static void tcp_ref(grpc_tcp *tcp, const char *reason, const char *file, + int line) { + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, "TCP ref %p : %s %d -> %d", tcp, + reason, tcp->refcount.count, tcp->refcount.count + 1); + gpr_ref(&tcp->refcount); +} +#else +#define TCP_UNREF(cl, tcp, reason) tcp_unref((cl), (tcp)) +#define TCP_REF(tcp, reason) tcp_ref((tcp)) +static void tcp_unref(grpc_exec_ctx *exec_ctx, grpc_tcp *tcp) { + if (gpr_unref(&tcp->refcount)) { + tcp_free(exec_ctx, tcp); + } +} + +static void tcp_ref(grpc_tcp *tcp) { gpr_ref(&tcp->refcount); } +#endif + +static void tcp_destroy(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep) { + grpc_tcp *tcp = (grpc_tcp *)ep; + TCP_UNREF(exec_ctx, tcp, "destroy"); +} + +static void call_read_cb(grpc_exec_ctx *exec_ctx, grpc_tcp *tcp, int success) { + grpc_closure *cb = tcp->read_cb; + + if (grpc_tcp_trace) { + size_t i; + gpr_log(GPR_DEBUG, "read: success=%d", success); + for (i = 0; i < tcp->incoming_buffer->count; i++) { + char *dump = gpr_dump_slice(tcp->incoming_buffer->slices[i], + GPR_DUMP_HEX | GPR_DUMP_ASCII); + gpr_log(GPR_DEBUG, "READ %p: %s", tcp, dump); + gpr_free(dump); + } + } + + tcp->read_cb = NULL; + tcp->incoming_buffer = NULL; + cb->cb(exec_ctx, cb->cb_arg, success); +} + +#define MAX_READ_IOVEC 4 +static void tcp_continue_read(grpc_exec_ctx *exec_ctx, grpc_tcp *tcp) { + struct msghdr msg; + struct iovec iov[MAX_READ_IOVEC]; + ssize_t read_bytes; + size_t i; + + GPR_ASSERT(!tcp->finished_edge); + GPR_ASSERT(tcp->iov_size <= MAX_READ_IOVEC); + GPR_ASSERT(tcp->incoming_buffer->count <= MAX_READ_IOVEC); + GPR_TIMER_BEGIN("tcp_continue_read", 0); + + while (tcp->incoming_buffer->count < (size_t)tcp->iov_size) { + gpr_slice_buffer_add_indexed(tcp->incoming_buffer, + gpr_slice_malloc(tcp->slice_size)); + } + for (i = 0; i < tcp->incoming_buffer->count; i++) { + iov[i].iov_base = GPR_SLICE_START_PTR(tcp->incoming_buffer->slices[i]); + iov[i].iov_len = GPR_SLICE_LENGTH(tcp->incoming_buffer->slices[i]); + } + + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = iov; + msg.msg_iovlen = tcp->iov_size; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + + GPR_TIMER_BEGIN("recvmsg", 1); + do { + read_bytes = recvmsg(tcp->fd, &msg, 0); + } while (read_bytes < 0 && errno == EINTR); + GPR_TIMER_END("recvmsg", 0); + + if (read_bytes < 0) { + /* NB: After calling call_read_cb a parallel call of the read handler may + * be running. */ + if (errno == EAGAIN) { + if (tcp->iov_size > 1) { + tcp->iov_size /= 2; + } + /* We've consumed the edge, request a new one */ + grpc_fd_notify_on_read(exec_ctx, tcp->em_fd, &tcp->read_closure); + } else { + /* TODO(klempner): Log interesting errors */ + gpr_slice_buffer_reset_and_unref(tcp->incoming_buffer); + call_read_cb(exec_ctx, tcp, 0); + TCP_UNREF(exec_ctx, tcp, "read"); + } + } else if (read_bytes == 0) { + /* 0 read size ==> end of stream */ + gpr_slice_buffer_reset_and_unref(tcp->incoming_buffer); + call_read_cb(exec_ctx, tcp, 0); + TCP_UNREF(exec_ctx, tcp, "read"); + } else { + GPR_ASSERT((size_t)read_bytes <= tcp->incoming_buffer->length); + if ((size_t)read_bytes < tcp->incoming_buffer->length) { + gpr_slice_buffer_trim_end( + tcp->incoming_buffer, + tcp->incoming_buffer->length - (size_t)read_bytes, + &tcp->last_read_buffer); + } else if (tcp->iov_size < MAX_READ_IOVEC) { + ++tcp->iov_size; + } + GPR_ASSERT((size_t)read_bytes == tcp->incoming_buffer->length); + call_read_cb(exec_ctx, tcp, 1); + TCP_UNREF(exec_ctx, tcp, "read"); + } + + GPR_TIMER_END("tcp_continue_read", 0); +} + +static void tcp_handle_read(grpc_exec_ctx *exec_ctx, void *arg /* grpc_tcp */, + bool success) { + grpc_tcp *tcp = (grpc_tcp *)arg; + GPR_ASSERT(!tcp->finished_edge); + + if (!success) { + gpr_slice_buffer_reset_and_unref(tcp->incoming_buffer); + call_read_cb(exec_ctx, tcp, 0); + TCP_UNREF(exec_ctx, tcp, "read"); + } else { + tcp_continue_read(exec_ctx, tcp); + } +} + +static void tcp_read(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, + gpr_slice_buffer *incoming_buffer, grpc_closure *cb) { + grpc_tcp *tcp = (grpc_tcp *)ep; + GPR_ASSERT(tcp->read_cb == NULL); + tcp->read_cb = cb; + tcp->incoming_buffer = incoming_buffer; + gpr_slice_buffer_reset_and_unref(incoming_buffer); + gpr_slice_buffer_swap(incoming_buffer, &tcp->last_read_buffer); + TCP_REF(tcp, "read"); + if (tcp->finished_edge) { + tcp->finished_edge = 0; + grpc_fd_notify_on_read(exec_ctx, tcp->em_fd, &tcp->read_closure); + } else { + grpc_exec_ctx_enqueue(exec_ctx, &tcp->read_closure, true, NULL); + } +} + +typedef enum { FLUSH_DONE, FLUSH_PENDING, FLUSH_ERROR } flush_result; + +#define MAX_WRITE_IOVEC 16 +static flush_result tcp_flush(grpc_tcp *tcp) { + struct msghdr msg; + struct iovec iov[MAX_WRITE_IOVEC]; + msg_iovlen_type iov_size; + ssize_t sent_length; + size_t sending_length; + size_t trailing; + size_t unwind_slice_idx; + size_t unwind_byte_idx; + + for (;;) { + sending_length = 0; + unwind_slice_idx = tcp->outgoing_slice_idx; + unwind_byte_idx = tcp->outgoing_byte_idx; + for (iov_size = 0; tcp->outgoing_slice_idx != tcp->outgoing_buffer->count && + iov_size != MAX_WRITE_IOVEC; + iov_size++) { + iov[iov_size].iov_base = + GPR_SLICE_START_PTR( + tcp->outgoing_buffer->slices[tcp->outgoing_slice_idx]) + + tcp->outgoing_byte_idx; + iov[iov_size].iov_len = + GPR_SLICE_LENGTH( + tcp->outgoing_buffer->slices[tcp->outgoing_slice_idx]) - + tcp->outgoing_byte_idx; + sending_length += iov[iov_size].iov_len; + tcp->outgoing_slice_idx++; + tcp->outgoing_byte_idx = 0; + } + GPR_ASSERT(iov_size > 0); + + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = iov; + msg.msg_iovlen = iov_size; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + + GPR_TIMER_BEGIN("sendmsg", 1); + do { + /* TODO(klempner): Cork if this is a partial write */ + sent_length = sendmsg(tcp->fd, &msg, SENDMSG_FLAGS); + } while (sent_length < 0 && errno == EINTR); + GPR_TIMER_END("sendmsg", 0); + + if (sent_length < 0) { + if (errno == EAGAIN) { + tcp->outgoing_slice_idx = unwind_slice_idx; + tcp->outgoing_byte_idx = unwind_byte_idx; + return FLUSH_PENDING; + } else { + /* TODO(klempner): Log some of these */ + return FLUSH_ERROR; + } + } + + GPR_ASSERT(tcp->outgoing_byte_idx == 0); + trailing = sending_length - (size_t)sent_length; + while (trailing > 0) { + size_t slice_length; + + tcp->outgoing_slice_idx--; + slice_length = GPR_SLICE_LENGTH( + tcp->outgoing_buffer->slices[tcp->outgoing_slice_idx]); + if (slice_length > trailing) { + tcp->outgoing_byte_idx = slice_length - trailing; + break; + } else { + trailing -= slice_length; + } + } + + if (tcp->outgoing_slice_idx == tcp->outgoing_buffer->count) { + return FLUSH_DONE; + } + }; +} + +static void tcp_handle_write(grpc_exec_ctx *exec_ctx, void *arg /* grpc_tcp */, + bool success) { + grpc_tcp *tcp = (grpc_tcp *)arg; + flush_result status; + grpc_closure *cb; + + if (!success) { + cb = tcp->write_cb; + tcp->write_cb = NULL; + cb->cb(exec_ctx, cb->cb_arg, 0); + TCP_UNREF(exec_ctx, tcp, "write"); + return; + } + + status = tcp_flush(tcp); + if (status == FLUSH_PENDING) { + grpc_fd_notify_on_write(exec_ctx, tcp->em_fd, &tcp->write_closure); + } else { + cb = tcp->write_cb; + tcp->write_cb = NULL; + GPR_TIMER_BEGIN("tcp_handle_write.cb", 0); + cb->cb(exec_ctx, cb->cb_arg, status == FLUSH_DONE); + GPR_TIMER_END("tcp_handle_write.cb", 0); + TCP_UNREF(exec_ctx, tcp, "write"); + } +} + +static void tcp_write(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, + gpr_slice_buffer *buf, grpc_closure *cb) { + grpc_tcp *tcp = (grpc_tcp *)ep; + flush_result status; + + if (grpc_tcp_trace) { + size_t i; + + for (i = 0; i < buf->count; i++) { + char *data = + gpr_dump_slice(buf->slices[i], GPR_DUMP_HEX | GPR_DUMP_ASCII); + gpr_log(GPR_DEBUG, "WRITE %p: %s", tcp, data); + gpr_free(data); + } + } + + GPR_TIMER_BEGIN("tcp_write", 0); + GPR_ASSERT(tcp->write_cb == NULL); + + if (buf->length == 0) { + GPR_TIMER_END("tcp_write", 0); + grpc_exec_ctx_enqueue(exec_ctx, cb, true, NULL); + return; + } + tcp->outgoing_buffer = buf; + tcp->outgoing_slice_idx = 0; + tcp->outgoing_byte_idx = 0; + + status = tcp_flush(tcp); + if (status == FLUSH_PENDING) { + TCP_REF(tcp, "write"); + tcp->write_cb = cb; + grpc_fd_notify_on_write(exec_ctx, tcp->em_fd, &tcp->write_closure); + } else { + grpc_exec_ctx_enqueue(exec_ctx, cb, status == FLUSH_DONE, NULL); + } + + GPR_TIMER_END("tcp_write", 0); +} + +static void tcp_add_to_pollset(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, + grpc_pollset *pollset) { + grpc_tcp *tcp = (grpc_tcp *)ep; + grpc_pollset_add_fd(exec_ctx, pollset, tcp->em_fd); +} + +static void tcp_add_to_pollset_set(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, + grpc_pollset_set *pollset_set) { + grpc_tcp *tcp = (grpc_tcp *)ep; + grpc_pollset_set_add_fd(exec_ctx, pollset_set, tcp->em_fd); +} + +static char *tcp_get_peer(grpc_endpoint *ep) { + grpc_tcp *tcp = (grpc_tcp *)ep; + return gpr_strdup(tcp->peer_string); +} + +static const grpc_endpoint_vtable vtable = { + tcp_read, tcp_write, tcp_add_to_pollset, tcp_add_to_pollset_set, + tcp_shutdown, tcp_destroy, tcp_get_peer}; + +grpc_endpoint *grpc_tcp_create(grpc_fd *em_fd, size_t slice_size, + const char *peer_string) { + grpc_tcp *tcp = (grpc_tcp *)gpr_malloc(sizeof(grpc_tcp)); + tcp->base.vtable = &vtable; + tcp->peer_string = gpr_strdup(peer_string); + tcp->fd = em_fd->fd; + tcp->read_cb = NULL; + tcp->write_cb = NULL; + tcp->release_fd_cb = NULL; + tcp->release_fd = NULL; + tcp->incoming_buffer = NULL; + tcp->slice_size = slice_size; + tcp->iov_size = 1; + tcp->finished_edge = 1; + /* paired with unref in grpc_tcp_destroy */ + gpr_ref_init(&tcp->refcount, 1); + tcp->em_fd = em_fd; + tcp->read_closure.cb = tcp_handle_read; + tcp->read_closure.cb_arg = tcp; + tcp->write_closure.cb = tcp_handle_write; + tcp->write_closure.cb_arg = tcp; + gpr_slice_buffer_init(&tcp->last_read_buffer); + + return &tcp->base; +} + +int grpc_tcp_fd(grpc_endpoint *ep) { + grpc_tcp *tcp = (grpc_tcp *)ep; + GPR_ASSERT(ep->vtable == &vtable); + return grpc_fd_wrapped_fd(tcp->em_fd); +} + +void grpc_tcp_destroy_and_release_fd(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, + int *fd, grpc_closure *done) { + grpc_tcp *tcp = (grpc_tcp *)ep; + GPR_ASSERT(ep->vtable == &vtable); + tcp->release_fd = fd; + tcp->release_fd_cb = done; + TCP_UNREF(exec_ctx, tcp, "destroy"); +} + +#endif diff --git a/src/core/lib/iomgr/tcp_posix.h b/src/core/lib/iomgr/tcp_posix.h new file mode 100644 index 0000000000..09c4436f1f --- /dev/null +++ b/src/core/lib/iomgr/tcp_posix.h @@ -0,0 +1,71 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_TCP_POSIX_H +#define GRPC_CORE_LIB_IOMGR_TCP_POSIX_H +/* + Low level TCP "bottom half" implementation, for use by transports built on + top of a TCP connection. + + Note that this file does not (yet) include APIs for creating the socket in + the first place. + + All calls passing slice transfer ownership of a slice refcount unless + otherwise specified. +*/ + +#include "src/core/lib/iomgr/endpoint.h" +#include "src/core/lib/iomgr/fd_posix.h" + +#define GRPC_TCP_DEFAULT_READ_SLICE_SIZE 8192 + +extern int grpc_tcp_trace; + +/* Create a tcp endpoint given a file desciptor and a read slice size. + Takes ownership of fd. */ +grpc_endpoint *grpc_tcp_create(grpc_fd *fd, size_t read_slice_size, + const char *peer_string); + +/* Return the tcp endpoint's fd, or -1 if this is not available. Does not + release the fd. + Requires: ep must be a tcp endpoint. + */ +int grpc_tcp_fd(grpc_endpoint *ep); + +/* Destroy the tcp endpoint without closing its fd. *fd will be set and done + * will be called when the endpoint is destroyed. + * Requires: ep must be a tcp endpoint and fd must not be NULL. */ +void grpc_tcp_destroy_and_release_fd(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, + int *fd, grpc_closure *done); + +#endif /* GRPC_CORE_LIB_IOMGR_TCP_POSIX_H */ diff --git a/src/core/lib/iomgr/tcp_server.h b/src/core/lib/iomgr/tcp_server.h new file mode 100644 index 0000000000..81edb61997 --- /dev/null +++ b/src/core/lib/iomgr/tcp_server.h @@ -0,0 +1,103 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_TCP_SERVER_H +#define GRPC_CORE_LIB_IOMGR_TCP_SERVER_H + +#include "src/core/lib/iomgr/closure.h" +#include "src/core/lib/iomgr/endpoint.h" + +/* Forward decl of grpc_tcp_server */ +typedef struct grpc_tcp_server grpc_tcp_server; + +typedef struct grpc_tcp_server_acceptor { + /* grpc_tcp_server_cb functions share a ref on from_server that is valid + until the function returns. */ + grpc_tcp_server *from_server; + /* Indices that may be passed to grpc_tcp_server_port_fd(). */ + unsigned port_index; + unsigned fd_index; +} grpc_tcp_server_acceptor; + +/* Called for newly connected TCP connections. */ +typedef void (*grpc_tcp_server_cb)(grpc_exec_ctx *exec_ctx, void *arg, + grpc_endpoint *ep, + grpc_tcp_server_acceptor *acceptor); + +/* Create a server, initially not bound to any ports. The caller owns one ref. + If shutdown_complete is not NULL, it will be used by + grpc_tcp_server_unref() when the ref count reaches zero. */ +grpc_tcp_server *grpc_tcp_server_create(grpc_closure *shutdown_complete); + +/* Start listening to bound ports */ +void grpc_tcp_server_start(grpc_exec_ctx *exec_ctx, grpc_tcp_server *server, + grpc_pollset **pollsets, size_t pollset_count, + grpc_tcp_server_cb on_accept_cb, void *cb_arg); + +/* Add a port to the server, returning the newly allocated port on success, or + -1 on failure. + + The :: and 0.0.0.0 wildcard addresses are treated identically, accepting + both IPv4 and IPv6 connections, but :: is the preferred style. This usually + creates one socket, but possibly two on systems which support IPv6, + but not dualstack sockets. */ +/* TODO(ctiller): deprecate this, and make grpc_tcp_server_add_ports to handle + all of the multiple socket port matching logic in one place */ +int grpc_tcp_server_add_port(grpc_tcp_server *s, const void *addr, + size_t addr_len); + +/* Number of fds at the given port_index, or 0 if port_index is out of + bounds. */ +unsigned grpc_tcp_server_port_fd_count(grpc_tcp_server *s, unsigned port_index); + +/* Returns the file descriptor of the Mth (fd_index) listening socket of the Nth + (port_index) call to add_port() on this server, or -1 if the indices are out + of bounds. The file descriptor remains owned by the server, and will be + cleaned up when the ref count reaches zero. */ +int grpc_tcp_server_port_fd(grpc_tcp_server *s, unsigned port_index, + unsigned fd_index); + +/* Ref s and return s. */ +grpc_tcp_server *grpc_tcp_server_ref(grpc_tcp_server *s); + +/* shutdown_starting is called when ref count has reached zero and the server is + about to be destroyed. The server will be deleted after it returns. Calling + grpc_tcp_server_ref() from it has no effect. */ +void grpc_tcp_server_shutdown_starting_add(grpc_tcp_server *s, + grpc_closure *shutdown_starting); + +/* If the refcount drops to zero, delete s, and call (exec_ctx==NULL) or enqueue + a call (exec_ctx!=NULL) to shutdown_complete. */ +void grpc_tcp_server_unref(grpc_exec_ctx *exec_ctx, grpc_tcp_server *s); + +#endif /* GRPC_CORE_LIB_IOMGR_TCP_SERVER_H */ diff --git a/src/core/lib/iomgr/tcp_server_posix.c b/src/core/lib/iomgr/tcp_server_posix.c new file mode 100644 index 0000000000..ef1bf9aa94 --- /dev/null +++ b/src/core/lib/iomgr/tcp_server_posix.c @@ -0,0 +1,607 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* FIXME: "posix" files shouldn't be depending on _GNU_SOURCE */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_SOCKET + +#include "src/core/lib/iomgr/tcp_server.h" + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> +#include <grpc/support/sync.h> +#include <grpc/support/time.h> +#include "src/core/lib/iomgr/pollset_posix.h" +#include "src/core/lib/iomgr/resolve_address.h" +#include "src/core/lib/iomgr/sockaddr_utils.h" +#include "src/core/lib/iomgr/socket_utils_posix.h" +#include "src/core/lib/iomgr/tcp_posix.h" +#include "src/core/lib/iomgr/unix_sockets_posix.h" +#include "src/core/lib/support/string.h" + +#define MIN_SAFE_ACCEPT_QUEUE_SIZE 100 + +static gpr_once s_init_max_accept_queue_size; +static int s_max_accept_queue_size; + +/* one listening port */ +typedef struct grpc_tcp_listener grpc_tcp_listener; +struct grpc_tcp_listener { + int fd; + grpc_fd *emfd; + grpc_tcp_server *server; + union { + uint8_t untyped[GRPC_MAX_SOCKADDR_SIZE]; + struct sockaddr sockaddr; + } addr; + size_t addr_len; + int port; + unsigned port_index; + unsigned fd_index; + grpc_closure read_closure; + grpc_closure destroyed_closure; + struct grpc_tcp_listener *next; + /* When we add a listener, more than one can be created, mainly because of + IPv6. A sibling will still be in the normal list, but will be flagged + as such. Any action, such as ref or unref, will affect all of the + siblings in the list. */ + struct grpc_tcp_listener *sibling; + int is_sibling; +}; + +/* the overall server */ +struct grpc_tcp_server { + gpr_refcount refs; + /* Called whenever accept() succeeds on a server port. */ + grpc_tcp_server_cb on_accept_cb; + void *on_accept_cb_arg; + + gpr_mu mu; + + /* active port count: how many ports are actually still listening */ + size_t active_ports; + /* destroyed port count: how many ports are completely destroyed */ + size_t destroyed_ports; + + /* is this server shutting down? (boolean) */ + int shutdown; + + /* linked list of server ports */ + grpc_tcp_listener *head; + grpc_tcp_listener *tail; + unsigned nports; + + /* List of closures passed to shutdown_starting_add(). */ + grpc_closure_list shutdown_starting; + + /* shutdown callback */ + grpc_closure *shutdown_complete; + + /* all pollsets interested in new connections */ + grpc_pollset **pollsets; + /* number of pollsets in the pollsets array */ + size_t pollset_count; +}; + +grpc_tcp_server *grpc_tcp_server_create(grpc_closure *shutdown_complete) { + grpc_tcp_server *s = gpr_malloc(sizeof(grpc_tcp_server)); + gpr_ref_init(&s->refs, 1); + gpr_mu_init(&s->mu); + s->active_ports = 0; + s->destroyed_ports = 0; + s->shutdown = 0; + s->shutdown_starting.head = NULL; + s->shutdown_starting.tail = NULL; + s->shutdown_complete = shutdown_complete; + s->on_accept_cb = NULL; + s->on_accept_cb_arg = NULL; + s->head = NULL; + s->tail = NULL; + s->nports = 0; + return s; +} + +static void finish_shutdown(grpc_exec_ctx *exec_ctx, grpc_tcp_server *s) { + if (s->shutdown_complete != NULL) { + grpc_exec_ctx_enqueue(exec_ctx, s->shutdown_complete, true, NULL); + } + + gpr_mu_destroy(&s->mu); + + while (s->head) { + grpc_tcp_listener *sp = s->head; + s->head = sp->next; + gpr_free(sp); + } + + gpr_free(s); +} + +static void destroyed_port(grpc_exec_ctx *exec_ctx, void *server, + bool success) { + grpc_tcp_server *s = server; + gpr_mu_lock(&s->mu); + s->destroyed_ports++; + if (s->destroyed_ports == s->nports) { + gpr_mu_unlock(&s->mu); + finish_shutdown(exec_ctx, s); + } else { + GPR_ASSERT(s->destroyed_ports < s->nports); + gpr_mu_unlock(&s->mu); + } +} + +/* called when all listening endpoints have been shutdown, so no further + events will be received on them - at this point it's safe to destroy + things */ +static void deactivated_all_ports(grpc_exec_ctx *exec_ctx, grpc_tcp_server *s) { + /* delete ALL the things */ + gpr_mu_lock(&s->mu); + + if (!s->shutdown) { + gpr_mu_unlock(&s->mu); + return; + } + + if (s->head) { + grpc_tcp_listener *sp; + for (sp = s->head; sp; sp = sp->next) { + grpc_unlink_if_unix_domain_socket(&sp->addr.sockaddr); + sp->destroyed_closure.cb = destroyed_port; + sp->destroyed_closure.cb_arg = s; + grpc_fd_orphan(exec_ctx, sp->emfd, &sp->destroyed_closure, NULL, + "tcp_listener_shutdown"); + } + gpr_mu_unlock(&s->mu); + } else { + gpr_mu_unlock(&s->mu); + finish_shutdown(exec_ctx, s); + } +} + +static void tcp_server_destroy(grpc_exec_ctx *exec_ctx, grpc_tcp_server *s) { + gpr_mu_lock(&s->mu); + + GPR_ASSERT(!s->shutdown); + s->shutdown = 1; + + /* shutdown all fd's */ + if (s->active_ports) { + grpc_tcp_listener *sp; + for (sp = s->head; sp; sp = sp->next) { + grpc_fd_shutdown(exec_ctx, sp->emfd); + } + gpr_mu_unlock(&s->mu); + } else { + gpr_mu_unlock(&s->mu); + deactivated_all_ports(exec_ctx, s); + } +} + +/* get max listen queue size on linux */ +static void init_max_accept_queue_size(void) { + int n = SOMAXCONN; + char buf[64]; + FILE *fp = fopen("/proc/sys/net/core/somaxconn", "r"); + if (fp == NULL) { + /* 2.4 kernel. */ + s_max_accept_queue_size = SOMAXCONN; + return; + } + if (fgets(buf, sizeof buf, fp)) { + char *end; + long i = strtol(buf, &end, 10); + if (i > 0 && i <= INT_MAX && end && *end == 0) { + n = (int)i; + } + } + fclose(fp); + s_max_accept_queue_size = n; + + if (s_max_accept_queue_size < MIN_SAFE_ACCEPT_QUEUE_SIZE) { + gpr_log(GPR_INFO, + "Suspiciously small accept queue (%d) will probably lead to " + "connection drops", + s_max_accept_queue_size); + } +} + +static int get_max_accept_queue_size(void) { + gpr_once_init(&s_init_max_accept_queue_size, init_max_accept_queue_size); + return s_max_accept_queue_size; +} + +/* Prepare a recently-created socket for listening. */ +static int prepare_socket(int fd, const struct sockaddr *addr, + size_t addr_len) { + struct sockaddr_storage sockname_temp; + socklen_t sockname_len; + + if (fd < 0) { + goto error; + } + + if (!grpc_set_socket_nonblocking(fd, 1) || !grpc_set_socket_cloexec(fd, 1) || + (!grpc_is_unix_socket(addr) && (!grpc_set_socket_low_latency(fd, 1) || + !grpc_set_socket_reuse_addr(fd, 1))) || + !grpc_set_socket_no_sigpipe_if_possible(fd)) { + gpr_log(GPR_ERROR, "Unable to configure socket %d: %s", fd, + strerror(errno)); + goto error; + } + + GPR_ASSERT(addr_len < ~(socklen_t)0); + if (bind(fd, addr, (socklen_t)addr_len) < 0) { + char *addr_str; + grpc_sockaddr_to_string(&addr_str, addr, 0); + gpr_log(GPR_ERROR, "bind addr=%s: %s", addr_str, strerror(errno)); + gpr_free(addr_str); + goto error; + } + + if (listen(fd, get_max_accept_queue_size()) < 0) { + gpr_log(GPR_ERROR, "listen: %s", strerror(errno)); + goto error; + } + + sockname_len = sizeof(sockname_temp); + if (getsockname(fd, (struct sockaddr *)&sockname_temp, &sockname_len) < 0) { + goto error; + } + + return grpc_sockaddr_get_port((struct sockaddr *)&sockname_temp); + +error: + if (fd >= 0) { + close(fd); + } + return -1; +} + +/* event manager callback when reads are ready */ +static void on_read(grpc_exec_ctx *exec_ctx, void *arg, bool success) { + grpc_tcp_listener *sp = arg; + grpc_tcp_server_acceptor acceptor = {sp->server, sp->port_index, + sp->fd_index}; + grpc_fd *fdobj; + size_t i; + + if (!success) { + goto error; + } + + /* loop until accept4 returns EAGAIN, and then re-arm notification */ + for (;;) { + struct sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + char *addr_str; + char *name; + /* Note: If we ever decide to return this address to the user, remember to + strip off the ::ffff:0.0.0.0/96 prefix first. */ + int fd = grpc_accept4(sp->fd, (struct sockaddr *)&addr, &addrlen, 1, 1); + if (fd < 0) { + switch (errno) { + case EINTR: + continue; + case EAGAIN: + grpc_fd_notify_on_read(exec_ctx, sp->emfd, &sp->read_closure); + return; + default: + gpr_log(GPR_ERROR, "Failed accept4: %s", strerror(errno)); + goto error; + } + } + + grpc_set_socket_no_sigpipe_if_possible(fd); + + addr_str = grpc_sockaddr_to_uri((struct sockaddr *)&addr); + gpr_asprintf(&name, "tcp-server-connection:%s", addr_str); + + if (grpc_tcp_trace) { + gpr_log(GPR_DEBUG, "SERVER_CONNECT: incoming connection: %s", addr_str); + } + + fdobj = grpc_fd_create(fd, name); + /* TODO(ctiller): revise this when we have server-side sharding + of channels -- we certainly should not be automatically adding every + incoming channel to every pollset owned by the server */ + for (i = 0; i < sp->server->pollset_count; i++) { + grpc_pollset_add_fd(exec_ctx, sp->server->pollsets[i], fdobj); + } + sp->server->on_accept_cb( + exec_ctx, sp->server->on_accept_cb_arg, + grpc_tcp_create(fdobj, GRPC_TCP_DEFAULT_READ_SLICE_SIZE, addr_str), + &acceptor); + + gpr_free(name); + gpr_free(addr_str); + } + + GPR_UNREACHABLE_CODE(return ); + +error: + gpr_mu_lock(&sp->server->mu); + if (0 == --sp->server->active_ports) { + gpr_mu_unlock(&sp->server->mu); + deactivated_all_ports(exec_ctx, sp->server); + } else { + gpr_mu_unlock(&sp->server->mu); + } +} + +static grpc_tcp_listener *add_socket_to_server(grpc_tcp_server *s, int fd, + const struct sockaddr *addr, + size_t addr_len, + unsigned port_index, + unsigned fd_index) { + grpc_tcp_listener *sp = NULL; + int port; + char *addr_str; + char *name; + + port = prepare_socket(fd, addr, addr_len); + if (port >= 0) { + grpc_sockaddr_to_string(&addr_str, (struct sockaddr *)&addr, 1); + gpr_asprintf(&name, "tcp-server-listener:%s", addr_str); + gpr_mu_lock(&s->mu); + s->nports++; + GPR_ASSERT(!s->on_accept_cb && "must add ports before starting server"); + sp = gpr_malloc(sizeof(grpc_tcp_listener)); + sp->next = NULL; + if (s->head == NULL) { + s->head = sp; + } else { + s->tail->next = sp; + } + s->tail = sp; + sp->server = s; + sp->fd = fd; + sp->emfd = grpc_fd_create(fd, name); + memcpy(sp->addr.untyped, addr, addr_len); + sp->addr_len = addr_len; + sp->port = port; + sp->port_index = port_index; + sp->fd_index = fd_index; + sp->is_sibling = 0; + sp->sibling = NULL; + GPR_ASSERT(sp->emfd); + gpr_mu_unlock(&s->mu); + gpr_free(addr_str); + gpr_free(name); + } + + return sp; +} + +int grpc_tcp_server_add_port(grpc_tcp_server *s, const void *addr, + size_t addr_len) { + grpc_tcp_listener *sp; + grpc_tcp_listener *sp2 = NULL; + int fd; + grpc_dualstack_mode dsmode; + struct sockaddr_in6 addr6_v4mapped; + struct sockaddr_in wild4; + struct sockaddr_in6 wild6; + struct sockaddr_in addr4_copy; + struct sockaddr *allocated_addr = NULL; + struct sockaddr_storage sockname_temp; + socklen_t sockname_len; + int port; + unsigned port_index = 0; + unsigned fd_index = 0; + if (s->tail != NULL) { + port_index = s->tail->port_index + 1; + } + grpc_unlink_if_unix_domain_socket((struct sockaddr *)addr); + + /* Check if this is a wildcard port, and if so, try to keep the port the same + as some previously created listener. */ + if (grpc_sockaddr_get_port(addr) == 0) { + for (sp = s->head; sp; sp = sp->next) { + sockname_len = sizeof(sockname_temp); + if (0 == getsockname(sp->fd, (struct sockaddr *)&sockname_temp, + &sockname_len)) { + port = grpc_sockaddr_get_port((struct sockaddr *)&sockname_temp); + if (port > 0) { + allocated_addr = malloc(addr_len); + memcpy(allocated_addr, addr, addr_len); + grpc_sockaddr_set_port(allocated_addr, port); + addr = allocated_addr; + break; + } + } + } + } + + sp = NULL; + + if (grpc_sockaddr_to_v4mapped(addr, &addr6_v4mapped)) { + addr = (const struct sockaddr *)&addr6_v4mapped; + addr_len = sizeof(addr6_v4mapped); + } + + /* Treat :: or 0.0.0.0 as a family-agnostic wildcard. */ + if (grpc_sockaddr_is_wildcard(addr, &port)) { + grpc_sockaddr_make_wildcards(port, &wild4, &wild6); + + /* Try listening on IPv6 first. */ + addr = (struct sockaddr *)&wild6; + addr_len = sizeof(wild6); + fd = grpc_create_dualstack_socket(addr, SOCK_STREAM, 0, &dsmode); + sp = add_socket_to_server(s, fd, addr, addr_len, port_index, fd_index); + if (fd >= 0 && dsmode == GRPC_DSMODE_DUALSTACK) { + goto done; + } + if (sp != NULL) { + ++fd_index; + } + /* If we didn't get a dualstack socket, also listen on 0.0.0.0. */ + if (port == 0 && sp != NULL) { + grpc_sockaddr_set_port((struct sockaddr *)&wild4, sp->port); + } + addr = (struct sockaddr *)&wild4; + addr_len = sizeof(wild4); + } + + fd = grpc_create_dualstack_socket(addr, SOCK_STREAM, 0, &dsmode); + if (fd < 0) { + gpr_log(GPR_ERROR, "Unable to create socket: %s", strerror(errno)); + } else { + if (dsmode == GRPC_DSMODE_IPV4 && + grpc_sockaddr_is_v4mapped(addr, &addr4_copy)) { + addr = (struct sockaddr *)&addr4_copy; + addr_len = sizeof(addr4_copy); + } + sp2 = sp; + sp = add_socket_to_server(s, fd, addr, addr_len, port_index, fd_index); + if (sp2 != NULL && sp != NULL) { + sp2->sibling = sp; + sp->is_sibling = 1; + } + } + +done: + gpr_free(allocated_addr); + if (sp != NULL) { + return sp->port; + } else { + return -1; + } +} + +unsigned grpc_tcp_server_port_fd_count(grpc_tcp_server *s, + unsigned port_index) { + unsigned num_fds = 0; + grpc_tcp_listener *sp; + for (sp = s->head; sp && port_index != 0; sp = sp->next) { + if (!sp->is_sibling) { + --port_index; + } + } + for (; sp; sp = sp->sibling, ++num_fds) + ; + return num_fds; +} + +int grpc_tcp_server_port_fd(grpc_tcp_server *s, unsigned port_index, + unsigned fd_index) { + grpc_tcp_listener *sp; + for (sp = s->head; sp && port_index != 0; sp = sp->next) { + if (!sp->is_sibling) { + --port_index; + } + } + for (; sp && fd_index != 0; sp = sp->sibling, --fd_index) + ; + if (sp) { + return sp->fd; + } else { + return -1; + } +} + +void grpc_tcp_server_start(grpc_exec_ctx *exec_ctx, grpc_tcp_server *s, + grpc_pollset **pollsets, size_t pollset_count, + grpc_tcp_server_cb on_accept_cb, + void *on_accept_cb_arg) { + size_t i; + grpc_tcp_listener *sp; + GPR_ASSERT(on_accept_cb); + gpr_mu_lock(&s->mu); + GPR_ASSERT(!s->on_accept_cb); + GPR_ASSERT(s->active_ports == 0); + s->on_accept_cb = on_accept_cb; + s->on_accept_cb_arg = on_accept_cb_arg; + s->pollsets = pollsets; + s->pollset_count = pollset_count; + for (sp = s->head; sp; sp = sp->next) { + for (i = 0; i < pollset_count; i++) { + grpc_pollset_add_fd(exec_ctx, pollsets[i], sp->emfd); + } + sp->read_closure.cb = on_read; + sp->read_closure.cb_arg = sp; + grpc_fd_notify_on_read(exec_ctx, sp->emfd, &sp->read_closure); + s->active_ports++; + } + gpr_mu_unlock(&s->mu); +} + +grpc_tcp_server *grpc_tcp_server_ref(grpc_tcp_server *s) { + gpr_ref(&s->refs); + return s; +} + +void grpc_tcp_server_shutdown_starting_add(grpc_tcp_server *s, + grpc_closure *shutdown_starting) { + gpr_mu_lock(&s->mu); + grpc_closure_list_add(&s->shutdown_starting, shutdown_starting, 1); + gpr_mu_unlock(&s->mu); +} + +void grpc_tcp_server_unref(grpc_exec_ctx *exec_ctx, grpc_tcp_server *s) { + if (gpr_unref(&s->refs)) { + /* Complete shutdown_starting work before destroying. */ + grpc_exec_ctx local_exec_ctx = GRPC_EXEC_CTX_INIT; + gpr_mu_lock(&s->mu); + grpc_exec_ctx_enqueue_list(&local_exec_ctx, &s->shutdown_starting, NULL); + gpr_mu_unlock(&s->mu); + if (exec_ctx == NULL) { + grpc_exec_ctx_flush(&local_exec_ctx); + tcp_server_destroy(&local_exec_ctx, s); + grpc_exec_ctx_finish(&local_exec_ctx); + } else { + grpc_exec_ctx_finish(&local_exec_ctx); + tcp_server_destroy(exec_ctx, s); + } + } +} + +#endif diff --git a/src/core/lib/iomgr/tcp_server_windows.c b/src/core/lib/iomgr/tcp_server_windows.c new file mode 100644 index 0000000000..3d6a29b2e2 --- /dev/null +++ b/src/core/lib/iomgr/tcp_server_windows.c @@ -0,0 +1,557 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WINSOCK_SOCKET + +#include <io.h> + +#include "src/core/lib/iomgr/sockaddr_utils.h" + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/log_win32.h> +#include <grpc/support/string_util.h> +#include <grpc/support/sync.h> +#include <grpc/support/time.h> + +#include "src/core/lib/iomgr/iocp_windows.h" +#include "src/core/lib/iomgr/pollset_windows.h" +#include "src/core/lib/iomgr/socket_windows.h" +#include "src/core/lib/iomgr/tcp_server.h" +#include "src/core/lib/iomgr/tcp_windows.h" + +#define MIN_SAFE_ACCEPT_QUEUE_SIZE 100 + +/* one listening port */ +typedef struct grpc_tcp_listener grpc_tcp_listener; +struct grpc_tcp_listener { + /* This seemingly magic number comes from AcceptEx's documentation. each + address buffer needs to have at least 16 more bytes at their end. */ + uint8_t addresses[(sizeof(struct sockaddr_in6) + 16) * 2]; + /* This will hold the socket for the next accept. */ + SOCKET new_socket; + /* The listener winsocket. */ + grpc_winsocket *socket; + /* The actual TCP port number. */ + int port; + unsigned port_index; + grpc_tcp_server *server; + /* The cached AcceptEx for that port. */ + LPFN_ACCEPTEX AcceptEx; + int shutting_down; + /* closure for socket notification of accept being ready */ + grpc_closure on_accept; + /* linked list */ + struct grpc_tcp_listener *next; +}; + +/* the overall server */ +struct grpc_tcp_server { + gpr_refcount refs; + /* Called whenever accept() succeeds on a server port. */ + grpc_tcp_server_cb on_accept_cb; + void *on_accept_cb_arg; + + gpr_mu mu; + + /* active port count: how many ports are actually still listening */ + int active_ports; + + /* linked list of server ports */ + grpc_tcp_listener *head; + grpc_tcp_listener *tail; + + /* List of closures passed to shutdown_starting_add(). */ + grpc_closure_list shutdown_starting; + + /* shutdown callback */ + grpc_closure *shutdown_complete; +}; + +/* Public function. Allocates the proper data structures to hold a + grpc_tcp_server. */ +grpc_tcp_server *grpc_tcp_server_create(grpc_closure *shutdown_complete) { + grpc_tcp_server *s = gpr_malloc(sizeof(grpc_tcp_server)); + gpr_ref_init(&s->refs, 1); + gpr_mu_init(&s->mu); + s->active_ports = 0; + s->on_accept_cb = NULL; + s->on_accept_cb_arg = NULL; + s->head = NULL; + s->tail = NULL; + s->shutdown_starting.head = NULL; + s->shutdown_starting.tail = NULL; + s->shutdown_complete = shutdown_complete; + return s; +} + +static void finish_shutdown(grpc_exec_ctx *exec_ctx, grpc_tcp_server *s) { + if (s->shutdown_complete != NULL) { + grpc_exec_ctx_enqueue(exec_ctx, s->shutdown_complete, true, NULL); + } + + /* Now that the accepts have been aborted, we can destroy the sockets. + The IOCP won't get notified on these, so we can flag them as already + closed by the system. */ + while (s->head) { + grpc_tcp_listener *sp = s->head; + s->head = sp->next; + sp->next = NULL; + grpc_winsocket_destroy(sp->socket); + gpr_free(sp); + } + gpr_free(s); +} + +grpc_tcp_server *grpc_tcp_server_ref(grpc_tcp_server *s) { + gpr_ref(&s->refs); + return s; +} + +void grpc_tcp_server_shutdown_starting_add(grpc_tcp_server *s, + grpc_closure *shutdown_starting) { + gpr_mu_lock(&s->mu); + grpc_closure_list_add(&s->shutdown_starting, shutdown_starting, 1); + gpr_mu_unlock(&s->mu); +} + +static void tcp_server_destroy(grpc_exec_ctx *exec_ctx, grpc_tcp_server *s) { + int immediately_done = 0; + grpc_tcp_listener *sp; + gpr_mu_lock(&s->mu); + + /* First, shutdown all fd's. This will queue abortion calls for all + of the pending accepts due to the normal operation mechanism. */ + if (s->active_ports == 0) { + immediately_done = 1; + } + for (sp = s->head; sp; sp = sp->next) { + sp->shutting_down = 1; + grpc_winsocket_shutdown(sp->socket); + } + gpr_mu_unlock(&s->mu); + + if (immediately_done) { + finish_shutdown(exec_ctx, s); + } +} + +void grpc_tcp_server_unref(grpc_exec_ctx *exec_ctx, grpc_tcp_server *s) { + if (gpr_unref(&s->refs)) { + /* Complete shutdown_starting work before destroying. */ + grpc_exec_ctx local_exec_ctx = GRPC_EXEC_CTX_INIT; + gpr_mu_lock(&s->mu); + grpc_exec_ctx_enqueue_list(&local_exec_ctx, &s->shutdown_starting, NULL); + gpr_mu_unlock(&s->mu); + if (exec_ctx == NULL) { + grpc_exec_ctx_flush(&local_exec_ctx); + tcp_server_destroy(&local_exec_ctx, s); + grpc_exec_ctx_finish(&local_exec_ctx); + } else { + grpc_exec_ctx_finish(&local_exec_ctx); + tcp_server_destroy(exec_ctx, s); + } + } +} + +/* Prepare (bind) a recently-created socket for listening. */ +static int prepare_socket(SOCKET sock, const struct sockaddr *addr, + size_t addr_len) { + struct sockaddr_storage sockname_temp; + socklen_t sockname_len; + + if (sock == INVALID_SOCKET) goto error; + + if (!grpc_tcp_prepare_socket(sock)) { + char *utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, "Unable to prepare socket: %s", utf8_message); + gpr_free(utf8_message); + goto error; + } + + if (bind(sock, addr, (int)addr_len) == SOCKET_ERROR) { + char *addr_str; + char *utf8_message = gpr_format_message(WSAGetLastError()); + grpc_sockaddr_to_string(&addr_str, addr, 0); + gpr_log(GPR_ERROR, "bind addr=%s: %s", addr_str, utf8_message); + gpr_free(utf8_message); + gpr_free(addr_str); + goto error; + } + + if (listen(sock, SOMAXCONN) == SOCKET_ERROR) { + char *utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, "listen: %s", utf8_message); + gpr_free(utf8_message); + goto error; + } + + sockname_len = sizeof(sockname_temp); + if (getsockname(sock, (struct sockaddr *)&sockname_temp, &sockname_len) == + SOCKET_ERROR) { + char *utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, "getsockname: %s", utf8_message); + gpr_free(utf8_message); + goto error; + } + + return grpc_sockaddr_get_port((struct sockaddr *)&sockname_temp); + +error: + if (sock != INVALID_SOCKET) closesocket(sock); + return -1; +} + +static void decrement_active_ports_and_notify(grpc_exec_ctx *exec_ctx, + grpc_tcp_listener *sp) { + int notify = 0; + sp->shutting_down = 0; + gpr_mu_lock(&sp->server->mu); + GPR_ASSERT(sp->server->active_ports > 0); + if (0 == --sp->server->active_ports) { + notify = 1; + } + gpr_mu_unlock(&sp->server->mu); + if (notify) { + finish_shutdown(exec_ctx, sp->server); + } +} + +/* In order to do an async accept, we need to create a socket first which + will be the one assigned to the new incoming connection. */ +static void start_accept(grpc_exec_ctx *exec_ctx, grpc_tcp_listener *port) { + SOCKET sock = INVALID_SOCKET; + char *message; + char *utf8_message; + BOOL success; + DWORD addrlen = sizeof(struct sockaddr_in6) + 16; + DWORD bytes_received = 0; + + sock = WSASocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP, NULL, 0, + WSA_FLAG_OVERLAPPED); + + if (sock == INVALID_SOCKET) { + message = "Unable to create socket: %s"; + goto failure; + } + + if (!grpc_tcp_prepare_socket(sock)) { + message = "Unable to prepare socket: %s"; + goto failure; + } + + /* Start the "accept" asynchronously. */ + success = port->AcceptEx(port->socket->socket, sock, port->addresses, 0, + addrlen, addrlen, &bytes_received, + &port->socket->read_info.overlapped); + + /* It is possible to get an accept immediately without delay. However, we + will still get an IOCP notification for it. So let's just ignore it. */ + if (!success) { + int error = WSAGetLastError(); + if (error != ERROR_IO_PENDING) { + message = "AcceptEx failed: %s"; + goto failure; + } + } + + /* We're ready to do the accept. Calling grpc_socket_notify_on_read may + immediately process an accept that happened in the meantime. */ + port->new_socket = sock; + grpc_socket_notify_on_read(exec_ctx, port->socket, &port->on_accept); + return; + +failure: + if (port->shutting_down) { + /* We are abandoning the listener port, take that into account to prevent + occasional hangs on shutdown. The hang happens when sp->shutting_down + change is not seen by on_accept and we proceed to trying new accept, + but we fail there because the listening port has been closed in the + meantime. */ + decrement_active_ports_and_notify(exec_ctx, port); + return; + } + utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, message, utf8_message); + gpr_free(utf8_message); + if (sock != INVALID_SOCKET) closesocket(sock); +} + +/* Event manager callback when reads are ready. */ +static void on_accept(grpc_exec_ctx *exec_ctx, void *arg, bool from_iocp) { + grpc_tcp_listener *sp = arg; + grpc_tcp_server_acceptor acceptor = {sp->server, sp->port_index, 0}; + SOCKET sock = sp->new_socket; + grpc_winsocket_callback_info *info = &sp->socket->read_info; + grpc_endpoint *ep = NULL; + struct sockaddr_storage peer_name; + char *peer_name_string; + char *fd_name; + int peer_name_len = sizeof(peer_name); + DWORD transfered_bytes; + DWORD flags; + BOOL wsa_success; + int err; + + /* The general mechanism for shutting down is to queue abortion calls. While + this is necessary in the read/write case, it's useless for the accept + case. We only need to adjust the pending callback count */ + if (!from_iocp) { + return; + } + + /* The IOCP notified us of a completed operation. Let's grab the results, + and act accordingly. */ + transfered_bytes = 0; + wsa_success = WSAGetOverlappedResult(sock, &info->overlapped, + &transfered_bytes, FALSE, &flags); + if (!wsa_success) { + if (sp->shutting_down) { + /* During the shutdown case, we ARE expecting an error. So that's well, + and we can wake up the shutdown thread. */ + decrement_active_ports_and_notify(exec_ctx, sp); + return; + } else { + char *utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, "on_accept error: %s", utf8_message); + gpr_free(utf8_message); + closesocket(sock); + } + } else { + if (!sp->shutting_down) { + peer_name_string = NULL; + err = setsockopt(sock, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, + (char *)&sp->socket->socket, sizeof(sp->socket->socket)); + if (err) { + char *utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, "setsockopt error: %s", utf8_message); + gpr_free(utf8_message); + } + err = getpeername(sock, (struct sockaddr *)&peer_name, &peer_name_len); + if (!err) { + peer_name_string = grpc_sockaddr_to_uri((struct sockaddr *)&peer_name); + } else { + char *utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, "getpeername error: %s", utf8_message); + gpr_free(utf8_message); + } + gpr_asprintf(&fd_name, "tcp_server:%s", peer_name_string); + ep = grpc_tcp_create(grpc_winsocket_create(sock, fd_name), + peer_name_string); + gpr_free(fd_name); + gpr_free(peer_name_string); + } else { + closesocket(sock); + } + } + + /* The only time we should call our callback, is where we successfully + managed to accept a connection, and created an endpoint. */ + if (ep) + sp->server->on_accept_cb(exec_ctx, sp->server->on_accept_cb_arg, ep, + &acceptor); + /* As we were notified from the IOCP of one and exactly one accept, + the former socked we created has now either been destroy or assigned + to the new connection. We need to create a new one for the next + connection. */ + start_accept(exec_ctx, sp); +} + +static grpc_tcp_listener *add_socket_to_server(grpc_tcp_server *s, SOCKET sock, + const struct sockaddr *addr, + size_t addr_len, + unsigned port_index) { + grpc_tcp_listener *sp = NULL; + int port; + int status; + GUID guid = WSAID_ACCEPTEX; + DWORD ioctl_num_bytes; + LPFN_ACCEPTEX AcceptEx; + + if (sock == INVALID_SOCKET) return NULL; + + /* We need to grab the AcceptEx pointer for that port, as it may be + interface-dependent. We'll cache it to avoid doing that again. */ + status = + WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), + &AcceptEx, sizeof(AcceptEx), &ioctl_num_bytes, NULL, NULL); + + if (status != 0) { + char *utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, "on_connect error: %s", utf8_message); + gpr_free(utf8_message); + closesocket(sock); + return NULL; + } + + port = prepare_socket(sock, addr, addr_len); + if (port >= 0) { + gpr_mu_lock(&s->mu); + GPR_ASSERT(!s->on_accept_cb && "must add ports before starting server"); + sp = gpr_malloc(sizeof(grpc_tcp_listener)); + sp->next = NULL; + if (s->head == NULL) { + s->head = sp; + } else { + s->tail->next = sp; + } + s->tail = sp; + sp->server = s; + sp->socket = grpc_winsocket_create(sock, "listener"); + sp->shutting_down = 0; + sp->AcceptEx = AcceptEx; + sp->new_socket = INVALID_SOCKET; + sp->port = port; + sp->port_index = port_index; + grpc_closure_init(&sp->on_accept, on_accept, sp); + GPR_ASSERT(sp->socket); + gpr_mu_unlock(&s->mu); + } + + return sp; +} + +int grpc_tcp_server_add_port(grpc_tcp_server *s, const void *addr, + size_t addr_len) { + grpc_tcp_listener *sp; + SOCKET sock; + struct sockaddr_in6 addr6_v4mapped; + struct sockaddr_in6 wildcard; + struct sockaddr *allocated_addr = NULL; + struct sockaddr_storage sockname_temp; + socklen_t sockname_len; + int port; + unsigned port_index = 0; + if (s->tail != NULL) { + port_index = s->tail->port_index + 1; + } + + /* Check if this is a wildcard port, and if so, try to keep the port the same + as some previously created listener. */ + if (grpc_sockaddr_get_port(addr) == 0) { + for (sp = s->head; sp; sp = sp->next) { + sockname_len = sizeof(sockname_temp); + if (0 == getsockname(sp->socket->socket, + (struct sockaddr *)&sockname_temp, &sockname_len)) { + port = grpc_sockaddr_get_port((struct sockaddr *)&sockname_temp); + if (port > 0) { + allocated_addr = malloc(addr_len); + memcpy(allocated_addr, addr, addr_len); + grpc_sockaddr_set_port(allocated_addr, port); + addr = allocated_addr; + break; + } + } + } + } + + if (grpc_sockaddr_to_v4mapped(addr, &addr6_v4mapped)) { + addr = (const struct sockaddr *)&addr6_v4mapped; + addr_len = sizeof(addr6_v4mapped); + } + + /* Treat :: or 0.0.0.0 as a family-agnostic wildcard. */ + if (grpc_sockaddr_is_wildcard(addr, &port)) { + grpc_sockaddr_make_wildcard6(port, &wildcard); + + addr = (struct sockaddr *)&wildcard; + addr_len = sizeof(wildcard); + } + + sock = WSASocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP, NULL, 0, + WSA_FLAG_OVERLAPPED); + if (sock == INVALID_SOCKET) { + char *utf8_message = gpr_format_message(WSAGetLastError()); + gpr_log(GPR_ERROR, "unable to create socket: %s", utf8_message); + gpr_free(utf8_message); + } + + sp = add_socket_to_server(s, sock, addr, addr_len, port_index); + gpr_free(allocated_addr); + + if (sp) { + return sp->port; + } else { + return -1; + } +} + +unsigned grpc_tcp_server_port_fd_count(grpc_tcp_server *s, + unsigned port_index) { + grpc_tcp_listener *sp; + for (sp = s->head; sp && port_index != 0; sp = sp->next, --port_index) + ; + if (sp) { + return 1; + } else { + return 0; + } +} + +int grpc_tcp_server_port_fd(grpc_tcp_server *s, unsigned port_index, + unsigned fd_index) { + grpc_tcp_listener *sp; + if (fd_index != 0) { + /* Windows implementation has only one fd per port_index. */ + return -1; + } + for (sp = s->head; sp && port_index != 0; sp = sp->next, --port_index) + ; + if (sp) { + return _open_osfhandle((intptr_t)sp->socket->socket, 0); + } else { + return -1; + } +} + +void grpc_tcp_server_start(grpc_exec_ctx *exec_ctx, grpc_tcp_server *s, + grpc_pollset **pollset, size_t pollset_count, + grpc_tcp_server_cb on_accept_cb, + void *on_accept_cb_arg) { + grpc_tcp_listener *sp; + GPR_ASSERT(on_accept_cb); + gpr_mu_lock(&s->mu); + GPR_ASSERT(!s->on_accept_cb); + GPR_ASSERT(s->active_ports == 0); + s->on_accept_cb = on_accept_cb; + s->on_accept_cb_arg = on_accept_cb_arg; + for (sp = s->head; sp; sp = sp->next) { + start_accept(exec_ctx, sp); + s->active_ports++; + } + gpr_mu_unlock(&s->mu); +} + +#endif /* GPR_WINSOCK_SOCKET */ diff --git a/src/core/lib/iomgr/tcp_windows.c b/src/core/lib/iomgr/tcp_windows.c new file mode 100644 index 0000000000..c1ce725f2c --- /dev/null +++ b/src/core/lib/iomgr/tcp_windows.c @@ -0,0 +1,402 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WINSOCK_SOCKET + +#include "src/core/lib/iomgr/sockaddr_win32.h" + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/log_win32.h> +#include <grpc/support/slice_buffer.h> +#include <grpc/support/string_util.h> +#include <grpc/support/useful.h> + +#include "src/core/lib/iomgr/iocp_windows.h" +#include "src/core/lib/iomgr/sockaddr.h" +#include "src/core/lib/iomgr/sockaddr_utils.h" +#include "src/core/lib/iomgr/socket_windows.h" +#include "src/core/lib/iomgr/tcp_client.h" +#include "src/core/lib/iomgr/timer.h" + +static int set_non_block(SOCKET sock) { + int status; + unsigned long param = 1; + DWORD ret; + status = + WSAIoctl(sock, FIONBIO, ¶m, sizeof(param), NULL, 0, &ret, NULL, NULL); + return status == 0; +} + +static int set_dualstack(SOCKET sock) { + int status; + unsigned long param = 0; + status = setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)¶m, + sizeof(param)); + return status == 0; +} + +int grpc_tcp_prepare_socket(SOCKET sock) { + if (!set_non_block(sock)) return 0; + if (!set_dualstack(sock)) return 0; + return 1; +} + +typedef struct grpc_tcp { + /* This is our C++ class derivation emulation. */ + grpc_endpoint base; + /* The one socket this endpoint is using. */ + grpc_winsocket *socket; + /* Refcounting how many operations are in progress. */ + gpr_refcount refcount; + + grpc_closure on_read; + grpc_closure on_write; + + grpc_closure *read_cb; + grpc_closure *write_cb; + gpr_slice read_slice; + gpr_slice_buffer *write_slices; + gpr_slice_buffer *read_slices; + + /* The IO Completion Port runs from another thread. We need some mechanism + to protect ourselves when requesting a shutdown. */ + gpr_mu mu; + int shutting_down; + + char *peer_string; +} grpc_tcp; + +static void tcp_free(grpc_tcp *tcp) { + grpc_winsocket_destroy(tcp->socket); + gpr_mu_destroy(&tcp->mu); + gpr_free(tcp->peer_string); + gpr_free(tcp); +} + +/*#define GRPC_TCP_REFCOUNT_DEBUG*/ +#ifdef GRPC_TCP_REFCOUNT_DEBUG +#define TCP_UNREF(tcp, reason) tcp_unref((tcp), (reason), __FILE__, __LINE__) +#define TCP_REF(tcp, reason) tcp_ref((tcp), (reason), __FILE__, __LINE__) +static void tcp_unref(grpc_tcp *tcp, const char *reason, const char *file, + int line) { + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, "TCP unref %p : %s %d -> %d", tcp, + reason, tcp->refcount.count, tcp->refcount.count - 1); + if (gpr_unref(&tcp->refcount)) { + tcp_free(tcp); + } +} + +static void tcp_ref(grpc_tcp *tcp, const char *reason, const char *file, + int line) { + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, "TCP ref %p : %s %d -> %d", tcp, + reason, tcp->refcount.count, tcp->refcount.count + 1); + gpr_ref(&tcp->refcount); +} +#else +#define TCP_UNREF(tcp, reason) tcp_unref((tcp)) +#define TCP_REF(tcp, reason) tcp_ref((tcp)) +static void tcp_unref(grpc_tcp *tcp) { + if (gpr_unref(&tcp->refcount)) { + tcp_free(tcp); + } +} + +static void tcp_ref(grpc_tcp *tcp) { gpr_ref(&tcp->refcount); } +#endif + +/* Asynchronous callback from the IOCP, or the background thread. */ +static void on_read(grpc_exec_ctx *exec_ctx, void *tcpp, bool success) { + grpc_tcp *tcp = tcpp; + grpc_closure *cb = tcp->read_cb; + grpc_winsocket *socket = tcp->socket; + gpr_slice sub; + grpc_winsocket_callback_info *info = &socket->read_info; + + if (success) { + if (info->wsa_error != 0 && !tcp->shutting_down) { + if (info->wsa_error != WSAECONNRESET) { + char *utf8_message = gpr_format_message(info->wsa_error); + gpr_log(GPR_ERROR, "ReadFile overlapped error: %s", utf8_message); + gpr_free(utf8_message); + } + success = 0; + gpr_slice_unref(tcp->read_slice); + } else { + if (info->bytes_transfered != 0 && !tcp->shutting_down) { + sub = gpr_slice_sub_no_ref(tcp->read_slice, 0, info->bytes_transfered); + gpr_slice_buffer_add(tcp->read_slices, sub); + success = 1; + } else { + gpr_slice_unref(tcp->read_slice); + success = 0; + } + } + } + + tcp->read_cb = NULL; + TCP_UNREF(tcp, "read"); + if (cb) { + cb->cb(exec_ctx, cb->cb_arg, success); + } +} + +static void win_read(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, + gpr_slice_buffer *read_slices, grpc_closure *cb) { + grpc_tcp *tcp = (grpc_tcp *)ep; + grpc_winsocket *handle = tcp->socket; + grpc_winsocket_callback_info *info = &handle->read_info; + int status; + DWORD bytes_read = 0; + DWORD flags = 0; + WSABUF buffer; + + if (tcp->shutting_down) { + grpc_exec_ctx_enqueue(exec_ctx, cb, false, NULL); + return; + } + + tcp->read_cb = cb; + tcp->read_slices = read_slices; + gpr_slice_buffer_reset_and_unref(read_slices); + + tcp->read_slice = gpr_slice_malloc(8192); + + buffer.len = (ULONG)GPR_SLICE_LENGTH( + tcp->read_slice); // we know slice size fits in 32bit. + buffer.buf = (char *)GPR_SLICE_START_PTR(tcp->read_slice); + + TCP_REF(tcp, "read"); + + /* First let's try a synchronous, non-blocking read. */ + status = + WSARecv(tcp->socket->socket, &buffer, 1, &bytes_read, &flags, NULL, NULL); + info->wsa_error = status == 0 ? 0 : WSAGetLastError(); + + /* Did we get data immediately ? Yay. */ + if (info->wsa_error != WSAEWOULDBLOCK) { + info->bytes_transfered = bytes_read; + grpc_exec_ctx_enqueue(exec_ctx, &tcp->on_read, true, NULL); + return; + } + + /* Otherwise, let's retry, by queuing a read. */ + memset(&tcp->socket->read_info.overlapped, 0, sizeof(OVERLAPPED)); + status = WSARecv(tcp->socket->socket, &buffer, 1, &bytes_read, &flags, + &info->overlapped, NULL); + + if (status != 0) { + int wsa_error = WSAGetLastError(); + if (wsa_error != WSA_IO_PENDING) { + info->wsa_error = wsa_error; + grpc_exec_ctx_enqueue(exec_ctx, &tcp->on_read, false, NULL); + return; + } + } + + grpc_socket_notify_on_read(exec_ctx, tcp->socket, &tcp->on_read); +} + +/* Asynchronous callback from the IOCP, or the background thread. */ +static void on_write(grpc_exec_ctx *exec_ctx, void *tcpp, bool success) { + grpc_tcp *tcp = (grpc_tcp *)tcpp; + grpc_winsocket *handle = tcp->socket; + grpc_winsocket_callback_info *info = &handle->write_info; + grpc_closure *cb; + + gpr_mu_lock(&tcp->mu); + cb = tcp->write_cb; + tcp->write_cb = NULL; + gpr_mu_unlock(&tcp->mu); + + if (success) { + if (info->wsa_error != 0) { + if (info->wsa_error != WSAECONNRESET) { + char *utf8_message = gpr_format_message(info->wsa_error); + gpr_log(GPR_ERROR, "WSASend overlapped error: %s", utf8_message); + gpr_free(utf8_message); + } + success = 0; + } else { + GPR_ASSERT(info->bytes_transfered == tcp->write_slices->length); + } + } + + TCP_UNREF(tcp, "write"); + cb->cb(exec_ctx, cb->cb_arg, success); +} + +/* Initiates a write. */ +static void win_write(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, + gpr_slice_buffer *slices, grpc_closure *cb) { + grpc_tcp *tcp = (grpc_tcp *)ep; + grpc_winsocket *socket = tcp->socket; + grpc_winsocket_callback_info *info = &socket->write_info; + unsigned i; + DWORD bytes_sent; + int status; + WSABUF local_buffers[16]; + WSABUF *allocated = NULL; + WSABUF *buffers = local_buffers; + size_t len; + + if (tcp->shutting_down) { + grpc_exec_ctx_enqueue(exec_ctx, cb, false, NULL); + return; + } + + tcp->write_cb = cb; + tcp->write_slices = slices; + GPR_ASSERT(tcp->write_slices->count <= UINT_MAX); + if (tcp->write_slices->count > GPR_ARRAY_SIZE(local_buffers)) { + buffers = (WSABUF *)gpr_malloc(sizeof(WSABUF) * tcp->write_slices->count); + allocated = buffers; + } + + for (i = 0; i < tcp->write_slices->count; i++) { + len = GPR_SLICE_LENGTH(tcp->write_slices->slices[i]); + GPR_ASSERT(len <= ULONG_MAX); + buffers[i].len = (ULONG)len; + buffers[i].buf = (char *)GPR_SLICE_START_PTR(tcp->write_slices->slices[i]); + } + + /* First, let's try a synchronous, non-blocking write. */ + status = WSASend(socket->socket, buffers, (DWORD)tcp->write_slices->count, + &bytes_sent, 0, NULL, NULL); + info->wsa_error = status == 0 ? 0 : WSAGetLastError(); + + /* We would kind of expect to get a WSAEWOULDBLOCK here, especially on a busy + connection that has its send queue filled up. But if we don't, then we can + avoid doing an async write operation at all. */ + if (info->wsa_error != WSAEWOULDBLOCK) { + bool ok = false; + if (status == 0) { + ok = true; + GPR_ASSERT(bytes_sent == tcp->write_slices->length); + } else { + if (info->wsa_error != WSAECONNRESET) { + char *utf8_message = gpr_format_message(info->wsa_error); + gpr_log(GPR_ERROR, "WSASend error: %s", utf8_message); + gpr_free(utf8_message); + } + } + if (allocated) gpr_free(allocated); + grpc_exec_ctx_enqueue(exec_ctx, cb, ok, NULL); + return; + } + + TCP_REF(tcp, "write"); + + /* If we got a WSAEWOULDBLOCK earlier, then we need to re-do the same + operation, this time asynchronously. */ + memset(&socket->write_info.overlapped, 0, sizeof(OVERLAPPED)); + status = WSASend(socket->socket, buffers, (DWORD)tcp->write_slices->count, + &bytes_sent, 0, &socket->write_info.overlapped, NULL); + if (allocated) gpr_free(allocated); + + if (status != 0) { + int wsa_error = WSAGetLastError(); + if (wsa_error != WSA_IO_PENDING) { + TCP_UNREF(tcp, "write"); + grpc_exec_ctx_enqueue(exec_ctx, cb, false, NULL); + return; + } + } + + /* As all is now setup, we can now ask for the IOCP notification. It may + trigger the callback immediately however, but no matter. */ + grpc_socket_notify_on_write(exec_ctx, socket, &tcp->on_write); +} + +static void win_add_to_pollset(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, + grpc_pollset *ps) { + grpc_tcp *tcp; + (void)ps; + tcp = (grpc_tcp *)ep; + grpc_iocp_add_socket(tcp->socket); +} + +static void win_add_to_pollset_set(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, + grpc_pollset_set *pss) { + grpc_tcp *tcp; + (void)pss; + tcp = (grpc_tcp *)ep; + grpc_iocp_add_socket(tcp->socket); +} + +/* Initiates a shutdown of the TCP endpoint. This will queue abort callbacks + for the potential read and write operations. It is up to the caller to + guarantee this isn't called in parallel to a read or write request, so + we're not going to protect against these. However the IO Completion Port + callback will happen from another thread, so we need to protect against + concurrent access of the data structure in that regard. */ +static void win_shutdown(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep) { + grpc_tcp *tcp = (grpc_tcp *)ep; + gpr_mu_lock(&tcp->mu); + /* At that point, what may happen is that we're already inside the IOCP + callback. See the comments in on_read and on_write. */ + tcp->shutting_down = 1; + grpc_winsocket_shutdown(tcp->socket); + gpr_mu_unlock(&tcp->mu); +} + +static void win_destroy(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep) { + grpc_tcp *tcp = (grpc_tcp *)ep; + TCP_UNREF(tcp, "destroy"); +} + +static char *win_get_peer(grpc_endpoint *ep) { + grpc_tcp *tcp = (grpc_tcp *)ep; + return gpr_strdup(tcp->peer_string); +} + +static grpc_endpoint_vtable vtable = { + win_read, win_write, win_add_to_pollset, win_add_to_pollset_set, + win_shutdown, win_destroy, win_get_peer}; + +grpc_endpoint *grpc_tcp_create(grpc_winsocket *socket, char *peer_string) { + grpc_tcp *tcp = (grpc_tcp *)gpr_malloc(sizeof(grpc_tcp)); + memset(tcp, 0, sizeof(grpc_tcp)); + tcp->base.vtable = &vtable; + tcp->socket = socket; + gpr_mu_init(&tcp->mu); + gpr_ref_init(&tcp->refcount, 1); + grpc_closure_init(&tcp->on_read, on_read, tcp); + grpc_closure_init(&tcp->on_write, on_write, tcp); + tcp->peer_string = gpr_strdup(peer_string); + return &tcp->base; +} + +#endif /* GPR_WINSOCK_SOCKET */ diff --git a/src/core/lib/iomgr/tcp_windows.h b/src/core/lib/iomgr/tcp_windows.h new file mode 100644 index 0000000000..7a9ebd85eb --- /dev/null +++ b/src/core/lib/iomgr/tcp_windows.h @@ -0,0 +1,57 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_TCP_WINDOWS_H +#define GRPC_CORE_LIB_IOMGR_TCP_WINDOWS_H +/* + Low level TCP "bottom half" implementation, for use by transports built on + top of a TCP connection. + + Note that this file does not (yet) include APIs for creating the socket in + the first place. + + All calls passing slice transfer ownership of a slice refcount unless + otherwise specified. +*/ + +#include "src/core/lib/iomgr/endpoint.h" +#include "src/core/lib/iomgr/socket_windows.h" + +/* Create a tcp endpoint given a winsock handle. + * Takes ownership of the handle. + */ +grpc_endpoint *grpc_tcp_create(grpc_winsocket *socket, char *peer_string); + +int grpc_tcp_prepare_socket(SOCKET sock); + +#endif /* GRPC_CORE_LIB_IOMGR_TCP_WINDOWS_H */ diff --git a/src/core/lib/iomgr/time_averaged_stats.c b/src/core/lib/iomgr/time_averaged_stats.c new file mode 100644 index 0000000000..f24d68087e --- /dev/null +++ b/src/core/lib/iomgr/time_averaged_stats.c @@ -0,0 +1,77 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/iomgr/time_averaged_stats.h" + +void grpc_time_averaged_stats_init(grpc_time_averaged_stats* stats, + double init_avg, double regress_weight, + double persistence_factor) { + stats->init_avg = init_avg; + stats->regress_weight = regress_weight; + stats->persistence_factor = persistence_factor; + stats->batch_total_value = 0; + stats->batch_num_samples = 0; + stats->aggregate_total_weight = 0; + stats->aggregate_weighted_avg = init_avg; +} + +void grpc_time_averaged_stats_add_sample(grpc_time_averaged_stats* stats, + double value) { + stats->batch_total_value += value; + ++stats->batch_num_samples; +} + +double grpc_time_averaged_stats_update_average( + grpc_time_averaged_stats* stats) { + /* Start with the current batch: */ + double weighted_sum = stats->batch_total_value; + double total_weight = stats->batch_num_samples; + if (stats->regress_weight > 0) { + /* Add in the regression towards init_avg_: */ + weighted_sum += stats->regress_weight * stats->init_avg; + total_weight += stats->regress_weight; + } + if (stats->persistence_factor > 0) { + /* Add in the persistence: */ + const double prev_sample_weight = + stats->persistence_factor * stats->aggregate_total_weight; + weighted_sum += prev_sample_weight * stats->aggregate_weighted_avg; + total_weight += prev_sample_weight; + } + stats->aggregate_weighted_avg = + (total_weight > 0) ? (weighted_sum / total_weight) : stats->init_avg; + stats->aggregate_total_weight = total_weight; + stats->batch_num_samples = 0; + stats->batch_total_value = 0; + return stats->aggregate_weighted_avg; +} diff --git a/src/core/lib/iomgr/time_averaged_stats.h b/src/core/lib/iomgr/time_averaged_stats.h new file mode 100644 index 0000000000..4a662e17ec --- /dev/null +++ b/src/core/lib/iomgr/time_averaged_stats.h @@ -0,0 +1,88 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_TIME_AVERAGED_STATS_H +#define GRPC_CORE_LIB_IOMGR_TIME_AVERAGED_STATS_H + +/* This tracks a time-decaying weighted average. It works by collecting + batches of samples and then mixing their average into a time-decaying + weighted mean. It is designed for batch operations where we do many adds + before updating the average. */ + +typedef struct { + /* The initial average value. This is the reported average until the first + grpc_time_averaged_stats_update_average call. If a positive regress_weight + is used, we also regress towards this value on each update. */ + double init_avg; + /* The sample weight of "init_avg" that is mixed in with each call to + grpc_time_averaged_stats_update_average. If the calls to + grpc_time_averaged_stats_add_sample stop, this will cause the average to + regress back to the mean. This should be non-negative. Set it to 0 to + disable the bias. A value of 1 has the effect of adding in 1 bonus sample + with value init_avg to each sample period. */ + double regress_weight; + /* This determines the rate of decay of the time-averaging from one period + to the next by scaling the aggregate_total_weight of samples from prior + periods when combining with the latest period. It should be in the range + [0,1]. A higher value adapts more slowly. With a value of 0.5, if the + batches each have k samples, the samples_in_avg_ will grow to 2 k, so the + weighting of the time average will eventually be 1/3 new batch and 2/3 + old average. */ + double persistence_factor; + + /* The total value of samples since the last UpdateAverage(). */ + double batch_total_value; + /* The number of samples since the last UpdateAverage(). */ + double batch_num_samples; + /* The time-decayed sum of batch_num_samples_ over previous batches. This is + the "weight" of the old aggregate_weighted_avg_ when updating the + average. */ + double aggregate_total_weight; + /* A time-decayed average of the (batch_total_value_ / batch_num_samples_), + computed by decaying the samples_in_avg_ weight in the weighted average. */ + double aggregate_weighted_avg; +} grpc_time_averaged_stats; + +/* See the comments on the members above for an explanation of init_avg, + regress_weight, and persistence_factor. */ +void grpc_time_averaged_stats_init(grpc_time_averaged_stats* stats, + double init_avg, double regress_weight, + double persistence_factor); +/* Add a sample to the current batch. */ +void grpc_time_averaged_stats_add_sample(grpc_time_averaged_stats* stats, + double value); +/* Complete a batch and compute the new estimate of the average sample + value. */ +double grpc_time_averaged_stats_update_average(grpc_time_averaged_stats* stats); + +#endif /* GRPC_CORE_LIB_IOMGR_TIME_AVERAGED_STATS_H */ diff --git a/src/core/lib/iomgr/timer.c b/src/core/lib/iomgr/timer.c new file mode 100644 index 0000000000..4748f9b270 --- /dev/null +++ b/src/core/lib/iomgr/timer.c @@ -0,0 +1,356 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/iomgr/timer.h" + +#include <grpc/support/log.h> +#include <grpc/support/sync.h> +#include <grpc/support/useful.h> +#include "src/core/lib/iomgr/time_averaged_stats.h" +#include "src/core/lib/iomgr/timer_heap.h" + +#define INVALID_HEAP_INDEX 0xffffffffu + +#define LOG2_NUM_SHARDS 5 +#define NUM_SHARDS (1 << LOG2_NUM_SHARDS) +#define ADD_DEADLINE_SCALE 0.33 +#define MIN_QUEUE_WINDOW_DURATION 0.01 +#define MAX_QUEUE_WINDOW_DURATION 1 + +typedef struct { + gpr_mu mu; + grpc_time_averaged_stats stats; + /* All and only timers with deadlines <= this will be in the heap. */ + gpr_timespec queue_deadline_cap; + gpr_timespec min_deadline; + /* Index in the g_shard_queue */ + uint32_t shard_queue_index; + /* This holds all timers with deadlines < queue_deadline_cap. Timers in this + list have the top bit of their deadline set to 0. */ + grpc_timer_heap heap; + /* This holds timers whose deadline is >= queue_deadline_cap. */ + grpc_timer list; +} shard_type; + +/* Protects g_shard_queue */ +static gpr_mu g_mu; +/* Allow only one run_some_expired_timers at once */ +static gpr_mu g_checker_mu; +static gpr_clock_type g_clock_type; +static shard_type g_shards[NUM_SHARDS]; +/* Protected by g_mu */ +static shard_type *g_shard_queue[NUM_SHARDS]; + +static int run_some_expired_timers(grpc_exec_ctx *exec_ctx, gpr_timespec now, + gpr_timespec *next, int success); + +static gpr_timespec compute_min_deadline(shard_type *shard) { + return grpc_timer_heap_is_empty(&shard->heap) + ? shard->queue_deadline_cap + : grpc_timer_heap_top(&shard->heap)->deadline; +} + +void grpc_timer_list_init(gpr_timespec now) { + uint32_t i; + + gpr_mu_init(&g_mu); + gpr_mu_init(&g_checker_mu); + g_clock_type = now.clock_type; + + for (i = 0; i < NUM_SHARDS; i++) { + shard_type *shard = &g_shards[i]; + gpr_mu_init(&shard->mu); + grpc_time_averaged_stats_init(&shard->stats, 1.0 / ADD_DEADLINE_SCALE, 0.1, + 0.5); + shard->queue_deadline_cap = now; + shard->shard_queue_index = i; + grpc_timer_heap_init(&shard->heap); + shard->list.next = shard->list.prev = &shard->list; + shard->min_deadline = compute_min_deadline(shard); + g_shard_queue[i] = shard; + } +} + +void grpc_timer_list_shutdown(grpc_exec_ctx *exec_ctx) { + int i; + run_some_expired_timers(exec_ctx, gpr_inf_future(g_clock_type), NULL, 0); + for (i = 0; i < NUM_SHARDS; i++) { + shard_type *shard = &g_shards[i]; + gpr_mu_destroy(&shard->mu); + grpc_timer_heap_destroy(&shard->heap); + } + gpr_mu_destroy(&g_mu); + gpr_mu_destroy(&g_checker_mu); +} + +/* This is a cheap, but good enough, pointer hash for sharding the tasks: */ +static size_t shard_idx(const grpc_timer *info) { + size_t x = (size_t)info; + return ((x >> 4) ^ (x >> 9) ^ (x >> 14)) & (NUM_SHARDS - 1); +} + +static double ts_to_dbl(gpr_timespec ts) { + return (double)ts.tv_sec + 1e-9 * ts.tv_nsec; +} + +static gpr_timespec dbl_to_ts(double d) { + gpr_timespec ts; + ts.tv_sec = (int64_t)d; + ts.tv_nsec = (int32_t)(1e9 * (d - (double)ts.tv_sec)); + ts.clock_type = GPR_TIMESPAN; + return ts; +} + +static void list_join(grpc_timer *head, grpc_timer *timer) { + timer->next = head; + timer->prev = head->prev; + timer->next->prev = timer->prev->next = timer; +} + +static void list_remove(grpc_timer *timer) { + timer->next->prev = timer->prev; + timer->prev->next = timer->next; +} + +static void swap_adjacent_shards_in_queue(uint32_t first_shard_queue_index) { + shard_type *temp; + temp = g_shard_queue[first_shard_queue_index]; + g_shard_queue[first_shard_queue_index] = + g_shard_queue[first_shard_queue_index + 1]; + g_shard_queue[first_shard_queue_index + 1] = temp; + g_shard_queue[first_shard_queue_index]->shard_queue_index = + first_shard_queue_index; + g_shard_queue[first_shard_queue_index + 1]->shard_queue_index = + first_shard_queue_index + 1; +} + +static void note_deadline_change(shard_type *shard) { + while (shard->shard_queue_index > 0 && + gpr_time_cmp( + shard->min_deadline, + g_shard_queue[shard->shard_queue_index - 1]->min_deadline) < 0) { + swap_adjacent_shards_in_queue(shard->shard_queue_index - 1); + } + while (shard->shard_queue_index < NUM_SHARDS - 1 && + gpr_time_cmp( + shard->min_deadline, + g_shard_queue[shard->shard_queue_index + 1]->min_deadline) > 0) { + swap_adjacent_shards_in_queue(shard->shard_queue_index); + } +} + +void grpc_timer_init(grpc_exec_ctx *exec_ctx, grpc_timer *timer, + gpr_timespec deadline, grpc_iomgr_cb_func timer_cb, + void *timer_cb_arg, gpr_timespec now) { + int is_first_timer = 0; + shard_type *shard = &g_shards[shard_idx(timer)]; + GPR_ASSERT(deadline.clock_type == g_clock_type); + GPR_ASSERT(now.clock_type == g_clock_type); + grpc_closure_init(&timer->closure, timer_cb, timer_cb_arg); + timer->deadline = deadline; + timer->triggered = 0; + + /* TODO(ctiller): check deadline expired */ + + gpr_mu_lock(&shard->mu); + grpc_time_averaged_stats_add_sample(&shard->stats, + ts_to_dbl(gpr_time_sub(deadline, now))); + if (gpr_time_cmp(deadline, shard->queue_deadline_cap) < 0) { + is_first_timer = grpc_timer_heap_add(&shard->heap, timer); + } else { + timer->heap_index = INVALID_HEAP_INDEX; + list_join(&shard->list, timer); + } + gpr_mu_unlock(&shard->mu); + + /* Deadline may have decreased, we need to adjust the master queue. Note + that there is a potential racy unlocked region here. There could be a + reordering of multiple grpc_timer_init calls, at this point, but the < test + below should ensure that we err on the side of caution. There could + also be a race with grpc_timer_check, which might beat us to the lock. In + that case, it is possible that the timer that we added will have already + run by the time we hold the lock, but that too is a safe error. + Finally, it's possible that the grpc_timer_check that intervened failed to + trigger the new timer because the min_deadline hadn't yet been reduced. + In that case, the timer will simply have to wait for the next + grpc_timer_check. */ + if (is_first_timer) { + gpr_mu_lock(&g_mu); + if (gpr_time_cmp(deadline, shard->min_deadline) < 0) { + gpr_timespec old_min_deadline = g_shard_queue[0]->min_deadline; + shard->min_deadline = deadline; + note_deadline_change(shard); + if (shard->shard_queue_index == 0 && + gpr_time_cmp(deadline, old_min_deadline) < 0) { + grpc_kick_poller(); + } + } + gpr_mu_unlock(&g_mu); + } +} + +void grpc_timer_cancel(grpc_exec_ctx *exec_ctx, grpc_timer *timer) { + shard_type *shard = &g_shards[shard_idx(timer)]; + gpr_mu_lock(&shard->mu); + if (!timer->triggered) { + grpc_exec_ctx_enqueue(exec_ctx, &timer->closure, false, NULL); + timer->triggered = 1; + if (timer->heap_index == INVALID_HEAP_INDEX) { + list_remove(timer); + } else { + grpc_timer_heap_remove(&shard->heap, timer); + } + } + gpr_mu_unlock(&shard->mu); +} + +/* This is called when the queue is empty and "now" has reached the + queue_deadline_cap. We compute a new queue deadline and then scan the map + for timers that fall at or under it. Returns true if the queue is no + longer empty. + REQUIRES: shard->mu locked */ +static int refill_queue(shard_type *shard, gpr_timespec now) { + /* Compute the new queue window width and bound by the limits: */ + double computed_deadline_delta = + grpc_time_averaged_stats_update_average(&shard->stats) * + ADD_DEADLINE_SCALE; + double deadline_delta = + GPR_CLAMP(computed_deadline_delta, MIN_QUEUE_WINDOW_DURATION, + MAX_QUEUE_WINDOW_DURATION); + grpc_timer *timer, *next; + + /* Compute the new cap and put all timers under it into the queue: */ + shard->queue_deadline_cap = gpr_time_add( + gpr_time_max(now, shard->queue_deadline_cap), dbl_to_ts(deadline_delta)); + for (timer = shard->list.next; timer != &shard->list; timer = next) { + next = timer->next; + + if (gpr_time_cmp(timer->deadline, shard->queue_deadline_cap) < 0) { + list_remove(timer); + grpc_timer_heap_add(&shard->heap, timer); + } + } + return !grpc_timer_heap_is_empty(&shard->heap); +} + +/* This pops the next non-cancelled timer with deadline <= now from the queue, + or returns NULL if there isn't one. + REQUIRES: shard->mu locked */ +static grpc_timer *pop_one(shard_type *shard, gpr_timespec now) { + grpc_timer *timer; + for (;;) { + if (grpc_timer_heap_is_empty(&shard->heap)) { + if (gpr_time_cmp(now, shard->queue_deadline_cap) < 0) return NULL; + if (!refill_queue(shard, now)) return NULL; + } + timer = grpc_timer_heap_top(&shard->heap); + if (gpr_time_cmp(timer->deadline, now) > 0) return NULL; + timer->triggered = 1; + grpc_timer_heap_pop(&shard->heap); + return timer; + } +} + +/* REQUIRES: shard->mu unlocked */ +static size_t pop_timers(grpc_exec_ctx *exec_ctx, shard_type *shard, + gpr_timespec now, gpr_timespec *new_min_deadline, + int success) { + size_t n = 0; + grpc_timer *timer; + gpr_mu_lock(&shard->mu); + while ((timer = pop_one(shard, now))) { + grpc_exec_ctx_enqueue(exec_ctx, &timer->closure, success, NULL); + n++; + } + *new_min_deadline = compute_min_deadline(shard); + gpr_mu_unlock(&shard->mu); + return n; +} + +static int run_some_expired_timers(grpc_exec_ctx *exec_ctx, gpr_timespec now, + gpr_timespec *next, int success) { + size_t n = 0; + + /* TODO(ctiller): verify that there are any timers (atomically) here */ + + if (gpr_mu_trylock(&g_checker_mu)) { + gpr_mu_lock(&g_mu); + + while (gpr_time_cmp(g_shard_queue[0]->min_deadline, now) < 0) { + gpr_timespec new_min_deadline; + + /* For efficiency, we pop as many available timers as we can from the + shard. This may violate perfect timer deadline ordering, but that + shouldn't be a big deal because we don't make ordering guarantees. */ + n += pop_timers(exec_ctx, g_shard_queue[0], now, &new_min_deadline, + success); + + /* An grpc_timer_init() on the shard could intervene here, adding a new + timer that is earlier than new_min_deadline. However, + grpc_timer_init() will block on the master_lock before it can call + set_min_deadline, so this one will complete first and then the Addtimer + will reduce the min_deadline (perhaps unnecessarily). */ + g_shard_queue[0]->min_deadline = new_min_deadline; + note_deadline_change(g_shard_queue[0]); + } + + if (next) { + *next = gpr_time_min(*next, g_shard_queue[0]->min_deadline); + } + + gpr_mu_unlock(&g_mu); + gpr_mu_unlock(&g_checker_mu); + } else if (next != NULL) { + /* TODO(ctiller): this forces calling code to do an short poll, and + then retry the timer check (because this time through the timer list was + contended). + + We could reduce the cost here dramatically by keeping a count of how many + currently active pollers got through the uncontended case above + successfully, and waking up other pollers IFF that count drops to zero. + + Once that count is in place, this entire else branch could disappear. */ + *next = gpr_time_min( + *next, gpr_time_add(now, gpr_time_from_millis(1, GPR_TIMESPAN))); + } + + return (int)n; +} + +bool grpc_timer_check(grpc_exec_ctx *exec_ctx, gpr_timespec now, + gpr_timespec *next) { + GPR_ASSERT(now.clock_type == g_clock_type); + return run_some_expired_timers( + exec_ctx, now, next, + gpr_time_cmp(now, gpr_inf_future(now.clock_type)) != 0); +} diff --git a/src/core/lib/iomgr/timer.h b/src/core/lib/iomgr/timer.h new file mode 100644 index 0000000000..54f301c5ed --- /dev/null +++ b/src/core/lib/iomgr/timer.h @@ -0,0 +1,108 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_TIMER_H +#define GRPC_CORE_LIB_IOMGR_TIMER_H + +#include <grpc/support/port_platform.h> +#include <grpc/support/time.h> +#include "src/core/lib/iomgr/exec_ctx.h" +#include "src/core/lib/iomgr/iomgr.h" + +typedef struct grpc_timer { + gpr_timespec deadline; + uint32_t heap_index; /* INVALID_HEAP_INDEX if not in heap */ + int triggered; + struct grpc_timer *next; + struct grpc_timer *prev; + grpc_closure closure; +} grpc_timer; + +/* Initialize *timer. When expired or canceled, timer_cb will be called with + *timer_cb_arg and status to indicate if it expired (SUCCESS) or was + canceled (CANCELLED). timer_cb is guaranteed to be called exactly once, + and application code should check the status to determine how it was + invoked. The application callback is also responsible for maintaining + information about when to free up any user-level state. */ +void grpc_timer_init(grpc_exec_ctx *exec_ctx, grpc_timer *timer, + gpr_timespec deadline, grpc_iomgr_cb_func timer_cb, + void *timer_cb_arg, gpr_timespec now); + +/* Note that there is no timer destroy function. This is because the + timer is a one-time occurrence with a guarantee that the callback will + be called exactly once, either at expiration or cancellation. Thus, all + the internal timer event management state is destroyed just before + that callback is invoked. If the user has additional state associated with + the timer, the user is responsible for determining when it is safe to + destroy that state. */ + +/* Cancel an *timer. + There are three cases: + 1. We normally cancel the timer + 2. The timer has already run + 3. We can't cancel the timer because it is "in flight". + + In all of these cases, the cancellation is still considered successful. + They are essentially distinguished in that the timer_cb will be run + exactly once from either the cancellation (with status CANCELLED) + or from the activation (with status SUCCESS) + + Note carefully that the callback function MAY occur in the same callstack + as grpc_timer_cancel. It's expected that most timers will be cancelled (their + primary use is to implement deadlines), and so this code is optimized such + that cancellation costs as little as possible. Making callbacks run inline + matches this aim. + + Requires: cancel() must happen after add() on a given timer */ +void grpc_timer_cancel(grpc_exec_ctx *exec_ctx, grpc_timer *timer); + +/* iomgr internal api for dealing with timers */ + +/* Check for timers to be run, and run them. + Return true if timer callbacks were executed. + Drops drop_mu if it is non-null before executing callbacks. + If next is non-null, TRY to update *next with the next running timer + IF that timer occurs before *next current value. + *next is never guaranteed to be updated on any given execution; however, + with high probability at least one thread in the system will see an update + at any time slice. */ +bool grpc_timer_check(grpc_exec_ctx *exec_ctx, gpr_timespec now, + gpr_timespec *next); +void grpc_timer_list_init(gpr_timespec now); +void grpc_timer_list_shutdown(grpc_exec_ctx *exec_ctx); + +/* the following must be implemented by each iomgr implementation */ + +void grpc_kick_poller(void); + +#endif /* GRPC_CORE_LIB_IOMGR_TIMER_H */ diff --git a/src/core/lib/iomgr/timer_heap.c b/src/core/lib/iomgr/timer_heap.c new file mode 100644 index 0000000000..d43b6ccf75 --- /dev/null +++ b/src/core/lib/iomgr/timer_heap.c @@ -0,0 +1,146 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/iomgr/timer_heap.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/useful.h> + +/* Adjusts a heap so as to move a hole at position i closer to the root, + until a suitable position is found for element t. Then, copies t into that + position. This functor is called each time immediately after modifying a + value in the underlying container, with the offset of the modified element as + its argument. */ +static void adjust_upwards(grpc_timer **first, uint32_t i, grpc_timer *t) { + while (i > 0) { + uint32_t parent = (uint32_t)(((int)i - 1) / 2); + if (gpr_time_cmp(first[parent]->deadline, t->deadline) <= 0) break; + first[i] = first[parent]; + first[i]->heap_index = i; + i = parent; + } + first[i] = t; + t->heap_index = i; +} + +/* Adjusts a heap so as to move a hole at position i farther away from the root, + until a suitable position is found for element t. Then, copies t into that + position. */ +static void adjust_downwards(grpc_timer **first, uint32_t i, uint32_t length, + grpc_timer *t) { + for (;;) { + uint32_t left_child = 1u + 2u * i; + if (left_child >= length) break; + uint32_t right_child = left_child + 1; + uint32_t next_i = right_child < length && + gpr_time_cmp(first[left_child]->deadline, + first[right_child]->deadline) > 0 + ? right_child + : left_child; + if (gpr_time_cmp(t->deadline, first[next_i]->deadline) <= 0) break; + first[i] = first[next_i]; + first[i]->heap_index = i; + i = next_i; + } + first[i] = t; + t->heap_index = i; +} + +#define SHRINK_MIN_ELEMS 8 +#define SHRINK_FULLNESS_FACTOR 2 + +static void maybe_shrink(grpc_timer_heap *heap) { + if (heap->timer_count >= 8 && + heap->timer_count <= heap->timer_capacity / SHRINK_FULLNESS_FACTOR / 2) { + heap->timer_capacity = heap->timer_count * SHRINK_FULLNESS_FACTOR; + heap->timers = + gpr_realloc(heap->timers, heap->timer_capacity * sizeof(grpc_timer *)); + } +} + +static void note_changed_priority(grpc_timer_heap *heap, grpc_timer *timer) { + uint32_t i = timer->heap_index; + uint32_t parent = (uint32_t)(((int)i - 1) / 2); + if (gpr_time_cmp(heap->timers[parent]->deadline, timer->deadline) > 0) { + adjust_upwards(heap->timers, i, timer); + } else { + adjust_downwards(heap->timers, i, heap->timer_count, timer); + } +} + +void grpc_timer_heap_init(grpc_timer_heap *heap) { + memset(heap, 0, sizeof(*heap)); +} + +void grpc_timer_heap_destroy(grpc_timer_heap *heap) { gpr_free(heap->timers); } + +int grpc_timer_heap_add(grpc_timer_heap *heap, grpc_timer *timer) { + if (heap->timer_count == heap->timer_capacity) { + heap->timer_capacity = + GPR_MAX(heap->timer_capacity + 1, heap->timer_capacity * 3 / 2); + heap->timers = + gpr_realloc(heap->timers, heap->timer_capacity * sizeof(grpc_timer *)); + } + timer->heap_index = heap->timer_count; + adjust_upwards(heap->timers, heap->timer_count, timer); + heap->timer_count++; + return timer->heap_index == 0; +} + +void grpc_timer_heap_remove(grpc_timer_heap *heap, grpc_timer *timer) { + uint32_t i = timer->heap_index; + if (i == heap->timer_count - 1) { + heap->timer_count--; + maybe_shrink(heap); + return; + } + heap->timers[i] = heap->timers[heap->timer_count - 1]; + heap->timers[i]->heap_index = i; + heap->timer_count--; + maybe_shrink(heap); + note_changed_priority(heap, heap->timers[i]); +} + +int grpc_timer_heap_is_empty(grpc_timer_heap *heap) { + return heap->timer_count == 0; +} + +grpc_timer *grpc_timer_heap_top(grpc_timer_heap *heap) { + return heap->timers[0]; +} + +void grpc_timer_heap_pop(grpc_timer_heap *heap) { + grpc_timer_heap_remove(heap, grpc_timer_heap_top(heap)); +} diff --git a/src/core/lib/iomgr/timer_heap.h b/src/core/lib/iomgr/timer_heap.h new file mode 100644 index 0000000000..d5112cf0de --- /dev/null +++ b/src/core/lib/iomgr/timer_heap.h @@ -0,0 +1,57 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_TIMER_HEAP_H +#define GRPC_CORE_LIB_IOMGR_TIMER_HEAP_H + +#include "src/core/lib/iomgr/timer.h" + +typedef struct { + grpc_timer **timers; + uint32_t timer_count; + uint32_t timer_capacity; +} grpc_timer_heap; + +/* return 1 if the new timer is the first timer in the heap */ +int grpc_timer_heap_add(grpc_timer_heap *heap, grpc_timer *timer); + +void grpc_timer_heap_init(grpc_timer_heap *heap); +void grpc_timer_heap_destroy(grpc_timer_heap *heap); + +void grpc_timer_heap_remove(grpc_timer_heap *heap, grpc_timer *timer); +grpc_timer *grpc_timer_heap_top(grpc_timer_heap *heap); +void grpc_timer_heap_pop(grpc_timer_heap *heap); + +int grpc_timer_heap_is_empty(grpc_timer_heap *heap); + +#endif /* GRPC_CORE_LIB_IOMGR_TIMER_HEAP_H */ diff --git a/src/core/lib/iomgr/udp_server.c b/src/core/lib/iomgr/udp_server.c new file mode 100644 index 0000000000..3d8bcc9c81 --- /dev/null +++ b/src/core/lib/iomgr/udp_server.c @@ -0,0 +1,418 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* FIXME: "posix" files shouldn't be depending on _GNU_SOURCE */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <grpc/support/port_platform.h> + +#ifdef GRPC_NEED_UDP +#ifdef GPR_POSIX_SOCKET + +#include "src/core/lib/iomgr/udp_server.h" + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <grpc/grpc.h> +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> +#include <grpc/support/sync.h> +#include <grpc/support/time.h> +#include "src/core/lib/iomgr/fd_posix.h" +#include "src/core/lib/iomgr/pollset_posix.h" +#include "src/core/lib/iomgr/resolve_address.h" +#include "src/core/lib/iomgr/sockaddr_utils.h" +#include "src/core/lib/iomgr/socket_utils_posix.h" +#include "src/core/lib/iomgr/unix_sockets_posix.h" +#include "src/core/lib/support/string.h" + +#define INIT_PORT_CAP 2 + +/* one listening port */ +typedef struct { + int fd; + grpc_fd *emfd; + grpc_udp_server *server; + union { + uint8_t untyped[GRPC_MAX_SOCKADDR_SIZE]; + struct sockaddr sockaddr; + } addr; + size_t addr_len; + grpc_closure read_closure; + grpc_closure destroyed_closure; + grpc_udp_server_read_cb read_cb; +} server_port; + +/* the overall server */ +struct grpc_udp_server { + gpr_mu mu; + gpr_cv cv; + + /* active port count: how many ports are actually still listening */ + size_t active_ports; + /* destroyed port count: how many ports are completely destroyed */ + size_t destroyed_ports; + + /* is this server shutting down? (boolean) */ + int shutdown; + + /* all listening ports */ + server_port *ports; + size_t nports; + size_t port_capacity; + + /* shutdown callback */ + grpc_closure *shutdown_complete; + + /* all pollsets interested in new connections */ + grpc_pollset **pollsets; + /* number of pollsets in the pollsets array */ + size_t pollset_count; + /* The parent grpc server */ + grpc_server *grpc_server; +}; + +grpc_udp_server *grpc_udp_server_create(void) { + grpc_udp_server *s = gpr_malloc(sizeof(grpc_udp_server)); + gpr_mu_init(&s->mu); + gpr_cv_init(&s->cv); + s->active_ports = 0; + s->destroyed_ports = 0; + s->shutdown = 0; + s->ports = gpr_malloc(sizeof(server_port) * INIT_PORT_CAP); + s->nports = 0; + s->port_capacity = INIT_PORT_CAP; + + return s; +} + +static void finish_shutdown(grpc_exec_ctx *exec_ctx, grpc_udp_server *s) { + grpc_exec_ctx_enqueue(exec_ctx, s->shutdown_complete, 1, NULL); + + gpr_mu_destroy(&s->mu); + gpr_cv_destroy(&s->cv); + + gpr_free(s->ports); + gpr_free(s); +} + +static void destroyed_port(grpc_exec_ctx *exec_ctx, void *server, + bool success) { + grpc_udp_server *s = server; + gpr_mu_lock(&s->mu); + s->destroyed_ports++; + if (s->destroyed_ports == s->nports) { + gpr_mu_unlock(&s->mu); + finish_shutdown(exec_ctx, s); + } else { + gpr_mu_unlock(&s->mu); + } +} + +/* called when all listening endpoints have been shutdown, so no further + events will be received on them - at this point it's safe to destroy + things */ +static void deactivated_all_ports(grpc_exec_ctx *exec_ctx, grpc_udp_server *s) { + size_t i; + + /* delete ALL the things */ + gpr_mu_lock(&s->mu); + + if (!s->shutdown) { + gpr_mu_unlock(&s->mu); + return; + } + + if (s->nports) { + for (i = 0; i < s->nports; i++) { + server_port *sp = &s->ports[i]; + grpc_unlink_if_unix_domain_socket(&sp->addr.sockaddr); + sp->destroyed_closure.cb = destroyed_port; + sp->destroyed_closure.cb_arg = s; + grpc_fd_orphan(exec_ctx, sp->emfd, &sp->destroyed_closure, NULL, + "udp_listener_shutdown"); + } + gpr_mu_unlock(&s->mu); + } else { + gpr_mu_unlock(&s->mu); + finish_shutdown(exec_ctx, s); + } +} + +void grpc_udp_server_destroy(grpc_exec_ctx *exec_ctx, grpc_udp_server *s, + grpc_closure *on_done) { + size_t i; + gpr_mu_lock(&s->mu); + + GPR_ASSERT(!s->shutdown); + s->shutdown = 1; + + s->shutdown_complete = on_done; + + /* shutdown all fd's */ + if (s->active_ports) { + for (i = 0; i < s->nports; i++) { + grpc_fd_shutdown(exec_ctx, s->ports[i].emfd); + } + gpr_mu_unlock(&s->mu); + } else { + gpr_mu_unlock(&s->mu); + deactivated_all_ports(exec_ctx, s); + } +} + +/* Prepare a recently-created socket for listening. */ +static int prepare_socket(int fd, const struct sockaddr *addr, + size_t addr_len) { + struct sockaddr_storage sockname_temp; + socklen_t sockname_len; + int get_local_ip; + int rc; + + if (fd < 0) { + goto error; + } + + if (!grpc_set_socket_nonblocking(fd, 1) || !grpc_set_socket_cloexec(fd, 1)) { + gpr_log(GPR_ERROR, "Unable to configure socket %d: %s", fd, + strerror(errno)); + } + + get_local_ip = 1; + rc = setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &get_local_ip, + sizeof(get_local_ip)); + if (rc == 0 && addr->sa_family == AF_INET6) { +#if !defined(__APPLE__) + rc = setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &get_local_ip, + sizeof(get_local_ip)); +#endif + } + + GPR_ASSERT(addr_len < ~(socklen_t)0); + if (bind(fd, addr, (socklen_t)addr_len) < 0) { + char *addr_str; + grpc_sockaddr_to_string(&addr_str, addr, 0); + gpr_log(GPR_ERROR, "bind addr=%s: %s", addr_str, strerror(errno)); + gpr_free(addr_str); + goto error; + } + + sockname_len = sizeof(sockname_temp); + if (getsockname(fd, (struct sockaddr *)&sockname_temp, &sockname_len) < 0) { + goto error; + } + + return grpc_sockaddr_get_port((struct sockaddr *)&sockname_temp); + +error: + if (fd >= 0) { + close(fd); + } + return -1; +} + +/* event manager callback when reads are ready */ +static void on_read(grpc_exec_ctx *exec_ctx, void *arg, bool success) { + server_port *sp = arg; + + if (!success) { + gpr_mu_lock(&sp->server->mu); + if (0 == --sp->server->active_ports) { + gpr_mu_unlock(&sp->server->mu); + deactivated_all_ports(exec_ctx, sp->server); + } else { + gpr_mu_unlock(&sp->server->mu); + } + return; + } + + /* Tell the registered callback that data is available to read. */ + GPR_ASSERT(sp->read_cb); + sp->read_cb(exec_ctx, sp->emfd, sp->server->grpc_server); + + /* Re-arm the notification event so we get another chance to read. */ + grpc_fd_notify_on_read(exec_ctx, sp->emfd, &sp->read_closure); +} + +static int add_socket_to_server(grpc_udp_server *s, int fd, + const struct sockaddr *addr, size_t addr_len, + grpc_udp_server_read_cb read_cb) { + server_port *sp; + int port; + char *addr_str; + char *name; + + port = prepare_socket(fd, addr, addr_len); + if (port >= 0) { + grpc_sockaddr_to_string(&addr_str, (struct sockaddr *)&addr, 1); + gpr_asprintf(&name, "udp-server-listener:%s", addr_str); + gpr_free(addr_str); + gpr_mu_lock(&s->mu); + /* append it to the list under a lock */ + if (s->nports == s->port_capacity) { + s->port_capacity *= 2; + s->ports = gpr_realloc(s->ports, sizeof(server_port) * s->port_capacity); + } + sp = &s->ports[s->nports++]; + sp->server = s; + sp->fd = fd; + sp->emfd = grpc_fd_create(fd, name); + memcpy(sp->addr.untyped, addr, addr_len); + sp->addr_len = addr_len; + sp->read_cb = read_cb; + GPR_ASSERT(sp->emfd); + gpr_mu_unlock(&s->mu); + gpr_free(name); + } + + return port; +} + +int grpc_udp_server_add_port(grpc_udp_server *s, const void *addr, + size_t addr_len, grpc_udp_server_read_cb read_cb) { + int allocated_port1 = -1; + int allocated_port2 = -1; + unsigned i; + int fd; + grpc_dualstack_mode dsmode; + struct sockaddr_in6 addr6_v4mapped; + struct sockaddr_in wild4; + struct sockaddr_in6 wild6; + struct sockaddr_in addr4_copy; + struct sockaddr *allocated_addr = NULL; + struct sockaddr_storage sockname_temp; + socklen_t sockname_len; + int port; + + grpc_unlink_if_unix_domain_socket((struct sockaddr *)addr); + + /* Check if this is a wildcard port, and if so, try to keep the port the same + as some previously created listener. */ + if (grpc_sockaddr_get_port(addr) == 0) { + for (i = 0; i < s->nports; i++) { + sockname_len = sizeof(sockname_temp); + if (0 == getsockname(s->ports[i].fd, (struct sockaddr *)&sockname_temp, + &sockname_len)) { + port = grpc_sockaddr_get_port((struct sockaddr *)&sockname_temp); + if (port > 0) { + allocated_addr = malloc(addr_len); + memcpy(allocated_addr, addr, addr_len); + grpc_sockaddr_set_port(allocated_addr, port); + addr = allocated_addr; + break; + } + } + } + } + + if (grpc_sockaddr_to_v4mapped(addr, &addr6_v4mapped)) { + addr = (const struct sockaddr *)&addr6_v4mapped; + addr_len = sizeof(addr6_v4mapped); + } + + /* Treat :: or 0.0.0.0 as a family-agnostic wildcard. */ + if (grpc_sockaddr_is_wildcard(addr, &port)) { + grpc_sockaddr_make_wildcards(port, &wild4, &wild6); + + /* Try listening on IPv6 first. */ + addr = (struct sockaddr *)&wild6; + addr_len = sizeof(wild6); + fd = grpc_create_dualstack_socket(addr, SOCK_DGRAM, IPPROTO_UDP, &dsmode); + allocated_port1 = add_socket_to_server(s, fd, addr, addr_len, read_cb); + if (fd >= 0 && dsmode == GRPC_DSMODE_DUALSTACK) { + goto done; + } + + /* If we didn't get a dualstack socket, also listen on 0.0.0.0. */ + if (port == 0 && allocated_port1 > 0) { + grpc_sockaddr_set_port((struct sockaddr *)&wild4, allocated_port1); + } + addr = (struct sockaddr *)&wild4; + addr_len = sizeof(wild4); + } + + fd = grpc_create_dualstack_socket(addr, SOCK_DGRAM, IPPROTO_UDP, &dsmode); + if (fd < 0) { + gpr_log(GPR_ERROR, "Unable to create socket: %s", strerror(errno)); + } + if (dsmode == GRPC_DSMODE_IPV4 && + grpc_sockaddr_is_v4mapped(addr, &addr4_copy)) { + addr = (struct sockaddr *)&addr4_copy; + addr_len = sizeof(addr4_copy); + } + allocated_port2 = add_socket_to_server(s, fd, addr, addr_len, read_cb); + +done: + gpr_free(allocated_addr); + return allocated_port1 >= 0 ? allocated_port1 : allocated_port2; +} + +int grpc_udp_server_get_fd(grpc_udp_server *s, unsigned port_index) { + return (port_index < s->nports) ? s->ports[port_index].fd : -1; +} + +void grpc_udp_server_start(grpc_exec_ctx *exec_ctx, grpc_udp_server *s, + grpc_pollset **pollsets, size_t pollset_count, + grpc_server *server) { + size_t i, j; + gpr_mu_lock(&s->mu); + GPR_ASSERT(s->active_ports == 0); + s->pollsets = pollsets; + s->grpc_server = server; + for (i = 0; i < s->nports; i++) { + for (j = 0; j < pollset_count; j++) { + grpc_pollset_add_fd(exec_ctx, pollsets[j], s->ports[i].emfd); + } + s->ports[i].read_closure.cb = on_read; + s->ports[i].read_closure.cb_arg = &s->ports[i]; + grpc_fd_notify_on_read(exec_ctx, s->ports[i].emfd, + &s->ports[i].read_closure); + s->active_ports++; + } + gpr_mu_unlock(&s->mu); +} + +#endif +#endif diff --git a/src/core/lib/iomgr/udp_server.h b/src/core/lib/iomgr/udp_server.h new file mode 100644 index 0000000000..316845ad66 --- /dev/null +++ b/src/core/lib/iomgr/udp_server.h @@ -0,0 +1,77 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_UDP_SERVER_H +#define GRPC_CORE_LIB_IOMGR_UDP_SERVER_H + +#include "src/core/lib/iomgr/endpoint.h" +#include "src/core/lib/iomgr/fd_posix.h" + +/* Forward decl of struct grpc_server */ +/* This is not typedef'ed to avoid a typedef-redefinition error */ +struct grpc_server; + +/* Forward decl of grpc_udp_server */ +typedef struct grpc_udp_server grpc_udp_server; + +/* Called when data is available to read from the socket. */ +typedef void (*grpc_udp_server_read_cb)(grpc_exec_ctx *exec_ctx, grpc_fd *emfd, + struct grpc_server *server); + +/* Create a server, initially not bound to any ports */ +grpc_udp_server *grpc_udp_server_create(void); + +/* Start listening to bound ports */ +void grpc_udp_server_start(grpc_exec_ctx *exec_ctx, grpc_udp_server *udp_server, + grpc_pollset **pollsets, size_t pollset_count, + struct grpc_server *server); + +int grpc_udp_server_get_fd(grpc_udp_server *s, unsigned index); + +/* Add a port to the server, returning port number on success, or negative + on failure. + + The :: and 0.0.0.0 wildcard addresses are treated identically, accepting + both IPv4 and IPv6 connections, but :: is the preferred style. This usually + creates one socket, but possibly two on systems which support IPv6, + but not dualstack sockets. */ + +/* TODO(ctiller): deprecate this, and make grpc_udp_server_add_ports to handle + all of the multiple socket port matching logic in one place */ +int grpc_udp_server_add_port(grpc_udp_server *s, const void *addr, + size_t addr_len, grpc_udp_server_read_cb read_cb); + +void grpc_udp_server_destroy(grpc_exec_ctx *exec_ctx, grpc_udp_server *server, + grpc_closure *on_done); + +#endif /* GRPC_CORE_LIB_IOMGR_UDP_SERVER_H */ diff --git a/src/core/lib/iomgr/unix_sockets_posix.c b/src/core/lib/iomgr/unix_sockets_posix.c new file mode 100644 index 0000000000..42e44989e0 --- /dev/null +++ b/src/core/lib/iomgr/unix_sockets_posix.c @@ -0,0 +1,103 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/iomgr/unix_sockets_posix.h" + +#ifdef GPR_HAVE_UNIX_SOCKET + +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/un.h> + +#include <grpc/support/alloc.h> + +void grpc_create_socketpair_if_unix(int sv[2]) { + GPR_ASSERT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == 0); +} + +grpc_resolved_addresses *grpc_resolve_unix_domain_address(const char *name) { + struct sockaddr_un *un; + + grpc_resolved_addresses *addrs = gpr_malloc(sizeof(grpc_resolved_addresses)); + addrs->naddrs = 1; + addrs->addrs = gpr_malloc(sizeof(grpc_resolved_address)); + un = (struct sockaddr_un *)addrs->addrs->addr; + un->sun_family = AF_UNIX; + strcpy(un->sun_path, name); + addrs->addrs->len = strlen(un->sun_path) + sizeof(un->sun_family) + 1; + return addrs; +} + +int grpc_is_unix_socket(const struct sockaddr *addr) { + return addr->sa_family == AF_UNIX; +} + +void grpc_unlink_if_unix_domain_socket(const struct sockaddr *addr) { + if (addr->sa_family != AF_UNIX) { + return; + } + struct sockaddr_un *un = (struct sockaddr_un *)addr; + struct stat st; + + if (stat(un->sun_path, &st) == 0 && (st.st_mode & S_IFMT) == S_IFSOCK) { + unlink(un->sun_path); + } +} + +int grpc_parse_unix(grpc_uri *uri, struct sockaddr_storage *addr, size_t *len) { + struct sockaddr_un *un = (struct sockaddr_un *)addr; + + un->sun_family = AF_UNIX; + strcpy(un->sun_path, uri->path); + *len = strlen(un->sun_path) + sizeof(un->sun_family) + 1; + + return 1; +} + +char *grpc_unix_get_default_authority(grpc_resolver_factory *factory, + grpc_uri *uri) { + return gpr_strdup("localhost"); +} + +char *grpc_sockaddr_to_uri_unix_if_possible(const struct sockaddr *addr) { + if (addr->sa_family != AF_UNIX) { + return NULL; + } + + char *result; + gpr_asprintf(&result, "unix:%s", ((struct sockaddr_un *)addr)->sun_path); + return result; +} + +#endif diff --git a/src/core/lib/iomgr/unix_sockets_posix.h b/src/core/lib/iomgr/unix_sockets_posix.h new file mode 100644 index 0000000000..752cab85a5 --- /dev/null +++ b/src/core/lib/iomgr/unix_sockets_posix.h @@ -0,0 +1,61 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_UNIX_SOCKETS_POSIX_H +#define GRPC_CORE_LIB_IOMGR_UNIX_SOCKETS_POSIX_H + +#include <grpc/support/port_platform.h> + +#include <grpc/support/string_util.h> + +#include "src/core/lib/client_config/resolver_factory.h" +#include "src/core/lib/client_config/uri_parser.h" +#include "src/core/lib/iomgr/resolve_address.h" +#include "src/core/lib/iomgr/sockaddr.h" + +void grpc_create_socketpair_if_unix(int sv[2]); + +grpc_resolved_addresses *grpc_resolve_unix_domain_address(const char *name); + +int grpc_is_unix_socket(const struct sockaddr *addr); + +void grpc_unlink_if_unix_domain_socket(const struct sockaddr *addr); + +int grpc_parse_unix(grpc_uri *uri, struct sockaddr_storage *addr, size_t *len); + +char *grpc_unix_get_default_authority(grpc_resolver_factory *factory, + grpc_uri *uri); + +char *grpc_sockaddr_to_uri_unix_if_possible(const struct sockaddr *addr); + +#endif /* GRPC_CORE_LIB_IOMGR_UNIX_SOCKETS_POSIX_H */ diff --git a/src/core/lib/iomgr/unix_sockets_posix_noop.c b/src/core/lib/iomgr/unix_sockets_posix_noop.c new file mode 100644 index 0000000000..06f6ee05e7 --- /dev/null +++ b/src/core/lib/iomgr/unix_sockets_posix_noop.c @@ -0,0 +1,61 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/iomgr/unix_sockets_posix.h" + +#ifndef GPR_HAVE_UNIX_SOCKET + +void grpc_create_socketpair_if_unix(int sv[2]) {} + +grpc_resolved_addresses *grpc_resolve_unix_domain_address(const char *name) { + return NULL; +} + +int grpc_is_unix_socket(const struct sockaddr *addr) { return false; } + +void grpc_unlink_if_unix_domain_socket(const struct sockaddr *addr) {} + +int grpc_parse_unix(grpc_uri *uri, struct sockaddr_storage *addr, size_t *len) { + return 0; +} + +char *grpc_unix_get_default_authority(grpc_resolver_factory *factory, + grpc_uri *uri) { + return NULL; +} + +char *grpc_sockaddr_to_uri_unix_if_possible(const struct sockaddr *addr) { + return NULL; +} + +#endif diff --git a/src/core/lib/iomgr/wakeup_fd_eventfd.c b/src/core/lib/iomgr/wakeup_fd_eventfd.c new file mode 100644 index 0000000000..41ded0ca4d --- /dev/null +++ b/src/core/lib/iomgr/wakeup_fd_eventfd.c @@ -0,0 +1,85 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_LINUX_EVENTFD + +#include <errno.h> +#include <sys/eventfd.h> +#include <unistd.h> + +#include <grpc/support/log.h> + +#include "src/core/lib/iomgr/wakeup_fd_posix.h" +#include "src/core/lib/profiling/timers.h" + +static void eventfd_create(grpc_wakeup_fd* fd_info) { + int efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); + /* TODO(klempner): Handle failure more gracefully */ + GPR_ASSERT(efd >= 0); + fd_info->read_fd = efd; + fd_info->write_fd = -1; +} + +static void eventfd_consume(grpc_wakeup_fd* fd_info) { + eventfd_t value; + int err; + do { + err = eventfd_read(fd_info->read_fd, &value); + } while (err < 0 && errno == EINTR); +} + +static void eventfd_wakeup(grpc_wakeup_fd* fd_info) { + int err; + GPR_TIMER_BEGIN("eventfd_wakeup", 0); + do { + err = eventfd_write(fd_info->read_fd, 1); + } while (err < 0 && errno == EINTR); + GPR_TIMER_END("eventfd_wakeup", 0); +} + +static void eventfd_destroy(grpc_wakeup_fd* fd_info) { + if (fd_info->read_fd != 0) close(fd_info->read_fd); +} + +static int eventfd_check_availability(void) { + /* TODO(klempner): Actually check if eventfd is available */ + return 1; +} + +const grpc_wakeup_fd_vtable grpc_specialized_wakeup_fd_vtable = { + eventfd_create, eventfd_consume, eventfd_wakeup, eventfd_destroy, + eventfd_check_availability}; + +#endif /* GPR_LINUX_EVENTFD */ diff --git a/src/core/lib/iomgr/wakeup_fd_nospecial.c b/src/core/lib/iomgr/wakeup_fd_nospecial.c new file mode 100644 index 0000000000..39defa65c6 --- /dev/null +++ b/src/core/lib/iomgr/wakeup_fd_nospecial.c @@ -0,0 +1,51 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* + * This is a dummy file to provide an invalid specialized_wakeup_fd_vtable on + * systems without anything better than pipe. + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_NO_SPECIAL_WAKEUP_FD + +#include <stddef.h> +#include "src/core/lib/iomgr/wakeup_fd_posix.h" + +static int check_availability_invalid(void) { return 0; } + +const grpc_wakeup_fd_vtable grpc_specialized_wakeup_fd_vtable = { + NULL, NULL, NULL, NULL, check_availability_invalid}; + +#endif /* GPR_POSIX_NO_SPECIAL_WAKEUP_FD */ diff --git a/src/core/lib/iomgr/wakeup_fd_pipe.c b/src/core/lib/iomgr/wakeup_fd_pipe.c new file mode 100644 index 0000000000..820919e4dd --- /dev/null +++ b/src/core/lib/iomgr/wakeup_fd_pipe.c @@ -0,0 +1,102 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_WAKEUP_FD + +#include "src/core/lib/iomgr/wakeup_fd_posix.h" + +#include <errno.h> +#include <string.h> +#include <unistd.h> + +#include <grpc/support/log.h> + +#include "src/core/lib/iomgr/socket_utils_posix.h" + +static void pipe_init(grpc_wakeup_fd* fd_info) { + int pipefd[2]; + /* TODO(klempner): Make this nonfatal */ + int r = pipe(pipefd); + if (0 != r) { + gpr_log(GPR_ERROR, "pipe creation failed (%d): %s", errno, strerror(errno)); + abort(); + } + GPR_ASSERT(grpc_set_socket_nonblocking(pipefd[0], 1)); + GPR_ASSERT(grpc_set_socket_nonblocking(pipefd[1], 1)); + fd_info->read_fd = pipefd[0]; + fd_info->write_fd = pipefd[1]; +} + +static void pipe_consume(grpc_wakeup_fd* fd_info) { + char buf[128]; + ssize_t r; + + for (;;) { + r = read(fd_info->read_fd, buf, sizeof(buf)); + if (r > 0) continue; + if (r == 0) return; + switch (errno) { + case EAGAIN: + return; + case EINTR: + continue; + default: + gpr_log(GPR_ERROR, "error reading pipe: %s", strerror(errno)); + return; + } + } +} + +static void pipe_wakeup(grpc_wakeup_fd* fd_info) { + char c = 0; + while (write(fd_info->write_fd, &c, 1) != 1 && errno == EINTR) + ; +} + +static void pipe_destroy(grpc_wakeup_fd* fd_info) { + if (fd_info->read_fd != 0) close(fd_info->read_fd); + if (fd_info->write_fd != 0) close(fd_info->write_fd); +} + +static int pipe_check_availability(void) { + /* Assume that pipes are always available. */ + return 1; +} + +const grpc_wakeup_fd_vtable grpc_pipe_wakeup_fd_vtable = { + pipe_init, pipe_consume, pipe_wakeup, pipe_destroy, + pipe_check_availability}; + +#endif /* GPR_POSIX_WAKUP_FD */ diff --git a/src/core/lib/iomgr/wakeup_fd_pipe.h b/src/core/lib/iomgr/wakeup_fd_pipe.h new file mode 100644 index 0000000000..bbdb1fc448 --- /dev/null +++ b/src/core/lib/iomgr/wakeup_fd_pipe.h @@ -0,0 +1,41 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_WAKEUP_FD_PIPE_H +#define GRPC_CORE_LIB_IOMGR_WAKEUP_FD_PIPE_H + +#include "src/core/lib/iomgr/wakeup_fd_posix.h" + +extern grpc_wakeup_fd_vtable grpc_pipe_wakeup_fd_vtable; + +#endif /* GRPC_CORE_LIB_IOMGR_WAKEUP_FD_PIPE_H */ diff --git a/src/core/lib/iomgr/wakeup_fd_posix.c b/src/core/lib/iomgr/wakeup_fd_posix.c new file mode 100644 index 0000000000..c4d174fb34 --- /dev/null +++ b/src/core/lib/iomgr/wakeup_fd_posix.c @@ -0,0 +1,72 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_WAKEUP_FD + +#include <stddef.h> +#include "src/core/lib/iomgr/wakeup_fd_pipe.h" +#include "src/core/lib/iomgr/wakeup_fd_posix.h" + +static const grpc_wakeup_fd_vtable *wakeup_fd_vtable = NULL; +int grpc_allow_specialized_wakeup_fd = 1; + +void grpc_wakeup_fd_global_init(void) { + if (grpc_allow_specialized_wakeup_fd && + grpc_specialized_wakeup_fd_vtable.check_availability()) { + wakeup_fd_vtable = &grpc_specialized_wakeup_fd_vtable; + } else { + wakeup_fd_vtable = &grpc_pipe_wakeup_fd_vtable; + } +} + +void grpc_wakeup_fd_global_destroy(void) { wakeup_fd_vtable = NULL; } + +void grpc_wakeup_fd_init(grpc_wakeup_fd *fd_info) { + wakeup_fd_vtable->init(fd_info); +} + +void grpc_wakeup_fd_consume_wakeup(grpc_wakeup_fd *fd_info) { + wakeup_fd_vtable->consume(fd_info); +} + +void grpc_wakeup_fd_wakeup(grpc_wakeup_fd *fd_info) { + wakeup_fd_vtable->wakeup(fd_info); +} + +void grpc_wakeup_fd_destroy(grpc_wakeup_fd *fd_info) { + wakeup_fd_vtable->destroy(fd_info); +} + +#endif /* GPR_POSIX_WAKEUP_FD */ diff --git a/src/core/lib/iomgr/wakeup_fd_posix.h b/src/core/lib/iomgr/wakeup_fd_posix.h new file mode 100644 index 0000000000..20988d5fd3 --- /dev/null +++ b/src/core/lib/iomgr/wakeup_fd_posix.h @@ -0,0 +1,101 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* + * wakeup_fd abstracts the concept of a file descriptor for the purpose of + * waking up a thread in select()/poll()/epoll_wait()/etc. + + * The poll() family of system calls provide a way for a thread to block until + * there is activity on one (or more) of a set of file descriptors. An + * application may wish to wake up this thread to do non file related work. The + * typical way to do this is to add a pipe to the set of file descriptors, then + * write to the pipe to wake up the thread in poll(). + * + * Linux has a lighter weight eventfd specifically designed for this purpose. + * wakeup_fd abstracts the difference between the two. + * + * Setup: + * 1. Before calling anything, call global_init() at least once. + * 1. Call grpc_wakeup_fd_create() to get a wakeup_fd. + * 2. Add the result of GRPC_WAKEUP_FD_FD to the set of monitored file + * descriptors for the poll() style API you are using. Monitor the file + * descriptor for readability. + * 3. To tear down, call grpc_wakeup_fd_destroy(). This closes the underlying + * file descriptor. + * + * Usage: + * 1. To wake up a polling thread, call grpc_wakeup_fd_wakeup() on a wakeup_fd + * it is monitoring. + * 2. If the polling thread was awakened by a wakeup_fd event, call + * grpc_wakeup_fd_consume_wakeup() on it. + */ +#ifndef GRPC_CORE_LIB_IOMGR_WAKEUP_FD_POSIX_H +#define GRPC_CORE_LIB_IOMGR_WAKEUP_FD_POSIX_H + +void grpc_wakeup_fd_global_init(void); +void grpc_wakeup_fd_global_destroy(void); + +/* Force using the fallback implementation. This is intended for testing + * purposes only.*/ +void grpc_wakeup_fd_global_init_force_fallback(void); + +typedef struct grpc_wakeup_fd grpc_wakeup_fd; + +typedef struct grpc_wakeup_fd_vtable { + void (*init)(grpc_wakeup_fd* fd_info); + void (*consume)(grpc_wakeup_fd* fd_info); + void (*wakeup)(grpc_wakeup_fd* fd_info); + void (*destroy)(grpc_wakeup_fd* fd_info); + /* Must be called before calling any other functions */ + int (*check_availability)(void); +} grpc_wakeup_fd_vtable; + +struct grpc_wakeup_fd { + int read_fd; + int write_fd; +}; + +extern int grpc_allow_specialized_wakeup_fd; + +#define GRPC_WAKEUP_FD_GET_READ_FD(fd_info) ((fd_info)->read_fd) + +void grpc_wakeup_fd_init(grpc_wakeup_fd* fd_info); +void grpc_wakeup_fd_consume_wakeup(grpc_wakeup_fd* fd_info); +void grpc_wakeup_fd_wakeup(grpc_wakeup_fd* fd_info); +void grpc_wakeup_fd_destroy(grpc_wakeup_fd* fd_info); + +/* Defined in some specialized implementation's .c file, or by + * wakeup_fd_nospecial.c if no such implementation exists. */ +extern const grpc_wakeup_fd_vtable grpc_specialized_wakeup_fd_vtable; + +#endif /* GRPC_CORE_LIB_IOMGR_WAKEUP_FD_POSIX_H */ diff --git a/src/core/lib/iomgr/workqueue.h b/src/core/lib/iomgr/workqueue.h new file mode 100644 index 0000000000..9c420c57de --- /dev/null +++ b/src/core/lib/iomgr/workqueue.h @@ -0,0 +1,83 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_WORKQUEUE_H +#define GRPC_CORE_LIB_IOMGR_WORKQUEUE_H + +#include "src/core/lib/iomgr/closure.h" +#include "src/core/lib/iomgr/exec_ctx.h" +#include "src/core/lib/iomgr/iomgr.h" +#include "src/core/lib/iomgr/pollset.h" + +#ifdef GPR_POSIX_SOCKET +#include "src/core/lib/iomgr/workqueue_posix.h" +#endif + +#ifdef GPR_WIN32 +#include "src/core/lib/iomgr/workqueue_windows.h" +#endif + +/* grpc_workqueue is forward declared in exec_ctx.h */ + +/** Create a work queue */ +grpc_workqueue *grpc_workqueue_create(grpc_exec_ctx *exec_ctx); + +void grpc_workqueue_flush(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue); + +#define GRPC_WORKQUEUE_REFCOUNT_DEBUG +#ifdef GRPC_WORKQUEUE_REFCOUNT_DEBUG +#define GRPC_WORKQUEUE_REF(p, r) \ + grpc_workqueue_ref((p), __FILE__, __LINE__, (r)) +#define GRPC_WORKQUEUE_UNREF(cl, p, r) \ + grpc_workqueue_unref((cl), (p), __FILE__, __LINE__, (r)) +void grpc_workqueue_ref(grpc_workqueue *workqueue, const char *file, int line, + const char *reason); +void grpc_workqueue_unref(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue, + const char *file, int line, const char *reason); +#else +#define GRPC_WORKQUEUE_REF(p, r) grpc_workqueue_ref((p)) +#define GRPC_WORKQUEUE_UNREF(cl, p, r) grpc_workqueue_unref((cl), (p)) +void grpc_workqueue_ref(grpc_workqueue *workqueue); +void grpc_workqueue_unref(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue); +#endif + +/** Bind this workqueue to a pollset */ +void grpc_workqueue_add_to_pollset(grpc_exec_ctx *exec_ctx, + grpc_workqueue *workqueue, + grpc_pollset *pollset); + +/** Add a work item to a workqueue */ +void grpc_workqueue_push(grpc_workqueue *workqueue, grpc_closure *closure, + int success); + +#endif /* GRPC_CORE_LIB_IOMGR_WORKQUEUE_H */ diff --git a/src/core/lib/iomgr/workqueue_posix.c b/src/core/lib/iomgr/workqueue_posix.c new file mode 100644 index 0000000000..76830ef12d --- /dev/null +++ b/src/core/lib/iomgr/workqueue_posix.c @@ -0,0 +1,144 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_SOCKET + +#include "src/core/lib/iomgr/workqueue.h" + +#include <stdio.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/useful.h> + +#include "src/core/lib/iomgr/fd_posix.h" +#include "src/core/lib/iomgr/pollset_posix.h" + +static void on_readable(grpc_exec_ctx *exec_ctx, void *arg, bool success); + +grpc_workqueue *grpc_workqueue_create(grpc_exec_ctx *exec_ctx) { + char name[32]; + grpc_workqueue *workqueue = gpr_malloc(sizeof(grpc_workqueue)); + gpr_ref_init(&workqueue->refs, 1); + gpr_mu_init(&workqueue->mu); + workqueue->closure_list.head = workqueue->closure_list.tail = NULL; + grpc_wakeup_fd_init(&workqueue->wakeup_fd); + sprintf(name, "workqueue:%p", (void *)workqueue); + workqueue->wakeup_read_fd = + grpc_fd_create(GRPC_WAKEUP_FD_GET_READ_FD(&workqueue->wakeup_fd), name); + grpc_closure_init(&workqueue->read_closure, on_readable, workqueue); + grpc_fd_notify_on_read(exec_ctx, workqueue->wakeup_read_fd, + &workqueue->read_closure); + return workqueue; +} + +static void workqueue_destroy(grpc_exec_ctx *exec_ctx, + grpc_workqueue *workqueue) { + GPR_ASSERT(grpc_closure_list_empty(workqueue->closure_list)); + grpc_fd_shutdown(exec_ctx, workqueue->wakeup_read_fd); +} + +#ifdef GRPC_WORKQUEUE_REFCOUNT_DEBUG +void grpc_workqueue_ref(grpc_workqueue *workqueue, const char *file, int line, + const char *reason) { + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, "WORKQUEUE:%p ref %d -> %d %s", + workqueue, (int)workqueue->refs.count, (int)workqueue->refs.count + 1, + reason); +#else +void grpc_workqueue_ref(grpc_workqueue *workqueue) { +#endif + gpr_ref(&workqueue->refs); +} + +#ifdef GRPC_WORKQUEUE_REFCOUNT_DEBUG +void grpc_workqueue_unref(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue, + const char *file, int line, const char *reason) { + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, "WORKQUEUE:%p unref %d -> %d %s", + workqueue, (int)workqueue->refs.count, (int)workqueue->refs.count - 1, + reason); +#else +void grpc_workqueue_unref(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue) { +#endif + if (gpr_unref(&workqueue->refs)) { + workqueue_destroy(exec_ctx, workqueue); + } +} + +void grpc_workqueue_add_to_pollset(grpc_exec_ctx *exec_ctx, + grpc_workqueue *workqueue, + grpc_pollset *pollset) { + grpc_pollset_add_fd(exec_ctx, pollset, workqueue->wakeup_read_fd); +} + +void grpc_workqueue_flush(grpc_exec_ctx *exec_ctx, grpc_workqueue *workqueue) { + gpr_mu_lock(&workqueue->mu); + if (grpc_closure_list_empty(workqueue->closure_list)) { + grpc_wakeup_fd_wakeup(&workqueue->wakeup_fd); + } + grpc_exec_ctx_enqueue_list(exec_ctx, &workqueue->closure_list, NULL); + gpr_mu_unlock(&workqueue->mu); +} + +static void on_readable(grpc_exec_ctx *exec_ctx, void *arg, bool success) { + grpc_workqueue *workqueue = arg; + + if (!success) { + gpr_mu_destroy(&workqueue->mu); + /* HACK: let wakeup_fd code know that we stole the fd */ + workqueue->wakeup_fd.read_fd = 0; + grpc_wakeup_fd_destroy(&workqueue->wakeup_fd); + grpc_fd_orphan(exec_ctx, workqueue->wakeup_read_fd, NULL, NULL, "destroy"); + gpr_free(workqueue); + } else { + gpr_mu_lock(&workqueue->mu); + grpc_exec_ctx_enqueue_list(exec_ctx, &workqueue->closure_list, NULL); + grpc_wakeup_fd_consume_wakeup(&workqueue->wakeup_fd); + gpr_mu_unlock(&workqueue->mu); + grpc_fd_notify_on_read(exec_ctx, workqueue->wakeup_read_fd, + &workqueue->read_closure); + } +} + +void grpc_workqueue_push(grpc_workqueue *workqueue, grpc_closure *closure, + int success) { + gpr_mu_lock(&workqueue->mu); + if (grpc_closure_list_empty(workqueue->closure_list)) { + grpc_wakeup_fd_wakeup(&workqueue->wakeup_fd); + } + grpc_closure_list_add(&workqueue->closure_list, closure, success); + gpr_mu_unlock(&workqueue->mu); +} + +#endif /* GPR_POSIX_SOCKET */ diff --git a/src/core/lib/iomgr/workqueue_posix.h b/src/core/lib/iomgr/workqueue_posix.h new file mode 100644 index 0000000000..956de8fb27 --- /dev/null +++ b/src/core/lib/iomgr/workqueue_posix.h @@ -0,0 +1,53 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_WORKQUEUE_POSIX_H +#define GRPC_CORE_LIB_IOMGR_WORKQUEUE_POSIX_H + +#include "src/core/lib/iomgr/wakeup_fd_posix.h" + +struct grpc_fd; + +struct grpc_workqueue { + gpr_refcount refs; + + gpr_mu mu; + grpc_closure_list closure_list; + + grpc_wakeup_fd wakeup_fd; + struct grpc_fd *wakeup_read_fd; + + grpc_closure read_closure; +}; + +#endif /* GRPC_CORE_LIB_IOMGR_WORKQUEUE_POSIX_H */ diff --git a/src/core/lib/iomgr/workqueue_windows.c b/src/core/lib/iomgr/workqueue_windows.c new file mode 100644 index 0000000000..6697f93498 --- /dev/null +++ b/src/core/lib/iomgr/workqueue_windows.c @@ -0,0 +1,40 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WIN32 + +#include "src/core/lib/iomgr/workqueue.h" + +#endif /* GPR_WIN32 */ diff --git a/src/core/lib/iomgr/workqueue_windows.h b/src/core/lib/iomgr/workqueue_windows.h new file mode 100644 index 0000000000..8e6980b6d9 --- /dev/null +++ b/src/core/lib/iomgr/workqueue_windows.h @@ -0,0 +1,37 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_IOMGR_WORKQUEUE_WINDOWS_H +#define GRPC_CORE_LIB_IOMGR_WORKQUEUE_WINDOWS_H + +#endif /* GRPC_CORE_LIB_IOMGR_WORKQUEUE_WINDOWS_H */ diff --git a/src/core/lib/json/json.c b/src/core/lib/json/json.c new file mode 100644 index 0000000000..9793045d91 --- /dev/null +++ b/src/core/lib/json/json.c @@ -0,0 +1,64 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <string.h> + +#include <grpc/support/alloc.h> + +#include "src/core/lib/json/json.h" + +grpc_json *grpc_json_create(grpc_json_type type) { + grpc_json *json = gpr_malloc(sizeof(*json)); + memset(json, 0, sizeof(*json)); + json->type = type; + + return json; +} + +void grpc_json_destroy(grpc_json *json) { + while (json->child) { + grpc_json_destroy(json->child); + } + + if (json->next) { + json->next->prev = json->prev; + } + + if (json->prev) { + json->prev->next = json->next; + } else if (json->parent) { + json->parent->child = json->next; + } + + gpr_free(json); +} diff --git a/src/core/lib/json/json.h b/src/core/lib/json/json.h new file mode 100644 index 0000000000..41d87dd5ce --- /dev/null +++ b/src/core/lib/json/json.h @@ -0,0 +1,88 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_JSON_JSON_H +#define GRPC_CORE_LIB_JSON_JSON_H + +#include <stdlib.h> + +#include "src/core/lib/json/json_common.h" + +/* A tree-like structure to hold json values. The key and value pointers + * are not owned by it. + */ +typedef struct grpc_json { + struct grpc_json *next; + struct grpc_json *prev; + struct grpc_json *child; + struct grpc_json *parent; + + grpc_json_type type; + const char *key; + const char *value; +} grpc_json; + +/* The next two functions are going to parse the input string, and + * modify it in the process, in order to use its space to store + * all of the keys and values for the returned object tree. + * + * They assume UTF-8 input stream, and will output UTF-8 encoded + * strings in the tree. The input stream's UTF-8 isn't validated, + * as in, what you input is what you get as an output. + * + * All the keys and values in the grpc_json objects will be strings + * pointing at your input buffer. + * + * Delete the allocated tree afterward using grpc_json_destroy(). + */ +grpc_json *grpc_json_parse_string_with_len(char *input, size_t size); +grpc_json *grpc_json_parse_string(char *input); + +/* This function will create a new string using gpr_realloc, and will + * deserialize the grpc_json tree into it. It'll be zero-terminated, + * but will be allocated in chunks of 256 bytes. + * + * The indent parameter controls the way the output is formatted. + * If indent is 0, then newlines will be suppressed as well, and the + * output will be condensed at its maximum. + */ +char *grpc_json_dump_to_string(grpc_json *json, int indent); + +/* Use these to create or delete a grpc_json object. + * Deletion is recursive. We will not attempt to free any of the strings + * in any of the objects of that tree. + */ +grpc_json *grpc_json_create(grpc_json_type type); +void grpc_json_destroy(grpc_json *json); + +#endif /* GRPC_CORE_LIB_JSON_JSON_H */ diff --git a/src/core/lib/json/json_common.h b/src/core/lib/json/json_common.h new file mode 100644 index 0000000000..ce980040f8 --- /dev/null +++ b/src/core/lib/json/json_common.h @@ -0,0 +1,49 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_JSON_JSON_COMMON_H +#define GRPC_CORE_LIB_JSON_JSON_COMMON_H + +/* The various json types. */ +typedef enum { + GRPC_JSON_OBJECT, + GRPC_JSON_ARRAY, + GRPC_JSON_STRING, + GRPC_JSON_NUMBER, + GRPC_JSON_TRUE, + GRPC_JSON_FALSE, + GRPC_JSON_NULL, + GRPC_JSON_TOP_LEVEL +} grpc_json_type; + +#endif /* GRPC_CORE_LIB_JSON_JSON_COMMON_H */ diff --git a/src/core/lib/json/json_reader.c b/src/core/lib/json/json_reader.c new file mode 100644 index 0000000000..0807f029ce --- /dev/null +++ b/src/core/lib/json/json_reader.c @@ -0,0 +1,659 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <string.h> + +#include <grpc/support/port_platform.h> + +#include <grpc/support/log.h> + +#include "src/core/lib/json/json_reader.h" + +static void json_reader_string_clear(grpc_json_reader *reader) { + reader->vtable->string_clear(reader->userdata); +} + +static void json_reader_string_add_char(grpc_json_reader *reader, uint32_t c) { + reader->vtable->string_add_char(reader->userdata, c); +} + +static void json_reader_string_add_utf32(grpc_json_reader *reader, + uint32_t utf32) { + reader->vtable->string_add_utf32(reader->userdata, utf32); +} + +static uint32_t grpc_json_reader_read_char(grpc_json_reader *reader) { + return reader->vtable->read_char(reader->userdata); +} + +static void json_reader_container_begins(grpc_json_reader *reader, + grpc_json_type type) { + reader->vtable->container_begins(reader->userdata, type); +} + +static grpc_json_type grpc_json_reader_container_ends( + grpc_json_reader *reader) { + return reader->vtable->container_ends(reader->userdata); +} + +static void json_reader_set_key(grpc_json_reader *reader) { + reader->vtable->set_key(reader->userdata); +} + +static void json_reader_set_string(grpc_json_reader *reader) { + reader->vtable->set_string(reader->userdata); +} + +static int json_reader_set_number(grpc_json_reader *reader) { + return reader->vtable->set_number(reader->userdata); +} + +static void json_reader_set_true(grpc_json_reader *reader) { + reader->vtable->set_true(reader->userdata); +} + +static void json_reader_set_false(grpc_json_reader *reader) { + reader->vtable->set_false(reader->userdata); +} + +static void json_reader_set_null(grpc_json_reader *reader) { + reader->vtable->set_null(reader->userdata); +} + +/* Call this function to initialize the reader structure. */ +void grpc_json_reader_init(grpc_json_reader *reader, + grpc_json_reader_vtable *vtable, void *userdata) { + memset(reader, 0, sizeof(*reader)); + reader->vtable = vtable; + reader->userdata = userdata; + json_reader_string_clear(reader); + reader->state = GRPC_JSON_STATE_VALUE_BEGIN; +} + +int grpc_json_reader_is_complete(grpc_json_reader *reader) { + return ((reader->depth == 0) && + ((reader->state == GRPC_JSON_STATE_END) || + (reader->state == GRPC_JSON_STATE_VALUE_END))); +} + +grpc_json_reader_status grpc_json_reader_run(grpc_json_reader *reader) { + uint32_t c, success; + + /* This state-machine is a strict implementation of ECMA-404 */ + for (;;) { + c = grpc_json_reader_read_char(reader); + switch (c) { + /* Let's process the error cases first. */ + case GRPC_JSON_READ_CHAR_ERROR: + return GRPC_JSON_READ_ERROR; + + case GRPC_JSON_READ_CHAR_EAGAIN: + return GRPC_JSON_EAGAIN; + + case GRPC_JSON_READ_CHAR_EOF: + if (grpc_json_reader_is_complete(reader)) { + return GRPC_JSON_DONE; + } else { + return GRPC_JSON_PARSE_ERROR; + } + break; + + /* Processing whitespaces. */ + case ' ': + case '\t': + case '\n': + case '\r': + switch (reader->state) { + case GRPC_JSON_STATE_OBJECT_KEY_BEGIN: + case GRPC_JSON_STATE_OBJECT_KEY_END: + case GRPC_JSON_STATE_VALUE_BEGIN: + case GRPC_JSON_STATE_VALUE_END: + case GRPC_JSON_STATE_END: + break; + + case GRPC_JSON_STATE_OBJECT_KEY_STRING: + case GRPC_JSON_STATE_VALUE_STRING: + if (c != ' ') return GRPC_JSON_PARSE_ERROR; + if (reader->unicode_high_surrogate != 0) + return GRPC_JSON_PARSE_ERROR; + json_reader_string_add_char(reader, c); + break; + + case GRPC_JSON_STATE_VALUE_NUMBER: + case GRPC_JSON_STATE_VALUE_NUMBER_WITH_DECIMAL: + case GRPC_JSON_STATE_VALUE_NUMBER_ZERO: + case GRPC_JSON_STATE_VALUE_NUMBER_EPM: + success = (uint32_t)json_reader_set_number(reader); + if (!success) return GRPC_JSON_PARSE_ERROR; + json_reader_string_clear(reader); + reader->state = GRPC_JSON_STATE_VALUE_END; + break; + + default: + return GRPC_JSON_PARSE_ERROR; + } + break; + + /* Value, object or array terminations. */ + case ',': + case '}': + case ']': + switch (reader->state) { + case GRPC_JSON_STATE_OBJECT_KEY_STRING: + case GRPC_JSON_STATE_VALUE_STRING: + if (reader->unicode_high_surrogate != 0) + return GRPC_JSON_PARSE_ERROR; + json_reader_string_add_char(reader, c); + break; + + case GRPC_JSON_STATE_VALUE_NUMBER: + case GRPC_JSON_STATE_VALUE_NUMBER_WITH_DECIMAL: + case GRPC_JSON_STATE_VALUE_NUMBER_ZERO: + case GRPC_JSON_STATE_VALUE_NUMBER_EPM: + success = (uint32_t)json_reader_set_number(reader); + if (!success) return GRPC_JSON_PARSE_ERROR; + json_reader_string_clear(reader); + reader->state = GRPC_JSON_STATE_VALUE_END; + /* The missing break here is intentional. */ + + case GRPC_JSON_STATE_VALUE_END: + case GRPC_JSON_STATE_OBJECT_KEY_BEGIN: + case GRPC_JSON_STATE_VALUE_BEGIN: + if (c == ',') { + if (reader->state != GRPC_JSON_STATE_VALUE_END) { + return GRPC_JSON_PARSE_ERROR; + } + if (reader->in_object) { + reader->state = GRPC_JSON_STATE_OBJECT_KEY_BEGIN; + } else { + reader->state = GRPC_JSON_STATE_VALUE_BEGIN; + } + } else { + if (reader->depth-- == 0) return GRPC_JSON_PARSE_ERROR; + if ((c == '}') && !reader->in_object) { + return GRPC_JSON_PARSE_ERROR; + } + if ((c == '}') && + (reader->state == GRPC_JSON_STATE_OBJECT_KEY_BEGIN) && + !reader->container_just_begun) { + return GRPC_JSON_PARSE_ERROR; + } + if ((c == ']') && !reader->in_array) return GRPC_JSON_PARSE_ERROR; + if ((c == ']') && + (reader->state == GRPC_JSON_STATE_VALUE_BEGIN) && + !reader->container_just_begun) { + return GRPC_JSON_PARSE_ERROR; + } + reader->state = GRPC_JSON_STATE_VALUE_END; + switch (grpc_json_reader_container_ends(reader)) { + case GRPC_JSON_OBJECT: + reader->in_object = 1; + reader->in_array = 0; + break; + case GRPC_JSON_ARRAY: + reader->in_object = 0; + reader->in_array = 1; + break; + case GRPC_JSON_TOP_LEVEL: + GPR_ASSERT(reader->depth == 0); + reader->in_object = 0; + reader->in_array = 0; + reader->state = GRPC_JSON_STATE_END; + break; + default: + GPR_UNREACHABLE_CODE(return GRPC_JSON_INTERNAL_ERROR); + } + } + break; + + default: + return GRPC_JSON_PARSE_ERROR; + } + break; + + /* In-string escaping. */ + case '\\': + switch (reader->state) { + case GRPC_JSON_STATE_OBJECT_KEY_STRING: + reader->escaped_string_was_key = 1; + reader->state = GRPC_JSON_STATE_STRING_ESCAPE; + break; + + case GRPC_JSON_STATE_VALUE_STRING: + reader->escaped_string_was_key = 0; + reader->state = GRPC_JSON_STATE_STRING_ESCAPE; + break; + + /* This is the \\ case. */ + case GRPC_JSON_STATE_STRING_ESCAPE: + if (reader->unicode_high_surrogate != 0) + return GRPC_JSON_PARSE_ERROR; + json_reader_string_add_char(reader, '\\'); + if (reader->escaped_string_was_key) { + reader->state = GRPC_JSON_STATE_OBJECT_KEY_STRING; + } else { + reader->state = GRPC_JSON_STATE_VALUE_STRING; + } + break; + + default: + return GRPC_JSON_PARSE_ERROR; + } + break; + + default: + reader->container_just_begun = 0; + switch (reader->state) { + case GRPC_JSON_STATE_OBJECT_KEY_BEGIN: + if (c != '"') return GRPC_JSON_PARSE_ERROR; + reader->state = GRPC_JSON_STATE_OBJECT_KEY_STRING; + break; + + case GRPC_JSON_STATE_OBJECT_KEY_STRING: + GPR_ASSERT(reader->unicode_high_surrogate == 0); + if (c == '"') { + reader->state = GRPC_JSON_STATE_OBJECT_KEY_END; + json_reader_set_key(reader); + json_reader_string_clear(reader); + } else { + if (c <= 0x001f) return GRPC_JSON_PARSE_ERROR; + json_reader_string_add_char(reader, c); + } + break; + + case GRPC_JSON_STATE_VALUE_STRING: + if (reader->unicode_high_surrogate != 0) + return GRPC_JSON_PARSE_ERROR; + if (c == '"') { + reader->state = GRPC_JSON_STATE_VALUE_END; + json_reader_set_string(reader); + json_reader_string_clear(reader); + } else { + if (c < 32) return GRPC_JSON_PARSE_ERROR; + json_reader_string_add_char(reader, c); + } + break; + + case GRPC_JSON_STATE_OBJECT_KEY_END: + if (c != ':') return GRPC_JSON_PARSE_ERROR; + reader->state = GRPC_JSON_STATE_VALUE_BEGIN; + break; + + case GRPC_JSON_STATE_VALUE_BEGIN: + switch (c) { + case 't': + reader->state = GRPC_JSON_STATE_VALUE_TRUE_R; + break; + + case 'f': + reader->state = GRPC_JSON_STATE_VALUE_FALSE_A; + break; + + case 'n': + reader->state = GRPC_JSON_STATE_VALUE_NULL_U; + break; + + case '"': + reader->state = GRPC_JSON_STATE_VALUE_STRING; + break; + + case '0': + json_reader_string_add_char(reader, c); + reader->state = GRPC_JSON_STATE_VALUE_NUMBER_ZERO; + break; + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + json_reader_string_add_char(reader, c); + reader->state = GRPC_JSON_STATE_VALUE_NUMBER; + break; + + case '{': + reader->container_just_begun = 1; + json_reader_container_begins(reader, GRPC_JSON_OBJECT); + reader->depth++; + reader->state = GRPC_JSON_STATE_OBJECT_KEY_BEGIN; + reader->in_object = 1; + reader->in_array = 0; + break; + + case '[': + reader->container_just_begun = 1; + json_reader_container_begins(reader, GRPC_JSON_ARRAY); + reader->depth++; + reader->in_object = 0; + reader->in_array = 1; + break; + } + break; + + case GRPC_JSON_STATE_STRING_ESCAPE: + if (reader->escaped_string_was_key) { + reader->state = GRPC_JSON_STATE_OBJECT_KEY_STRING; + } else { + reader->state = GRPC_JSON_STATE_VALUE_STRING; + } + if (reader->unicode_high_surrogate && c != 'u') + return GRPC_JSON_PARSE_ERROR; + switch (c) { + case '"': + case '/': + json_reader_string_add_char(reader, c); + break; + case 'b': + json_reader_string_add_char(reader, '\b'); + break; + case 'f': + json_reader_string_add_char(reader, '\f'); + break; + case 'n': + json_reader_string_add_char(reader, '\n'); + break; + case 'r': + json_reader_string_add_char(reader, '\r'); + break; + case 't': + json_reader_string_add_char(reader, '\t'); + break; + case 'u': + reader->state = GRPC_JSON_STATE_STRING_ESCAPE_U1; + reader->unicode_char = 0; + break; + default: + return GRPC_JSON_PARSE_ERROR; + } + break; + + case GRPC_JSON_STATE_STRING_ESCAPE_U1: + case GRPC_JSON_STATE_STRING_ESCAPE_U2: + case GRPC_JSON_STATE_STRING_ESCAPE_U3: + case GRPC_JSON_STATE_STRING_ESCAPE_U4: + if ((c >= '0') && (c <= '9')) { + c -= '0'; + } else if ((c >= 'A') && (c <= 'F')) { + c -= 'A' - 10; + } else if ((c >= 'a') && (c <= 'f')) { + c -= 'a' - 10; + } else { + return GRPC_JSON_PARSE_ERROR; + } + reader->unicode_char = (uint16_t)(reader->unicode_char << 4); + reader->unicode_char = (uint16_t)(reader->unicode_char | c); + + switch (reader->state) { + case GRPC_JSON_STATE_STRING_ESCAPE_U1: + reader->state = GRPC_JSON_STATE_STRING_ESCAPE_U2; + break; + case GRPC_JSON_STATE_STRING_ESCAPE_U2: + reader->state = GRPC_JSON_STATE_STRING_ESCAPE_U3; + break; + case GRPC_JSON_STATE_STRING_ESCAPE_U3: + reader->state = GRPC_JSON_STATE_STRING_ESCAPE_U4; + break; + case GRPC_JSON_STATE_STRING_ESCAPE_U4: + /* See grpc_json_writer_escape_string to have a description + * of what's going on here. + */ + if ((reader->unicode_char & 0xfc00) == 0xd800) { + /* high surrogate utf-16 */ + if (reader->unicode_high_surrogate != 0) + return GRPC_JSON_PARSE_ERROR; + reader->unicode_high_surrogate = reader->unicode_char; + } else if ((reader->unicode_char & 0xfc00) == 0xdc00) { + /* low surrogate utf-16 */ + uint32_t utf32; + if (reader->unicode_high_surrogate == 0) + return GRPC_JSON_PARSE_ERROR; + utf32 = 0x10000; + utf32 += (uint32_t)( + (reader->unicode_high_surrogate - 0xd800) * 0x400); + utf32 += (uint32_t)(reader->unicode_char - 0xdc00); + json_reader_string_add_utf32(reader, utf32); + reader->unicode_high_surrogate = 0; + } else { + /* anything else */ + if (reader->unicode_high_surrogate != 0) + return GRPC_JSON_PARSE_ERROR; + json_reader_string_add_utf32(reader, reader->unicode_char); + } + if (reader->escaped_string_was_key) { + reader->state = GRPC_JSON_STATE_OBJECT_KEY_STRING; + } else { + reader->state = GRPC_JSON_STATE_VALUE_STRING; + } + break; + default: + GPR_UNREACHABLE_CODE(return GRPC_JSON_INTERNAL_ERROR); + } + break; + + case GRPC_JSON_STATE_VALUE_NUMBER: + json_reader_string_add_char(reader, c); + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + break; + case 'e': + case 'E': + reader->state = GRPC_JSON_STATE_VALUE_NUMBER_E; + break; + case '.': + reader->state = GRPC_JSON_STATE_VALUE_NUMBER_DOT; + break; + default: + return GRPC_JSON_PARSE_ERROR; + } + break; + + case GRPC_JSON_STATE_VALUE_NUMBER_WITH_DECIMAL: + json_reader_string_add_char(reader, c); + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + break; + case 'e': + case 'E': + reader->state = GRPC_JSON_STATE_VALUE_NUMBER_E; + break; + default: + return GRPC_JSON_PARSE_ERROR; + } + break; + + case GRPC_JSON_STATE_VALUE_NUMBER_ZERO: + if (c != '.') return GRPC_JSON_PARSE_ERROR; + json_reader_string_add_char(reader, c); + reader->state = GRPC_JSON_STATE_VALUE_NUMBER_DOT; + break; + + case GRPC_JSON_STATE_VALUE_NUMBER_DOT: + json_reader_string_add_char(reader, c); + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + reader->state = GRPC_JSON_STATE_VALUE_NUMBER_WITH_DECIMAL; + break; + default: + return GRPC_JSON_PARSE_ERROR; + } + break; + + case GRPC_JSON_STATE_VALUE_NUMBER_E: + json_reader_string_add_char(reader, c); + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + reader->state = GRPC_JSON_STATE_VALUE_NUMBER_EPM; + break; + default: + return GRPC_JSON_PARSE_ERROR; + } + break; + + case GRPC_JSON_STATE_VALUE_NUMBER_EPM: + json_reader_string_add_char(reader, c); + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + break; + default: + return GRPC_JSON_PARSE_ERROR; + } + break; + + case GRPC_JSON_STATE_VALUE_TRUE_R: + if (c != 'r') return GRPC_JSON_PARSE_ERROR; + reader->state = GRPC_JSON_STATE_VALUE_TRUE_U; + break; + + case GRPC_JSON_STATE_VALUE_TRUE_U: + if (c != 'u') return GRPC_JSON_PARSE_ERROR; + reader->state = GRPC_JSON_STATE_VALUE_TRUE_E; + break; + + case GRPC_JSON_STATE_VALUE_TRUE_E: + if (c != 'e') return GRPC_JSON_PARSE_ERROR; + json_reader_set_true(reader); + reader->state = GRPC_JSON_STATE_VALUE_END; + break; + + case GRPC_JSON_STATE_VALUE_FALSE_A: + if (c != 'a') return GRPC_JSON_PARSE_ERROR; + reader->state = GRPC_JSON_STATE_VALUE_FALSE_L; + break; + + case GRPC_JSON_STATE_VALUE_FALSE_L: + if (c != 'l') return GRPC_JSON_PARSE_ERROR; + reader->state = GRPC_JSON_STATE_VALUE_FALSE_S; + break; + + case GRPC_JSON_STATE_VALUE_FALSE_S: + if (c != 's') return GRPC_JSON_PARSE_ERROR; + reader->state = GRPC_JSON_STATE_VALUE_FALSE_E; + break; + + case GRPC_JSON_STATE_VALUE_FALSE_E: + if (c != 'e') return GRPC_JSON_PARSE_ERROR; + json_reader_set_false(reader); + reader->state = GRPC_JSON_STATE_VALUE_END; + break; + + case GRPC_JSON_STATE_VALUE_NULL_U: + if (c != 'u') return GRPC_JSON_PARSE_ERROR; + reader->state = GRPC_JSON_STATE_VALUE_NULL_L1; + break; + + case GRPC_JSON_STATE_VALUE_NULL_L1: + if (c != 'l') return GRPC_JSON_PARSE_ERROR; + reader->state = GRPC_JSON_STATE_VALUE_NULL_L2; + break; + + case GRPC_JSON_STATE_VALUE_NULL_L2: + if (c != 'l') return GRPC_JSON_PARSE_ERROR; + json_reader_set_null(reader); + reader->state = GRPC_JSON_STATE_VALUE_END; + break; + + /* All of the VALUE_END cases are handled in the specialized case + * above. */ + case GRPC_JSON_STATE_VALUE_END: + switch (c) { + case ',': + case '}': + case ']': + GPR_UNREACHABLE_CODE(return GRPC_JSON_INTERNAL_ERROR); + break; + + default: + return GRPC_JSON_PARSE_ERROR; + } + break; + + case GRPC_JSON_STATE_END: + return GRPC_JSON_PARSE_ERROR; + } + } + } + + GPR_UNREACHABLE_CODE(return GRPC_JSON_INTERNAL_ERROR); +} diff --git a/src/core/lib/json/json_reader.h b/src/core/lib/json/json_reader.h new file mode 100644 index 0000000000..37a838889d --- /dev/null +++ b/src/core/lib/json/json_reader.h @@ -0,0 +1,160 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_JSON_JSON_READER_H +#define GRPC_CORE_LIB_JSON_JSON_READER_H + +#include <grpc/support/port_platform.h> +#include "src/core/lib/json/json_common.h" + +typedef enum { + GRPC_JSON_STATE_OBJECT_KEY_BEGIN, + GRPC_JSON_STATE_OBJECT_KEY_STRING, + GRPC_JSON_STATE_OBJECT_KEY_END, + GRPC_JSON_STATE_VALUE_BEGIN, + GRPC_JSON_STATE_VALUE_STRING, + GRPC_JSON_STATE_STRING_ESCAPE, + GRPC_JSON_STATE_STRING_ESCAPE_U1, + GRPC_JSON_STATE_STRING_ESCAPE_U2, + GRPC_JSON_STATE_STRING_ESCAPE_U3, + GRPC_JSON_STATE_STRING_ESCAPE_U4, + GRPC_JSON_STATE_VALUE_NUMBER, + GRPC_JSON_STATE_VALUE_NUMBER_WITH_DECIMAL, + GRPC_JSON_STATE_VALUE_NUMBER_ZERO, + GRPC_JSON_STATE_VALUE_NUMBER_DOT, + GRPC_JSON_STATE_VALUE_NUMBER_E, + GRPC_JSON_STATE_VALUE_NUMBER_EPM, + GRPC_JSON_STATE_VALUE_TRUE_R, + GRPC_JSON_STATE_VALUE_TRUE_U, + GRPC_JSON_STATE_VALUE_TRUE_E, + GRPC_JSON_STATE_VALUE_FALSE_A, + GRPC_JSON_STATE_VALUE_FALSE_L, + GRPC_JSON_STATE_VALUE_FALSE_S, + GRPC_JSON_STATE_VALUE_FALSE_E, + GRPC_JSON_STATE_VALUE_NULL_U, + GRPC_JSON_STATE_VALUE_NULL_L1, + GRPC_JSON_STATE_VALUE_NULL_L2, + GRPC_JSON_STATE_VALUE_END, + GRPC_JSON_STATE_END +} grpc_json_reader_state; + +enum { + /* The first non-unicode value is 0x110000. But let's pick + * a value high enough to start our error codes from. These + * values are safe to return from the read_char function. + */ + GRPC_JSON_READ_CHAR_EOF = 0x7ffffff0, + GRPC_JSON_READ_CHAR_EAGAIN, + GRPC_JSON_READ_CHAR_ERROR +}; + +struct grpc_json_reader; + +typedef struct grpc_json_reader_vtable { + /* Clears your internal string scratchpad. */ + void (*string_clear)(void *userdata); + /* Adds a char to the string scratchpad. */ + void (*string_add_char)(void *userdata, uint32_t c); + /* Adds a utf32 char to the string scratchpad. */ + void (*string_add_utf32)(void *userdata, uint32_t c); + /* Reads a character from your input. May be utf-8, 16 or 32. */ + uint32_t (*read_char)(void *userdata); + /* Starts a container of type GRPC_JSON_ARRAY or GRPC_JSON_OBJECT. */ + void (*container_begins)(void *userdata, grpc_json_type type); + /* Ends the current container. Must return the type of its parent. */ + grpc_json_type (*container_ends)(void *userdata); + /* Your internal string scratchpad is an object's key. */ + void (*set_key)(void *userdata); + /* Your internal string scratchpad is a string value. */ + void (*set_string)(void *userdata); + /* Your internal string scratchpad is a numerical value. Return 1 if valid. */ + int (*set_number)(void *userdata); + /* Sets the values true, false or null. */ + void (*set_true)(void *userdata); + void (*set_false)(void *userdata); + void (*set_null)(void *userdata); +} grpc_json_reader_vtable; + +typedef struct grpc_json_reader { + /* That structure is fully private, and initialized by grpc_json_reader_init. + * The definition is public so you can put it on your stack. + */ + + void *userdata; + grpc_json_reader_vtable *vtable; + int depth; + int in_object; + int in_array; + int escaped_string_was_key; + int container_just_begun; + uint16_t unicode_char, unicode_high_surrogate; + grpc_json_reader_state state; +} grpc_json_reader; + +/* The return type of the parser. */ +typedef enum { + GRPC_JSON_DONE, /* The parser finished successfully. */ + GRPC_JSON_EAGAIN, /* The parser yields to get more data. */ + GRPC_JSON_READ_ERROR, /* The parser passes through a read error. */ + GRPC_JSON_PARSE_ERROR, /* The parser found an error in the json stream. */ + GRPC_JSON_INTERNAL_ERROR /* The parser got an internal error. */ +} grpc_json_reader_status; + +/* Call this function to start parsing the input. It will return the following: + * . GRPC_JSON_DONE if the input got eof, and the parsing finished + * successfully. + * . GRPC_JSON_EAGAIN if the read_char function returned again. Call the + * parser again as needed. It is okay to call the parser in polling mode, + * although a bit dull. + * . GRPC_JSON_READ_ERROR if the read_char function returned an error. The + * state isn't broken however, and the function can be called again if the + * error has been corrected. But please use the EAGAIN feature instead for + * consistency. + * . GRPC_JSON_PARSE_ERROR if the input was somehow invalid. + * . GRPC_JSON_INTERNAL_ERROR if the parser somehow ended into an invalid + * internal state. + */ +grpc_json_reader_status grpc_json_reader_run(grpc_json_reader *reader); + +/* Call this function to initialize the reader structure. */ +void grpc_json_reader_init(grpc_json_reader *reader, + grpc_json_reader_vtable *vtable, void *userdata); + +/* You may call this from the read_char callback if you don't know where is the + * end of your input stream, and you'd like the json reader to hint you that it + * has completed reading its input, so you can return an EOF to it. Note that + * there might still be trailing whitespaces after that point. + */ +int grpc_json_reader_is_complete(grpc_json_reader *reader); + +#endif /* GRPC_CORE_LIB_JSON_JSON_READER_H */ diff --git a/src/core/lib/json/json_string.c b/src/core/lib/json/json_string.c new file mode 100644 index 0000000000..8e6f1253dc --- /dev/null +++ b/src/core/lib/json/json_string.c @@ -0,0 +1,379 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <stdlib.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +#include "src/core/lib/json/json.h" +#include "src/core/lib/json/json_reader.h" +#include "src/core/lib/json/json_writer.h" + +/* The json reader will construct a bunch of grpc_json objects and + * link them all up together in a tree-like structure that will represent + * the json data in memory. + * + * It also uses its own input as a scratchpad to store all of the decoded, + * unescaped strings. So we need to keep track of all these pointers in + * that opaque structure the reader will carry for us. + * + * Note that this works because the act of parsing json always reduces its + * input size, and never expands it. + */ +typedef struct { + grpc_json *top; + grpc_json *current_container; + grpc_json *current_value; + uint8_t *input; + uint8_t *key; + uint8_t *string; + uint8_t *string_ptr; + size_t remaining_input; +} json_reader_userdata; + +/* This json writer will put everything in a big string. + * The point is that we allocate that string in chunks of 256 bytes. + */ +typedef struct { + char *output; + size_t free_space; + size_t string_len; + size_t allocated; +} json_writer_userdata; + +/* This function checks if there's enough space left in the output buffer, + * and will enlarge it if necessary. We're only allocating chunks of 256 + * bytes at a time (or multiples thereof). + */ +static void json_writer_output_check(void *userdata, size_t needed) { + json_writer_userdata *state = userdata; + if (state->free_space >= needed) return; + needed -= state->free_space; + /* Round up by 256 bytes. */ + needed = (needed + 0xff) & ~0xffU; + state->output = gpr_realloc(state->output, state->allocated + needed); + state->free_space += needed; + state->allocated += needed; +} + +/* These are needed by the writer's implementation. */ +static void json_writer_output_char(void *userdata, char c) { + json_writer_userdata *state = userdata; + json_writer_output_check(userdata, 1); + state->output[state->string_len++] = c; + state->free_space--; +} + +static void json_writer_output_string_with_len(void *userdata, const char *str, + size_t len) { + json_writer_userdata *state = userdata; + json_writer_output_check(userdata, len); + memcpy(state->output + state->string_len, str, len); + state->string_len += len; + state->free_space -= len; +} + +static void json_writer_output_string(void *userdata, const char *str) { + size_t len = strlen(str); + json_writer_output_string_with_len(userdata, str, len); +} + +/* The reader asks us to clear our scratchpad. In our case, we'll simply mark + * the end of the current string, and advance our output pointer. + */ +static void json_reader_string_clear(void *userdata) { + json_reader_userdata *state = userdata; + if (state->string) { + GPR_ASSERT(state->string_ptr < state->input); + *state->string_ptr++ = 0; + } + state->string = state->string_ptr; +} + +static void json_reader_string_add_char(void *userdata, uint32_t c) { + json_reader_userdata *state = userdata; + GPR_ASSERT(state->string_ptr < state->input); + GPR_ASSERT(c <= 0xff); + *state->string_ptr++ = (uint8_t)c; +} + +/* We are converting a UTF-32 character into UTF-8 here, + * as described by RFC3629. + */ +static void json_reader_string_add_utf32(void *userdata, uint32_t c) { + if (c <= 0x7f) { + json_reader_string_add_char(userdata, c); + } else if (c <= 0x7ff) { + uint32_t b1 = 0xc0 | ((c >> 6) & 0x1f); + uint32_t b2 = 0x80 | (c & 0x3f); + json_reader_string_add_char(userdata, b1); + json_reader_string_add_char(userdata, b2); + } else if (c <= 0xffff) { + uint32_t b1 = 0xe0 | ((c >> 12) & 0x0f); + uint32_t b2 = 0x80 | ((c >> 6) & 0x3f); + uint32_t b3 = 0x80 | (c & 0x3f); + json_reader_string_add_char(userdata, b1); + json_reader_string_add_char(userdata, b2); + json_reader_string_add_char(userdata, b3); + } else if (c <= 0x1fffff) { + uint32_t b1 = 0xf0 | ((c >> 18) & 0x07); + uint32_t b2 = 0x80 | ((c >> 12) & 0x3f); + uint32_t b3 = 0x80 | ((c >> 6) & 0x3f); + uint32_t b4 = 0x80 | (c & 0x3f); + json_reader_string_add_char(userdata, b1); + json_reader_string_add_char(userdata, b2); + json_reader_string_add_char(userdata, b3); + json_reader_string_add_char(userdata, b4); + } +} + +/* We consider that the input may be a zero-terminated string. So we + * can end up hitting eof before the end of the alleged string length. + */ +static uint32_t json_reader_read_char(void *userdata) { + uint32_t r; + json_reader_userdata *state = userdata; + + if (state->remaining_input == 0) return GRPC_JSON_READ_CHAR_EOF; + + r = *state->input++; + state->remaining_input--; + + if (r == 0) { + state->remaining_input = 0; + return GRPC_JSON_READ_CHAR_EOF; + } + + return r; +} + +/* Helper function to create a new grpc_json object and link it into + * our tree-in-progress inside our opaque structure. + */ +static grpc_json *json_create_and_link(void *userdata, grpc_json_type type) { + json_reader_userdata *state = userdata; + grpc_json *json = grpc_json_create(type); + + json->parent = state->current_container; + json->prev = state->current_value; + state->current_value = json; + + if (json->prev) { + json->prev->next = json; + } + if (json->parent) { + if (!json->parent->child) { + json->parent->child = json; + } + if (json->parent->type == GRPC_JSON_OBJECT) { + json->key = (char *)state->key; + } + } + if (!state->top) { + state->top = json; + } + + return json; +} + +static void json_reader_container_begins(void *userdata, grpc_json_type type) { + json_reader_userdata *state = userdata; + grpc_json *container; + + GPR_ASSERT(type == GRPC_JSON_ARRAY || type == GRPC_JSON_OBJECT); + + container = json_create_and_link(userdata, type); + state->current_container = container; + state->current_value = NULL; +} + +/* It's important to remember that the reader is mostly stateless, so it + * isn't trying to remember what the container was prior the one that just + * ends. Since we're keeping track of these for our own purpose, we are + * able to return that information back, which is useful for it to validate + * the input json stream. + * + * Also note that if we're at the top of the tree, and the last container + * ends, we have to return GRPC_JSON_TOP_LEVEL. + */ +static grpc_json_type json_reader_container_ends(void *userdata) { + grpc_json_type container_type = GRPC_JSON_TOP_LEVEL; + json_reader_userdata *state = userdata; + + GPR_ASSERT(state->current_container); + + state->current_value = state->current_container; + state->current_container = state->current_container->parent; + + if (state->current_container) { + container_type = state->current_container->type; + } + + return container_type; +} + +/* The next 3 functions basically are the reader asking us to use our string + * scratchpad for one of these 3 purposes. + * + * Note that in the set_number case, we're not going to try interpreting it. + * We'll keep it as a string, and leave it to the caller to evaluate it. + */ +static void json_reader_set_key(void *userdata) { + json_reader_userdata *state = userdata; + state->key = state->string; +} + +static void json_reader_set_string(void *userdata) { + json_reader_userdata *state = userdata; + grpc_json *json = json_create_and_link(userdata, GRPC_JSON_STRING); + json->value = (char *)state->string; +} + +static int json_reader_set_number(void *userdata) { + json_reader_userdata *state = userdata; + grpc_json *json = json_create_and_link(userdata, GRPC_JSON_NUMBER); + json->value = (char *)state->string; + return 1; +} + +/* The object types true, false and null are self-sufficient, and don't need + * any more information beside their type. + */ +static void json_reader_set_true(void *userdata) { + json_create_and_link(userdata, GRPC_JSON_TRUE); +} + +static void json_reader_set_false(void *userdata) { + json_create_and_link(userdata, GRPC_JSON_FALSE); +} + +static void json_reader_set_null(void *userdata) { + json_create_and_link(userdata, GRPC_JSON_NULL); +} + +static grpc_json_reader_vtable reader_vtable = { + json_reader_string_clear, json_reader_string_add_char, + json_reader_string_add_utf32, json_reader_read_char, + json_reader_container_begins, json_reader_container_ends, + json_reader_set_key, json_reader_set_string, + json_reader_set_number, json_reader_set_true, + json_reader_set_false, json_reader_set_null}; + +/* And finally, let's define our public API. */ +grpc_json *grpc_json_parse_string_with_len(char *input, size_t size) { + grpc_json_reader reader; + json_reader_userdata state; + grpc_json *json = NULL; + grpc_json_reader_status status; + + if (!input) return NULL; + + state.top = state.current_container = state.current_value = NULL; + state.string = state.key = NULL; + state.string_ptr = state.input = (uint8_t *)input; + state.remaining_input = size; + grpc_json_reader_init(&reader, &reader_vtable, &state); + + status = grpc_json_reader_run(&reader); + json = state.top; + + if ((status != GRPC_JSON_DONE) && json) { + grpc_json_destroy(json); + json = NULL; + } + + return json; +} + +#define UNBOUND_JSON_STRING_LENGTH 0x7fffffff + +grpc_json *grpc_json_parse_string(char *input) { + return grpc_json_parse_string_with_len(input, UNBOUND_JSON_STRING_LENGTH); +} + +static void json_dump_recursive(grpc_json_writer *writer, grpc_json *json, + int in_object) { + while (json) { + if (in_object) grpc_json_writer_object_key(writer, json->key); + + switch (json->type) { + case GRPC_JSON_OBJECT: + case GRPC_JSON_ARRAY: + grpc_json_writer_container_begins(writer, json->type); + if (json->child) + json_dump_recursive(writer, json->child, + json->type == GRPC_JSON_OBJECT); + grpc_json_writer_container_ends(writer, json->type); + break; + case GRPC_JSON_STRING: + grpc_json_writer_value_string(writer, json->value); + break; + case GRPC_JSON_NUMBER: + grpc_json_writer_value_raw(writer, json->value); + break; + case GRPC_JSON_TRUE: + grpc_json_writer_value_raw_with_len(writer, "true", 4); + break; + case GRPC_JSON_FALSE: + grpc_json_writer_value_raw_with_len(writer, "false", 5); + break; + case GRPC_JSON_NULL: + grpc_json_writer_value_raw_with_len(writer, "null", 4); + break; + default: + GPR_UNREACHABLE_CODE(abort()); + } + json = json->next; + } +} + +static grpc_json_writer_vtable writer_vtable = { + json_writer_output_char, json_writer_output_string, + json_writer_output_string_with_len}; + +char *grpc_json_dump_to_string(grpc_json *json, int indent) { + grpc_json_writer writer; + json_writer_userdata state; + + state.output = NULL; + state.free_space = state.string_len = state.allocated = 0; + grpc_json_writer_init(&writer, indent, &writer_vtable, &state); + + json_dump_recursive(&writer, json, 0); + + json_writer_output_char(&state, 0); + + return state.output; +} diff --git a/src/core/lib/json/json_writer.c b/src/core/lib/json/json_writer.c new file mode 100644 index 0000000000..d614a72fc4 --- /dev/null +++ b/src/core/lib/json/json_writer.c @@ -0,0 +1,258 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <string.h> + +#include <grpc/support/port_platform.h> + +#include "src/core/lib/json/json_writer.h" + +static void json_writer_output_char(grpc_json_writer *writer, char c) { + writer->vtable->output_char(writer->userdata, c); +} + +static void json_writer_output_string(grpc_json_writer *writer, + const char *str) { + writer->vtable->output_string(writer->userdata, str); +} + +static void json_writer_output_string_with_len(grpc_json_writer *writer, + const char *str, size_t len) { + writer->vtable->output_string_with_len(writer->userdata, str, len); +} + +void grpc_json_writer_init(grpc_json_writer *writer, int indent, + grpc_json_writer_vtable *vtable, void *userdata) { + memset(writer, 0, sizeof(*writer)); + writer->container_empty = 1; + writer->indent = indent; + writer->vtable = vtable; + writer->userdata = userdata; +} + +static void json_writer_output_indent(grpc_json_writer *writer) { + static const char spacesstr[] = + " " + " " + " " + " "; + + unsigned spaces = (unsigned)(writer->depth * writer->indent); + + if (writer->indent == 0) return; + + if (writer->got_key) { + json_writer_output_char(writer, ' '); + return; + } + + while (spaces >= (sizeof(spacesstr) - 1)) { + json_writer_output_string_with_len(writer, spacesstr, + sizeof(spacesstr) - 1); + spaces -= (unsigned)(sizeof(spacesstr) - 1); + } + + if (spaces == 0) return; + + json_writer_output_string_with_len( + writer, spacesstr + sizeof(spacesstr) - 1 - spaces, spaces); +} + +static void json_writer_value_end(grpc_json_writer *writer) { + if (writer->container_empty) { + writer->container_empty = 0; + if ((writer->indent == 0) || (writer->depth == 0)) return; + json_writer_output_char(writer, '\n'); + } else { + json_writer_output_char(writer, ','); + if (writer->indent == 0) return; + json_writer_output_char(writer, '\n'); + } +} + +static void json_writer_escape_utf16(grpc_json_writer *writer, uint16_t utf16) { + static const char hex[] = "0123456789abcdef"; + + json_writer_output_string_with_len(writer, "\\u", 2); + json_writer_output_char(writer, hex[(utf16 >> 12) & 0x0f]); + json_writer_output_char(writer, hex[(utf16 >> 8) & 0x0f]); + json_writer_output_char(writer, hex[(utf16 >> 4) & 0x0f]); + json_writer_output_char(writer, hex[(utf16)&0x0f]); +} + +static void json_writer_escape_string(grpc_json_writer *writer, + const char *string) { + json_writer_output_char(writer, '"'); + + for (;;) { + uint8_t c = (uint8_t)*string++; + if (c == 0) { + break; + } else if ((c >= 32) && (c <= 126)) { + if ((c == '\\') || (c == '"')) json_writer_output_char(writer, '\\'); + json_writer_output_char(writer, (char)c); + } else if ((c < 32) || (c == 127)) { + switch (c) { + case '\b': + json_writer_output_string_with_len(writer, "\\b", 2); + break; + case '\f': + json_writer_output_string_with_len(writer, "\\f", 2); + break; + case '\n': + json_writer_output_string_with_len(writer, "\\n", 2); + break; + case '\r': + json_writer_output_string_with_len(writer, "\\r", 2); + break; + case '\t': + json_writer_output_string_with_len(writer, "\\t", 2); + break; + default: + json_writer_escape_utf16(writer, c); + break; + } + } else { + uint32_t utf32 = 0; + int extra = 0; + int i; + int valid = 1; + if ((c & 0xe0) == 0xc0) { + utf32 = c & 0x1f; + extra = 1; + } else if ((c & 0xf0) == 0xe0) { + utf32 = c & 0x0f; + extra = 2; + } else if ((c & 0xf8) == 0xf0) { + utf32 = c & 0x07; + extra = 3; + } else { + break; + } + for (i = 0; i < extra; i++) { + utf32 <<= 6; + c = (uint8_t)(*string++); + /* Breaks out and bail on any invalid UTF-8 sequence, including \0. */ + if ((c & 0xc0) != 0x80) { + valid = 0; + break; + } + utf32 |= c & 0x3f; + } + if (!valid) break; + /* The range 0xd800 - 0xdfff is reserved by the surrogates ad vitam. + * Any other range is technically reserved for future usage, so if we + * don't want the software to break in the future, we have to allow + * anything else. The first non-unicode character is 0x110000. */ + if (((utf32 >= 0xd800) && (utf32 <= 0xdfff)) || (utf32 >= 0x110000)) + break; + if (utf32 >= 0x10000) { + /* If utf32 contains a character that is above 0xffff, it needs to be + * broken down into a utf-16 surrogate pair. A surrogate pair is first + * a high surrogate, followed by a low surrogate. Each surrogate holds + * 10 bits of usable data, thus allowing a total of 20 bits of data. + * The high surrogate marker is 0xd800, while the low surrogate marker + * is 0xdc00. The low 10 bits of each will be the usable data. + * + * After re-combining the 20 bits of data, one has to add 0x10000 to + * the resulting value, in order to obtain the original character. + * This is obviously because the range 0x0000 - 0xffff can be written + * without any special trick. + * + * Since 0x10ffff is the highest allowed character, we're working in + * the range 0x00000 - 0xfffff after we decrement it by 0x10000. + * That range is exactly 20 bits. + */ + utf32 -= 0x10000; + json_writer_escape_utf16(writer, (uint16_t)(0xd800 | (utf32 >> 10))); + json_writer_escape_utf16(writer, (uint16_t)(0xdc00 | (utf32 & 0x3ff))); + } else { + json_writer_escape_utf16(writer, (uint16_t)utf32); + } + } + } + + json_writer_output_char(writer, '"'); +} + +void grpc_json_writer_container_begins(grpc_json_writer *writer, + grpc_json_type type) { + if (!writer->got_key) json_writer_value_end(writer); + json_writer_output_indent(writer); + json_writer_output_char(writer, type == GRPC_JSON_OBJECT ? '{' : '['); + writer->container_empty = 1; + writer->got_key = 0; + writer->depth++; +} + +void grpc_json_writer_container_ends(grpc_json_writer *writer, + grpc_json_type type) { + if (writer->indent && !writer->container_empty) + json_writer_output_char(writer, '\n'); + writer->depth--; + if (!writer->container_empty) json_writer_output_indent(writer); + json_writer_output_char(writer, type == GRPC_JSON_OBJECT ? '}' : ']'); + writer->container_empty = 0; + writer->got_key = 0; +} + +void grpc_json_writer_object_key(grpc_json_writer *writer, const char *string) { + json_writer_value_end(writer); + json_writer_output_indent(writer); + json_writer_escape_string(writer, string); + json_writer_output_char(writer, ':'); + writer->got_key = 1; +} + +void grpc_json_writer_value_raw(grpc_json_writer *writer, const char *string) { + if (!writer->got_key) json_writer_value_end(writer); + json_writer_output_indent(writer); + json_writer_output_string(writer, string); + writer->got_key = 0; +} + +void grpc_json_writer_value_raw_with_len(grpc_json_writer *writer, + const char *string, size_t len) { + if (!writer->got_key) json_writer_value_end(writer); + json_writer_output_indent(writer); + json_writer_output_string_with_len(writer, string, len); + writer->got_key = 0; +} + +void grpc_json_writer_value_string(grpc_json_writer *writer, + const char *string) { + if (!writer->got_key) json_writer_value_end(writer); + json_writer_output_indent(writer); + json_writer_escape_string(writer, string); + writer->got_key = 0; +} diff --git a/src/core/lib/json/json_writer.h b/src/core/lib/json/json_writer.h new file mode 100644 index 0000000000..f90e79cd74 --- /dev/null +++ b/src/core/lib/json/json_writer.h @@ -0,0 +1,97 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* The idea of the writer is basically symmetrical of the reader. While the + * reader emits various calls to your code, the writer takes basically the + * same calls and emit json out of it. It doesn't try to make any check on + * the order of the calls you do on it. Meaning you can theorically force + * it to generate invalid json. + * + * Also, unlike the reader, the writer expects UTF-8 encoded input strings. + * These strings will be UTF-8 validated, and any invalid character will + * cut the conversion short, before any invalid UTF-8 sequence, thus forming + * a valid UTF-8 string overall. + */ + +#ifndef GRPC_CORE_LIB_JSON_JSON_WRITER_H +#define GRPC_CORE_LIB_JSON_JSON_WRITER_H + +#include <stdlib.h> + +#include "src/core/lib/json/json_common.h" + +typedef struct grpc_json_writer_vtable { + /* Adds a character to the output stream. */ + void (*output_char)(void *userdata, char); + /* Adds a zero-terminated string to the output stream. */ + void (*output_string)(void *userdata, const char *str); + /* Adds a fixed-length string to the output stream. */ + void (*output_string_with_len)(void *userdata, const char *str, size_t len); + +} grpc_json_writer_vtable; + +typedef struct grpc_json_writer { + void *userdata; + grpc_json_writer_vtable *vtable; + int indent; + int depth; + int container_empty; + int got_key; +} grpc_json_writer; + +/* Call this to initialize your writer structure. The indent parameter is + * specifying the number of spaces to use for indenting the output. If you + * use indent=0, then the output will not have any newlines either, thus + * emitting a condensed json output. + */ +void grpc_json_writer_init(grpc_json_writer *writer, int indent, + grpc_json_writer_vtable *vtable, void *userdata); + +/* Signals the beginning of a container. */ +void grpc_json_writer_container_begins(grpc_json_writer *writer, + grpc_json_type type); +/* Signals the end of a container. */ +void grpc_json_writer_container_ends(grpc_json_writer *writer, + grpc_json_type type); +/* Writes down an object key for the next value. */ +void grpc_json_writer_object_key(grpc_json_writer *writer, const char *string); +/* Sets a raw value. Useful for numbers. */ +void grpc_json_writer_value_raw(grpc_json_writer *writer, const char *string); +/* Sets a raw value with its length. Useful for values like true or false. */ +void grpc_json_writer_value_raw_with_len(grpc_json_writer *writer, + const char *string, size_t len); +/* Sets a string value. It'll be escaped, and utf-8 validated. */ +void grpc_json_writer_value_string(grpc_json_writer *writer, + const char *string); + +#endif /* GRPC_CORE_LIB_JSON_JSON_WRITER_H */ diff --git a/src/core/lib/profiling/basic_timers.c b/src/core/lib/profiling/basic_timers.c new file mode 100644 index 0000000000..15a9584981 --- /dev/null +++ b/src/core/lib/profiling/basic_timers.c @@ -0,0 +1,274 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GRPC_BASIC_PROFILER + +#include "src/core/lib/profiling/timers.h" + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/sync.h> +#include <grpc/support/thd.h> +#include <grpc/support/time.h> +#include <stdio.h> + +typedef enum { BEGIN = '{', END = '}', MARK = '.' } marker_type; + +typedef struct gpr_timer_entry { + gpr_timespec tm; + const char *tagstr; + const char *file; + short line; + char type; + uint8_t important; + int thd; +} gpr_timer_entry; + +#define MAX_COUNT 1000000 + +typedef struct gpr_timer_log { + size_t num_entries; + struct gpr_timer_log *next; + struct gpr_timer_log *prev; + gpr_timer_entry log[MAX_COUNT]; +} gpr_timer_log; + +typedef struct gpr_timer_log_list { + gpr_timer_log *head; + /* valid iff head!=NULL */ + gpr_timer_log *tail; +} gpr_timer_log_list; + +static __thread gpr_timer_log *g_thread_log; +static gpr_once g_once_init = GPR_ONCE_INIT; +static FILE *output_file; +static const char *output_filename = "latency_trace.txt"; +static pthread_mutex_t g_mu; +static pthread_cond_t g_cv; +static gpr_timer_log_list g_in_progress_logs; +static gpr_timer_log_list g_done_logs; +static int g_shutdown; +static gpr_thd_id g_writing_thread; +static __thread int g_thread_id; +static int g_next_thread_id; + +static int timer_log_push_back(gpr_timer_log_list *list, gpr_timer_log *log) { + if (list->head == NULL) { + list->head = list->tail = log; + log->next = log->prev = NULL; + return 1; + } else { + log->prev = list->tail; + log->next = NULL; + list->tail->next = log; + list->tail = log; + return 0; + } +} + +static gpr_timer_log *timer_log_pop_front(gpr_timer_log_list *list) { + gpr_timer_log *out = list->head; + if (out != NULL) { + list->head = out->next; + if (list->head != NULL) { + list->head->prev = NULL; + } else { + list->tail = NULL; + } + } + return out; +} + +static void timer_log_remove(gpr_timer_log_list *list, gpr_timer_log *log) { + if (log->prev == NULL) { + list->head = log->next; + if (list->head != NULL) { + list->head->prev = NULL; + } + } else { + log->prev->next = log->next; + } + if (log->next == NULL) { + list->tail = log->prev; + if (list->tail != NULL) { + list->tail->next = NULL; + } + } else { + log->next->prev = log->prev; + } +} + +static void write_log(gpr_timer_log *log) { + size_t i; + if (output_file == NULL) { + output_file = fopen(output_filename, "w"); + } + for (i = 0; i < log->num_entries; i++) { + gpr_timer_entry *entry = &(log->log[i]); + if (gpr_time_cmp(entry->tm, gpr_time_0(entry->tm.clock_type)) < 0) { + entry->tm = gpr_time_0(entry->tm.clock_type); + } + fprintf(output_file, + "{\"t\": %lld.%09d, \"thd\": \"%d\", \"type\": \"%c\", \"tag\": " + "\"%s\", \"file\": \"%s\", \"line\": %d, \"imp\": %d}\n", + (long long)entry->tm.tv_sec, (int)entry->tm.tv_nsec, entry->thd, + entry->type, entry->tagstr, entry->file, entry->line, + entry->important); + } +} + +static void writing_thread(void *unused) { + gpr_timer_log *log; + pthread_mutex_lock(&g_mu); + for (;;) { + while ((log = timer_log_pop_front(&g_done_logs)) == NULL && !g_shutdown) { + pthread_cond_wait(&g_cv, &g_mu); + } + if (log != NULL) { + pthread_mutex_unlock(&g_mu); + write_log(log); + free(log); + pthread_mutex_lock(&g_mu); + } + if (g_shutdown) { + pthread_mutex_unlock(&g_mu); + return; + } + } +} + +static void flush_logs(gpr_timer_log_list *list) { + gpr_timer_log *log; + while ((log = timer_log_pop_front(list)) != NULL) { + write_log(log); + free(log); + } +} + +static void finish_writing() { + pthread_mutex_lock(&g_mu); + g_shutdown = 1; + pthread_cond_signal(&g_cv); + pthread_mutex_unlock(&g_mu); + gpr_thd_join(g_writing_thread); + + gpr_log(GPR_INFO, "flushing logs"); + + pthread_mutex_lock(&g_mu); + flush_logs(&g_done_logs); + flush_logs(&g_in_progress_logs); + pthread_mutex_unlock(&g_mu); + + if (output_file) { + fclose(output_file); + } +} + +void gpr_timers_set_log_filename(const char *filename) { + output_filename = filename; +} + +static void init_output() { + gpr_thd_options options = gpr_thd_options_default(); + gpr_thd_options_set_joinable(&options); + gpr_thd_new(&g_writing_thread, writing_thread, NULL, &options); + atexit(finish_writing); +} + +static void rotate_log() { + gpr_timer_log *new = malloc(sizeof(*new)); + gpr_once_init(&g_once_init, init_output); + new->num_entries = 0; + pthread_mutex_lock(&g_mu); + if (g_thread_log != NULL) { + timer_log_remove(&g_in_progress_logs, g_thread_log); + if (timer_log_push_back(&g_done_logs, g_thread_log)) { + pthread_cond_signal(&g_cv); + } + } else { + g_thread_id = g_next_thread_id++; + } + timer_log_push_back(&g_in_progress_logs, new); + pthread_mutex_unlock(&g_mu); + g_thread_log = new; +} + +static void gpr_timers_log_add(const char *tagstr, marker_type type, + int important, const char *file, int line) { + gpr_timer_entry *entry; + + if (g_thread_log == NULL || g_thread_log->num_entries == MAX_COUNT) { + rotate_log(); + } + + entry = &g_thread_log->log[g_thread_log->num_entries++]; + + entry->tm = gpr_now(GPR_CLOCK_PRECISE); + entry->tagstr = tagstr; + entry->type = type; + entry->file = file; + entry->line = (short)line; + entry->important = important != 0; + entry->thd = g_thread_id; +} + +/* Latency profiler API implementation. */ +void gpr_timer_add_mark(const char *tagstr, int important, const char *file, + int line) { + gpr_timers_log_add(tagstr, MARK, important, file, line); +} + +void gpr_timer_begin(const char *tagstr, int important, const char *file, + int line) { + gpr_timers_log_add(tagstr, BEGIN, important, file, line); +} + +void gpr_timer_end(const char *tagstr, int important, const char *file, + int line) { + gpr_timers_log_add(tagstr, END, important, file, line); +} + +/* Basic profiler specific API functions. */ +void gpr_timers_global_init(void) {} + +void gpr_timers_global_destroy(void) {} + +#else /* !GRPC_BASIC_PROFILER */ +void gpr_timers_global_init(void) {} + +void gpr_timers_global_destroy(void) {} + +void gpr_timers_set_log_filename(const char *filename) {} +#endif /* GRPC_BASIC_PROFILER */ diff --git a/src/core/lib/profiling/stap_probes.d b/src/core/lib/profiling/stap_probes.d new file mode 100644 index 0000000000..153de91752 --- /dev/null +++ b/src/core/lib/profiling/stap_probes.d @@ -0,0 +1,7 @@ +provider _stap { + probe add_mark(int tag); + probe add_important_mark(int tag); + probe timing_ns_begin(int tag); + probe timing_ns_end(int tag); +}; + diff --git a/src/core/lib/profiling/stap_timers.c b/src/core/lib/profiling/stap_timers.c new file mode 100644 index 0000000000..f55c1a569a --- /dev/null +++ b/src/core/lib/profiling/stap_timers.c @@ -0,0 +1,65 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GRPC_STAP_PROFILER + +#include "src/core/lib/profiling/timers.h" + +#include <sys/sdt.h> +/* Generated from src/core/profiling/stap_probes.d */ +#include "src/core/lib/profiling/stap_probes.h" + +/* Latency profiler API implementation. */ +void gpr_timer_add_mark(int tag, const char *tagstr, void *id, const char *file, + int line) { + _STAP_ADD_MARK(tag); +} + +void gpr_timer_add_important_mark(int tag, const char *tagstr, void *id, + const char *file, int line) { + _STAP_ADD_IMPORTANT_MARK(tag); +} + +void gpr_timer_begin(int tag, const char *tagstr, void *id, const char *file, + int line) { + _STAP_TIMING_NS_BEGIN(tag); +} + +void gpr_timer_end(int tag, const char *tagstr, void *id, const char *file, + int line) { + _STAP_TIMING_NS_END(tag); +} + +#endif /* GRPC_STAP_PROFILER */ diff --git a/src/core/lib/profiling/timers.h b/src/core/lib/profiling/timers.h new file mode 100644 index 0000000000..1303593ffb --- /dev/null +++ b/src/core/lib/profiling/timers.h @@ -0,0 +1,119 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_PROFILING_TIMERS_H +#define GRPC_CORE_LIB_PROFILING_TIMERS_H + +#ifdef __cplusplus +extern "C" { +#endif + +void gpr_timers_global_init(void); +void gpr_timers_global_destroy(void); + +void gpr_timer_add_mark(const char *tagstr, int important, const char *file, + int line); +void gpr_timer_begin(const char *tagstr, int important, const char *file, + int line); +void gpr_timer_end(const char *tagstr, int important, const char *file, + int line); + +void gpr_timers_set_log_filename(const char *filename); + +#if !(defined(GRPC_STAP_PROFILER) + defined(GRPC_BASIC_PROFILER)) +/* No profiling. No-op all the things. */ +#define GPR_TIMER_MARK(tag, important) \ + do { \ + } while (0) + +#define GPR_TIMER_BEGIN(tag, important) \ + do { \ + } while (0) + +#define GPR_TIMER_END(tag, important) \ + do { \ + } while (0) + +#else /* at least one profiler requested... */ +/* ... hopefully only one. */ +#if defined(GRPC_STAP_PROFILER) && defined(GRPC_BASIC_PROFILER) +#error "GRPC_STAP_PROFILER and GRPC_BASIC_PROFILER are mutually exclusive." +#endif + +/* Generic profiling interface. */ +#define GPR_TIMER_MARK(tag, important) \ + gpr_timer_add_mark(tag, important, __FILE__, __LINE__); + +#define GPR_TIMER_BEGIN(tag, important) \ + gpr_timer_begin(tag, important, __FILE__, __LINE__); + +#define GPR_TIMER_END(tag, important) \ + gpr_timer_end(tag, important, __FILE__, __LINE__); + +#ifdef GRPC_STAP_PROFILER +/* Empty placeholder for now. */ +#endif /* GRPC_STAP_PROFILER */ + +#ifdef GRPC_BASIC_PROFILER +/* Empty placeholder for now. */ +#endif /* GRPC_BASIC_PROFILER */ + +#endif /* at least one profiler requested. */ + +#ifdef __cplusplus +} + +#if (defined(GRPC_STAP_PROFILER) + defined(GRPC_BASIC_PROFILER)) +namespace grpc { +class ProfileScope { + public: + ProfileScope(const char *desc, bool important) : desc_(desc) { + GPR_TIMER_BEGIN(desc_, important ? 1 : 0); + } + ~ProfileScope() { GPR_TIMER_END(desc_, 0); } + + private: + const char *const desc_; +}; +} + +#define GPR_TIMER_SCOPE(tag, important) \ + ::grpc::ProfileScope _profile_scope_##__LINE__((tag), (important)) +#else +#define GPR_TIMER_SCOPE(tag, important) \ + do { \ + } while (false) +#endif +#endif + +#endif /* GRPC_CORE_LIB_PROFILING_TIMERS_H */ diff --git a/src/core/lib/proto/grpc/lb/v0/load_balancer.pb.c b/src/core/lib/proto/grpc/lb/v0/load_balancer.pb.c new file mode 100644 index 0000000000..8f82141f96 --- /dev/null +++ b/src/core/lib/proto/grpc/lb/v0/load_balancer.pb.c @@ -0,0 +1,119 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.3.5-dev */ + +#include "src/core/lib/proto/grpc/lb/v0/load_balancer.pb.h" + +#if PB_PROTO_HEADER_VERSION != 30 +#error Regenerate this file with the current version of nanopb generator. +#endif + + + +const pb_field_t grpc_lb_v0_Duration_fields[3] = { + PB_FIELD( 1, INT64 , OPTIONAL, STATIC , FIRST, grpc_lb_v0_Duration, seconds, seconds, 0), + PB_FIELD( 2, INT32 , OPTIONAL, STATIC , OTHER, grpc_lb_v0_Duration, nanos, seconds, 0), + PB_LAST_FIELD +}; + +const pb_field_t grpc_lb_v0_LoadBalanceRequest_fields[3] = { + PB_FIELD( 1, MESSAGE , OPTIONAL, STATIC , FIRST, grpc_lb_v0_LoadBalanceRequest, initial_request, initial_request, &grpc_lb_v0_InitialLoadBalanceRequest_fields), + PB_FIELD( 2, MESSAGE , OPTIONAL, STATIC , OTHER, grpc_lb_v0_LoadBalanceRequest, client_stats, initial_request, &grpc_lb_v0_ClientStats_fields), + PB_LAST_FIELD +}; + +const pb_field_t grpc_lb_v0_InitialLoadBalanceRequest_fields[2] = { + PB_FIELD( 1, STRING , OPTIONAL, STATIC , FIRST, grpc_lb_v0_InitialLoadBalanceRequest, name, name, 0), + PB_LAST_FIELD +}; + +const pb_field_t grpc_lb_v0_ClientStats_fields[4] = { + PB_FIELD( 1, INT64 , OPTIONAL, STATIC , FIRST, grpc_lb_v0_ClientStats, total_requests, total_requests, 0), + PB_FIELD( 2, INT64 , OPTIONAL, STATIC , OTHER, grpc_lb_v0_ClientStats, client_rpc_errors, total_requests, 0), + PB_FIELD( 3, INT64 , OPTIONAL, STATIC , OTHER, grpc_lb_v0_ClientStats, dropped_requests, client_rpc_errors, 0), + PB_LAST_FIELD +}; + +const pb_field_t grpc_lb_v0_LoadBalanceResponse_fields[3] = { + PB_FIELD( 1, MESSAGE , OPTIONAL, STATIC , FIRST, grpc_lb_v0_LoadBalanceResponse, initial_response, initial_response, &grpc_lb_v0_InitialLoadBalanceResponse_fields), + PB_FIELD( 2, MESSAGE , OPTIONAL, STATIC , OTHER, grpc_lb_v0_LoadBalanceResponse, server_list, initial_response, &grpc_lb_v0_ServerList_fields), + PB_LAST_FIELD +}; + +const pb_field_t grpc_lb_v0_InitialLoadBalanceResponse_fields[4] = { + PB_FIELD( 1, STRING , OPTIONAL, STATIC , FIRST, grpc_lb_v0_InitialLoadBalanceResponse, client_config, client_config, 0), + PB_FIELD( 2, STRING , OPTIONAL, STATIC , OTHER, grpc_lb_v0_InitialLoadBalanceResponse, load_balancer_delegate, client_config, 0), + PB_FIELD( 3, MESSAGE , OPTIONAL, STATIC , OTHER, grpc_lb_v0_InitialLoadBalanceResponse, client_stats_report_interval, load_balancer_delegate, &grpc_lb_v0_Duration_fields), + PB_LAST_FIELD +}; + +const pb_field_t grpc_lb_v0_ServerList_fields[3] = { + PB_FIELD( 1, MESSAGE , REPEATED, CALLBACK, FIRST, grpc_lb_v0_ServerList, servers, servers, &grpc_lb_v0_Server_fields), + PB_FIELD( 3, MESSAGE , OPTIONAL, STATIC , OTHER, grpc_lb_v0_ServerList, expiration_interval, servers, &grpc_lb_v0_Duration_fields), + PB_LAST_FIELD +}; + +const pb_field_t grpc_lb_v0_Server_fields[5] = { + PB_FIELD( 1, STRING , OPTIONAL, STATIC , FIRST, grpc_lb_v0_Server, ip_address, ip_address, 0), + PB_FIELD( 2, INT32 , OPTIONAL, STATIC , OTHER, grpc_lb_v0_Server, port, ip_address, 0), + PB_FIELD( 3, BYTES , OPTIONAL, STATIC , OTHER, grpc_lb_v0_Server, load_balance_token, port, 0), + PB_FIELD( 4, BOOL , OPTIONAL, STATIC , OTHER, grpc_lb_v0_Server, drop_request, load_balance_token, 0), + PB_LAST_FIELD +}; + + +/* Check that field information fits in pb_field_t */ +#if !defined(PB_FIELD_32BIT) +/* If you get an error here, it means that you need to define PB_FIELD_32BIT + * compile-time option. You can do that in pb.h or on compiler command line. + * + * The reason you need to do this is that some of your messages contain tag + * numbers or field sizes that are larger than what can fit in 8 or 16 bit + * field descriptors. + */ +PB_STATIC_ASSERT((pb_membersize(grpc_lb_v0_LoadBalanceRequest, initial_request) < 65536 && pb_membersize(grpc_lb_v0_LoadBalanceRequest, client_stats) < 65536 && pb_membersize(grpc_lb_v0_LoadBalanceResponse, initial_response) < 65536 && pb_membersize(grpc_lb_v0_LoadBalanceResponse, server_list) < 65536 && pb_membersize(grpc_lb_v0_InitialLoadBalanceResponse, client_stats_report_interval) < 65536 && pb_membersize(grpc_lb_v0_ServerList, servers) < 65536 && pb_membersize(grpc_lb_v0_ServerList, expiration_interval) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_grpc_lb_v0_Duration_grpc_lb_v0_LoadBalanceRequest_grpc_lb_v0_InitialLoadBalanceRequest_grpc_lb_v0_ClientStats_grpc_lb_v0_LoadBalanceResponse_grpc_lb_v0_InitialLoadBalanceResponse_grpc_lb_v0_ServerList_grpc_lb_v0_Server) +#endif + +#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT) +/* If you get an error here, it means that you need to define PB_FIELD_16BIT + * compile-time option. You can do that in pb.h or on compiler command line. + * + * The reason you need to do this is that some of your messages contain tag + * numbers or field sizes that are larger than what can fit in the default + * 8 bit descriptors. + */ +PB_STATIC_ASSERT((pb_membersize(grpc_lb_v0_LoadBalanceRequest, initial_request) < 256 && pb_membersize(grpc_lb_v0_LoadBalanceRequest, client_stats) < 256 && pb_membersize(grpc_lb_v0_LoadBalanceResponse, initial_response) < 256 && pb_membersize(grpc_lb_v0_LoadBalanceResponse, server_list) < 256 && pb_membersize(grpc_lb_v0_InitialLoadBalanceResponse, client_stats_report_interval) < 256 && pb_membersize(grpc_lb_v0_ServerList, servers) < 256 && pb_membersize(grpc_lb_v0_ServerList, expiration_interval) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_grpc_lb_v0_Duration_grpc_lb_v0_LoadBalanceRequest_grpc_lb_v0_InitialLoadBalanceRequest_grpc_lb_v0_ClientStats_grpc_lb_v0_LoadBalanceResponse_grpc_lb_v0_InitialLoadBalanceResponse_grpc_lb_v0_ServerList_grpc_lb_v0_Server) +#endif + + diff --git a/src/core/lib/proto/grpc/lb/v0/load_balancer.pb.h b/src/core/lib/proto/grpc/lb/v0/load_balancer.pb.h new file mode 100644 index 0000000000..3599f881bb --- /dev/null +++ b/src/core/lib/proto/grpc/lb/v0/load_balancer.pb.h @@ -0,0 +1,182 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.3.5-dev */ + +#ifndef PB_LOAD_BALANCER_PB_H_INCLUDED +#define PB_LOAD_BALANCER_PB_H_INCLUDED +#include "third_party/nanopb/pb.h" +#if PB_PROTO_HEADER_VERSION != 30 +#error Regenerate this file with the current version of nanopb generator. +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Struct definitions */ +typedef struct _grpc_lb_v0_ClientStats { + bool has_total_requests; + int64_t total_requests; + bool has_client_rpc_errors; + int64_t client_rpc_errors; + bool has_dropped_requests; + int64_t dropped_requests; +} grpc_lb_v0_ClientStats; + +typedef struct _grpc_lb_v0_Duration { + bool has_seconds; + int64_t seconds; + bool has_nanos; + int32_t nanos; +} grpc_lb_v0_Duration; + +typedef struct _grpc_lb_v0_InitialLoadBalanceRequest { + bool has_name; + char name[128]; +} grpc_lb_v0_InitialLoadBalanceRequest; + +typedef PB_BYTES_ARRAY_T(64) grpc_lb_v0_Server_load_balance_token_t; +typedef struct _grpc_lb_v0_Server { + bool has_ip_address; + char ip_address[46]; + bool has_port; + int32_t port; + bool has_load_balance_token; + grpc_lb_v0_Server_load_balance_token_t load_balance_token; + bool has_drop_request; + bool drop_request; +} grpc_lb_v0_Server; + +typedef struct _grpc_lb_v0_InitialLoadBalanceResponse { + bool has_client_config; + char client_config[64]; + bool has_load_balancer_delegate; + char load_balancer_delegate[64]; + bool has_client_stats_report_interval; + grpc_lb_v0_Duration client_stats_report_interval; +} grpc_lb_v0_InitialLoadBalanceResponse; + +typedef struct _grpc_lb_v0_LoadBalanceRequest { + bool has_initial_request; + grpc_lb_v0_InitialLoadBalanceRequest initial_request; + bool has_client_stats; + grpc_lb_v0_ClientStats client_stats; +} grpc_lb_v0_LoadBalanceRequest; + +typedef struct _grpc_lb_v0_ServerList { + pb_callback_t servers; + bool has_expiration_interval; + grpc_lb_v0_Duration expiration_interval; +} grpc_lb_v0_ServerList; + +typedef struct _grpc_lb_v0_LoadBalanceResponse { + bool has_initial_response; + grpc_lb_v0_InitialLoadBalanceResponse initial_response; + bool has_server_list; + grpc_lb_v0_ServerList server_list; +} grpc_lb_v0_LoadBalanceResponse; + +/* Default values for struct fields */ + +/* Initializer values for message structs */ +#define grpc_lb_v0_Duration_init_default {false, 0, false, 0} +#define grpc_lb_v0_LoadBalanceRequest_init_default {false, grpc_lb_v0_InitialLoadBalanceRequest_init_default, false, grpc_lb_v0_ClientStats_init_default} +#define grpc_lb_v0_InitialLoadBalanceRequest_init_default {false, ""} +#define grpc_lb_v0_ClientStats_init_default {false, 0, false, 0, false, 0} +#define grpc_lb_v0_LoadBalanceResponse_init_default {false, grpc_lb_v0_InitialLoadBalanceResponse_init_default, false, grpc_lb_v0_ServerList_init_default} +#define grpc_lb_v0_InitialLoadBalanceResponse_init_default {false, "", false, "", false, grpc_lb_v0_Duration_init_default} +#define grpc_lb_v0_ServerList_init_default {{{NULL}, NULL}, false, grpc_lb_v0_Duration_init_default} +#define grpc_lb_v0_Server_init_default {false, "", false, 0, false, {0, {0}}, false, 0} +#define grpc_lb_v0_Duration_init_zero {false, 0, false, 0} +#define grpc_lb_v0_LoadBalanceRequest_init_zero {false, grpc_lb_v0_InitialLoadBalanceRequest_init_zero, false, grpc_lb_v0_ClientStats_init_zero} +#define grpc_lb_v0_InitialLoadBalanceRequest_init_zero {false, ""} +#define grpc_lb_v0_ClientStats_init_zero {false, 0, false, 0, false, 0} +#define grpc_lb_v0_LoadBalanceResponse_init_zero {false, grpc_lb_v0_InitialLoadBalanceResponse_init_zero, false, grpc_lb_v0_ServerList_init_zero} +#define grpc_lb_v0_InitialLoadBalanceResponse_init_zero {false, "", false, "", false, grpc_lb_v0_Duration_init_zero} +#define grpc_lb_v0_ServerList_init_zero {{{NULL}, NULL}, false, grpc_lb_v0_Duration_init_zero} +#define grpc_lb_v0_Server_init_zero {false, "", false, 0, false, {0, {0}}, false, 0} + +/* Field tags (for use in manual encoding/decoding) */ +#define grpc_lb_v0_ClientStats_total_requests_tag 1 +#define grpc_lb_v0_ClientStats_client_rpc_errors_tag 2 +#define grpc_lb_v0_ClientStats_dropped_requests_tag 3 +#define grpc_lb_v0_Duration_seconds_tag 1 +#define grpc_lb_v0_Duration_nanos_tag 2 +#define grpc_lb_v0_InitialLoadBalanceRequest_name_tag 1 +#define grpc_lb_v0_Server_ip_address_tag 1 +#define grpc_lb_v0_Server_port_tag 2 +#define grpc_lb_v0_Server_load_balance_token_tag 3 +#define grpc_lb_v0_Server_drop_request_tag 4 +#define grpc_lb_v0_InitialLoadBalanceResponse_client_config_tag 1 +#define grpc_lb_v0_InitialLoadBalanceResponse_load_balancer_delegate_tag 2 +#define grpc_lb_v0_InitialLoadBalanceResponse_client_stats_report_interval_tag 3 +#define grpc_lb_v0_LoadBalanceRequest_initial_request_tag 1 +#define grpc_lb_v0_LoadBalanceRequest_client_stats_tag 2 +#define grpc_lb_v0_ServerList_servers_tag 1 +#define grpc_lb_v0_ServerList_expiration_interval_tag 3 +#define grpc_lb_v0_LoadBalanceResponse_initial_response_tag 1 +#define grpc_lb_v0_LoadBalanceResponse_server_list_tag 2 + +/* Struct field encoding specification for nanopb */ +extern const pb_field_t grpc_lb_v0_Duration_fields[3]; +extern const pb_field_t grpc_lb_v0_LoadBalanceRequest_fields[3]; +extern const pb_field_t grpc_lb_v0_InitialLoadBalanceRequest_fields[2]; +extern const pb_field_t grpc_lb_v0_ClientStats_fields[4]; +extern const pb_field_t grpc_lb_v0_LoadBalanceResponse_fields[3]; +extern const pb_field_t grpc_lb_v0_InitialLoadBalanceResponse_fields[4]; +extern const pb_field_t grpc_lb_v0_ServerList_fields[3]; +extern const pb_field_t grpc_lb_v0_Server_fields[5]; + +/* Maximum encoded size of messages (where known) */ +#define grpc_lb_v0_Duration_size 22 +#define grpc_lb_v0_LoadBalanceRequest_size 169 +#define grpc_lb_v0_InitialLoadBalanceRequest_size 131 +#define grpc_lb_v0_ClientStats_size 33 +#define grpc_lb_v0_LoadBalanceResponse_size (165 + grpc_lb_v0_ServerList_size) +#define grpc_lb_v0_InitialLoadBalanceResponse_size 156 +#define grpc_lb_v0_Server_size 127 + +/* Message IDs (where set with "msgid" option) */ +#ifdef PB_MSGID + +#define LOAD_BALANCER_MESSAGES \ + + +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/core/lib/security/auth_filters.h b/src/core/lib/security/auth_filters.h new file mode 100644 index 0000000000..162b60e2c8 --- /dev/null +++ b/src/core/lib/security/auth_filters.h @@ -0,0 +1,42 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SECURITY_AUTH_FILTERS_H +#define GRPC_CORE_LIB_SECURITY_AUTH_FILTERS_H + +#include "src/core/lib/channel/channel_stack.h" + +extern const grpc_channel_filter grpc_client_auth_filter; +extern const grpc_channel_filter grpc_server_auth_filter; + +#endif /* GRPC_CORE_LIB_SECURITY_AUTH_FILTERS_H */ diff --git a/src/core/lib/security/b64.c b/src/core/lib/security/b64.c new file mode 100644 index 0000000000..1d3879534c --- /dev/null +++ b/src/core/lib/security/b64.c @@ -0,0 +1,233 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/security/b64.h" + +#include <stdint.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/useful.h> + +/* --- Constants. --- */ + +static const int8_t base64_bytes[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 0x3E, -1, -1, -1, 0x3F, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, -1, -1, + -1, 0x7F, -1, -1, -1, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, -1, -1, -1, -1, -1, + -1, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, + 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, + 0x31, 0x32, 0x33, -1, -1, -1, -1, -1}; + +static const char base64_url_unsafe_chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char base64_url_safe_chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +#define GRPC_BASE64_PAD_CHAR '=' +#define GRPC_BASE64_PAD_BYTE 0x7F +#define GRPC_BASE64_MULTILINE_LINE_LEN 76 +#define GRPC_BASE64_MULTILINE_NUM_BLOCKS (GRPC_BASE64_MULTILINE_LINE_LEN / 4) + +/* --- base64 functions. --- */ + +char *grpc_base64_encode(const void *vdata, size_t data_size, int url_safe, + int multiline) { + const unsigned char *data = vdata; + const char *base64_chars = + url_safe ? base64_url_safe_chars : base64_url_unsafe_chars; + size_t result_projected_size = + 4 * ((data_size + 3) / 3) + + 2 * (multiline ? (data_size / (3 * GRPC_BASE64_MULTILINE_NUM_BLOCKS)) + : 0) + + 1; + char *result = gpr_malloc(result_projected_size); + char *current = result; + size_t num_blocks = 0; + size_t i = 0; + + /* Encode each block. */ + while (data_size >= 3) { + *current++ = base64_chars[(data[i] >> 2) & 0x3F]; + *current++ = + base64_chars[((data[i] & 0x03) << 4) | ((data[i + 1] >> 4) & 0x0F)]; + *current++ = + base64_chars[((data[i + 1] & 0x0F) << 2) | ((data[i + 2] >> 6) & 0x03)]; + *current++ = base64_chars[data[i + 2] & 0x3F]; + + data_size -= 3; + i += 3; + if (multiline && (++num_blocks == GRPC_BASE64_MULTILINE_NUM_BLOCKS)) { + *current++ = '\r'; + *current++ = '\n'; + num_blocks = 0; + } + } + + /* Take care of the tail. */ + if (data_size == 2) { + *current++ = base64_chars[(data[i] >> 2) & 0x3F]; + *current++ = + base64_chars[((data[i] & 0x03) << 4) | ((data[i + 1] >> 4) & 0x0F)]; + *current++ = base64_chars[(data[i + 1] & 0x0F) << 2]; + *current++ = GRPC_BASE64_PAD_CHAR; + } else if (data_size == 1) { + *current++ = base64_chars[(data[i] >> 2) & 0x3F]; + *current++ = base64_chars[(data[i] & 0x03) << 4]; + *current++ = GRPC_BASE64_PAD_CHAR; + *current++ = GRPC_BASE64_PAD_CHAR; + } + + GPR_ASSERT(current >= result); + GPR_ASSERT((uintptr_t)(current - result) < result_projected_size); + result[current - result] = '\0'; + return result; +} + +gpr_slice grpc_base64_decode(const char *b64, int url_safe) { + return grpc_base64_decode_with_len(b64, strlen(b64), url_safe); +} + +static void decode_one_char(const unsigned char *codes, unsigned char *result, + size_t *result_offset) { + uint32_t packed = ((uint32_t)codes[0] << 2) | ((uint32_t)codes[1] >> 4); + result[(*result_offset)++] = (unsigned char)packed; +} + +static void decode_two_chars(const unsigned char *codes, unsigned char *result, + size_t *result_offset) { + uint32_t packed = ((uint32_t)codes[0] << 10) | ((uint32_t)codes[1] << 4) | + ((uint32_t)codes[2] >> 2); + result[(*result_offset)++] = (unsigned char)(packed >> 8); + result[(*result_offset)++] = (unsigned char)(packed); +} + +static int decode_group(const unsigned char *codes, size_t num_codes, + unsigned char *result, size_t *result_offset) { + GPR_ASSERT(num_codes <= 4); + + /* Short end groups that may not have padding. */ + if (num_codes == 1) { + gpr_log(GPR_ERROR, "Invalid group. Must be at least 2 bytes."); + return 0; + } + if (num_codes == 2) { + decode_one_char(codes, result, result_offset); + return 1; + } + if (num_codes == 3) { + decode_two_chars(codes, result, result_offset); + return 1; + } + + /* Regular 4 byte groups with padding or not. */ + GPR_ASSERT(num_codes == 4); + if (codes[0] == GRPC_BASE64_PAD_BYTE || codes[1] == GRPC_BASE64_PAD_BYTE) { + gpr_log(GPR_ERROR, "Invalid padding detected."); + return 0; + } + if (codes[2] == GRPC_BASE64_PAD_BYTE) { + if (codes[3] == GRPC_BASE64_PAD_BYTE) { + decode_one_char(codes, result, result_offset); + } else { + gpr_log(GPR_ERROR, "Invalid padding detected."); + return 0; + } + } else if (codes[3] == GRPC_BASE64_PAD_BYTE) { + decode_two_chars(codes, result, result_offset); + } else { + /* No padding. */ + uint32_t packed = ((uint32_t)codes[0] << 18) | ((uint32_t)codes[1] << 12) | + ((uint32_t)codes[2] << 6) | codes[3]; + result[(*result_offset)++] = (unsigned char)(packed >> 16); + result[(*result_offset)++] = (unsigned char)(packed >> 8); + result[(*result_offset)++] = (unsigned char)(packed); + } + return 1; +} + +gpr_slice grpc_base64_decode_with_len(const char *b64, size_t b64_len, + int url_safe) { + gpr_slice result = gpr_slice_malloc(b64_len); + unsigned char *current = GPR_SLICE_START_PTR(result); + size_t result_size = 0; + unsigned char codes[4]; + size_t num_codes = 0; + + while (b64_len--) { + unsigned char c = (unsigned char)(*b64++); + signed char code; + if (c >= GPR_ARRAY_SIZE(base64_bytes)) continue; + if (url_safe) { + if (c == '+' || c == '/') { + gpr_log(GPR_ERROR, "Invalid character for url safe base64 %c", c); + goto fail; + } + if (c == '-') { + c = '+'; + } else if (c == '_') { + c = '/'; + } + } + code = base64_bytes[c]; + if (code == -1) { + if (c != '\r' && c != '\n') { + gpr_log(GPR_ERROR, "Invalid character %c", c); + goto fail; + } + } else { + codes[num_codes++] = (unsigned char)code; + if (num_codes == 4) { + if (!decode_group(codes, num_codes, current, &result_size)) goto fail; + num_codes = 0; + } + } + } + + if (num_codes != 0 && + !decode_group(codes, num_codes, current, &result_size)) { + goto fail; + } + GPR_SLICE_SET_LENGTH(result, result_size); + return result; + +fail: + gpr_slice_unref(result); + return gpr_empty_slice(); +} diff --git a/src/core/lib/security/b64.h b/src/core/lib/security/b64.h new file mode 100644 index 0000000000..0bf372a1e7 --- /dev/null +++ b/src/core/lib/security/b64.h @@ -0,0 +1,52 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SECURITY_B64_H +#define GRPC_CORE_LIB_SECURITY_B64_H + +#include <grpc/support/slice.h> + +/* Encodes data using base64. It is the caller's responsability to free + the returned char * using gpr_free. Returns NULL on NULL input. */ +char *grpc_base64_encode(const void *data, size_t data_size, int url_safe, + int multiline); + +/* Decodes data according to the base64 specification. Returns an empty + slice in case of failure. */ +gpr_slice grpc_base64_decode(const char *b64, int url_safe); + +/* Same as above except that the length is provided by the caller. */ +gpr_slice grpc_base64_decode_with_len(const char *b64, size_t b64_len, + int url_safe); + +#endif /* GRPC_CORE_LIB_SECURITY_B64_H */ diff --git a/src/core/lib/security/client_auth_filter.c b/src/core/lib/security/client_auth_filter.c new file mode 100644 index 0000000000..b9e5bf0339 --- /dev/null +++ b/src/core/lib/security/client_auth_filter.c @@ -0,0 +1,336 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/security/auth_filters.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> + +#include "src/core/lib/channel/channel_stack.h" +#include "src/core/lib/security/credentials.h" +#include "src/core/lib/security/security_connector.h" +#include "src/core/lib/security/security_context.h" +#include "src/core/lib/support/string.h" +#include "src/core/lib/surface/call.h" +#include "src/core/lib/transport/static_metadata.h" + +#define MAX_CREDENTIALS_METADATA_COUNT 4 + +/* We can have a per-call credentials. */ +typedef struct { + grpc_call_credentials *creds; + grpc_mdstr *host; + grpc_mdstr *method; + /* pollset bound to this call; if we need to make external + network requests, they should be done under this pollset + so that work can progress when this call wants work to + progress */ + grpc_pollset *pollset; + grpc_transport_stream_op op; + uint8_t security_context_set; + grpc_linked_mdelem md_links[MAX_CREDENTIALS_METADATA_COUNT]; + grpc_auth_metadata_context auth_md_context; +} call_data; + +/* We can have a per-channel credentials. */ +typedef struct { + grpc_channel_security_connector *security_connector; + grpc_auth_context *auth_context; +} channel_data; + +static void reset_auth_metadata_context( + grpc_auth_metadata_context *auth_md_context) { + if (auth_md_context->service_url != NULL) { + gpr_free((char *)auth_md_context->service_url); + auth_md_context->service_url = NULL; + } + if (auth_md_context->method_name != NULL) { + gpr_free((char *)auth_md_context->method_name); + auth_md_context->method_name = NULL; + } + GRPC_AUTH_CONTEXT_UNREF( + (grpc_auth_context *)auth_md_context->channel_auth_context, + "grpc_auth_metadata_context"); + auth_md_context->channel_auth_context = NULL; +} + +static void bubble_up_error(grpc_exec_ctx *exec_ctx, grpc_call_element *elem, + grpc_status_code status, const char *error_msg) { + call_data *calld = elem->call_data; + gpr_log(GPR_ERROR, "Client side authentication failure: %s", error_msg); + grpc_transport_stream_op_add_cancellation(&calld->op, status); + grpc_call_next_op(exec_ctx, elem, &calld->op); +} + +static void on_credentials_metadata(grpc_exec_ctx *exec_ctx, void *user_data, + grpc_credentials_md *md_elems, + size_t num_md, + grpc_credentials_status status) { + grpc_call_element *elem = (grpc_call_element *)user_data; + call_data *calld = elem->call_data; + grpc_transport_stream_op *op = &calld->op; + grpc_metadata_batch *mdb; + size_t i; + reset_auth_metadata_context(&calld->auth_md_context); + if (status != GRPC_CREDENTIALS_OK) { + bubble_up_error(exec_ctx, elem, GRPC_STATUS_UNAUTHENTICATED, + "Credentials failed to get metadata."); + return; + } + GPR_ASSERT(num_md <= MAX_CREDENTIALS_METADATA_COUNT); + GPR_ASSERT(op->send_initial_metadata != NULL); + mdb = op->send_initial_metadata; + for (i = 0; i < num_md; i++) { + grpc_metadata_batch_add_tail( + mdb, &calld->md_links[i], + grpc_mdelem_from_slices(gpr_slice_ref(md_elems[i].key), + gpr_slice_ref(md_elems[i].value))); + } + grpc_call_next_op(exec_ctx, elem, op); +} + +void build_auth_metadata_context(grpc_security_connector *sc, + grpc_auth_context *auth_context, + call_data *calld) { + char *service = gpr_strdup(grpc_mdstr_as_c_string(calld->method)); + char *last_slash = strrchr(service, '/'); + char *method_name = NULL; + char *service_url = NULL; + reset_auth_metadata_context(&calld->auth_md_context); + if (last_slash == NULL) { + gpr_log(GPR_ERROR, "No '/' found in fully qualified method name"); + service[0] = '\0'; + } else if (last_slash == service) { + /* No service part in fully qualified method name: will just be "/". */ + service[1] = '\0'; + } else { + *last_slash = '\0'; + method_name = gpr_strdup(last_slash + 1); + } + if (method_name == NULL) method_name = gpr_strdup(""); + gpr_asprintf(&service_url, "%s://%s%s", + sc->url_scheme == NULL ? "" : sc->url_scheme, + grpc_mdstr_as_c_string(calld->host), service); + calld->auth_md_context.service_url = service_url; + calld->auth_md_context.method_name = method_name; + calld->auth_md_context.channel_auth_context = + GRPC_AUTH_CONTEXT_REF(auth_context, "grpc_auth_metadata_context"); + gpr_free(service); +} + +static void send_security_metadata(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem, + grpc_transport_stream_op *op) { + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + grpc_client_security_context *ctx = + (grpc_client_security_context *)op->context[GRPC_CONTEXT_SECURITY].value; + grpc_call_credentials *channel_call_creds = + chand->security_connector->request_metadata_creds; + int call_creds_has_md = (ctx != NULL) && (ctx->creds != NULL); + + if (channel_call_creds == NULL && !call_creds_has_md) { + /* Skip sending metadata altogether. */ + grpc_call_next_op(exec_ctx, elem, op); + return; + } + + if (channel_call_creds != NULL && call_creds_has_md) { + calld->creds = grpc_composite_call_credentials_create(channel_call_creds, + ctx->creds, NULL); + if (calld->creds == NULL) { + bubble_up_error(exec_ctx, elem, GRPC_STATUS_INVALID_ARGUMENT, + "Incompatible credentials set on channel and call."); + return; + } + } else { + calld->creds = grpc_call_credentials_ref( + call_creds_has_md ? ctx->creds : channel_call_creds); + } + + build_auth_metadata_context(&chand->security_connector->base, + chand->auth_context, calld); + calld->op = *op; /* Copy op (originates from the caller's stack). */ + GPR_ASSERT(calld->pollset); + grpc_call_credentials_get_request_metadata( + exec_ctx, calld->creds, calld->pollset, calld->auth_md_context, + on_credentials_metadata, elem); +} + +static void on_host_checked(grpc_exec_ctx *exec_ctx, void *user_data, + grpc_security_status status) { + grpc_call_element *elem = (grpc_call_element *)user_data; + call_data *calld = elem->call_data; + + if (status == GRPC_SECURITY_OK) { + send_security_metadata(exec_ctx, elem, &calld->op); + } else { + char *error_msg; + gpr_asprintf(&error_msg, "Invalid host %s set in :authority metadata.", + grpc_mdstr_as_c_string(calld->host)); + bubble_up_error(exec_ctx, elem, GRPC_STATUS_INVALID_ARGUMENT, error_msg); + gpr_free(error_msg); + } +} + +/* Called either: + - in response to an API call (or similar) from above, to send something + - a network event (or similar) from below, to receive something + op contains type and call direction information, in addition to the data + that is being sent or received. */ +static void auth_start_transport_op(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem, + grpc_transport_stream_op *op) { + /* grab pointers to our data from the call element */ + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + grpc_linked_mdelem *l; + grpc_client_security_context *sec_ctx = NULL; + + if (calld->security_context_set == 0 && + op->cancel_with_status == GRPC_STATUS_OK) { + calld->security_context_set = 1; + GPR_ASSERT(op->context); + if (op->context[GRPC_CONTEXT_SECURITY].value == NULL) { + op->context[GRPC_CONTEXT_SECURITY].value = + grpc_client_security_context_create(); + op->context[GRPC_CONTEXT_SECURITY].destroy = + grpc_client_security_context_destroy; + } + sec_ctx = op->context[GRPC_CONTEXT_SECURITY].value; + GRPC_AUTH_CONTEXT_UNREF(sec_ctx->auth_context, "client auth filter"); + sec_ctx->auth_context = + GRPC_AUTH_CONTEXT_REF(chand->auth_context, "client_auth_filter"); + } + + if (op->send_initial_metadata != NULL) { + for (l = op->send_initial_metadata->list.head; l != NULL; l = l->next) { + grpc_mdelem *md = l->md; + /* Pointer comparison is OK for md_elems created from the same context. + */ + if (md->key == GRPC_MDSTR_AUTHORITY) { + if (calld->host != NULL) GRPC_MDSTR_UNREF(calld->host); + calld->host = GRPC_MDSTR_REF(md->value); + } else if (md->key == GRPC_MDSTR_PATH) { + if (calld->method != NULL) GRPC_MDSTR_UNREF(calld->method); + calld->method = GRPC_MDSTR_REF(md->value); + } + } + if (calld->host != NULL) { + const char *call_host = grpc_mdstr_as_c_string(calld->host); + calld->op = *op; /* Copy op (originates from the caller's stack). */ + grpc_channel_security_connector_check_call_host( + exec_ctx, chand->security_connector, call_host, chand->auth_context, + on_host_checked, elem); + return; /* early exit */ + } + } + + /* pass control down the stack */ + grpc_call_next_op(exec_ctx, elem, op); +} + +/* Constructor for call_data */ +static void init_call_elem(grpc_exec_ctx *exec_ctx, grpc_call_element *elem, + grpc_call_element_args *args) { + call_data *calld = elem->call_data; + memset(calld, 0, sizeof(*calld)); +} + +static void set_pollset(grpc_exec_ctx *exec_ctx, grpc_call_element *elem, + grpc_pollset *pollset) { + call_data *calld = elem->call_data; + calld->pollset = pollset; +} + +/* Destructor for call_data */ +static void destroy_call_elem(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem) { + call_data *calld = elem->call_data; + grpc_call_credentials_unref(calld->creds); + if (calld->host != NULL) { + GRPC_MDSTR_UNREF(calld->host); + } + if (calld->method != NULL) { + GRPC_MDSTR_UNREF(calld->method); + } + reset_auth_metadata_context(&calld->auth_md_context); +} + +/* Constructor for channel_data */ +static void init_channel_elem(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem, + grpc_channel_element_args *args) { + grpc_security_connector *sc = + grpc_find_security_connector_in_args(args->channel_args); + grpc_auth_context *auth_context = + grpc_find_auth_context_in_args(args->channel_args); + + /* grab pointers to our data from the channel element */ + channel_data *chand = elem->channel_data; + + /* The first and the last filters tend to be implemented differently to + handle the case that there's no 'next' filter to call on the up or down + path */ + GPR_ASSERT(!args->is_last); + GPR_ASSERT(sc != NULL); + GPR_ASSERT(auth_context != NULL); + + /* initialize members */ + chand->security_connector = + (grpc_channel_security_connector *)GRPC_SECURITY_CONNECTOR_REF( + sc, "client_auth_filter"); + chand->auth_context = + GRPC_AUTH_CONTEXT_REF(auth_context, "client_auth_filter"); +} + +/* Destructor for channel data */ +static void destroy_channel_elem(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem) { + /* grab pointers to our data from the channel element */ + channel_data *chand = elem->channel_data; + grpc_channel_security_connector *sc = chand->security_connector; + if (sc != NULL) { + GRPC_SECURITY_CONNECTOR_UNREF(&sc->base, "client_auth_filter"); + } + GRPC_AUTH_CONTEXT_UNREF(chand->auth_context, "client_auth_filter"); +} + +const grpc_channel_filter grpc_client_auth_filter = { + auth_start_transport_op, grpc_channel_next_op, sizeof(call_data), + init_call_elem, set_pollset, destroy_call_elem, + sizeof(channel_data), init_channel_elem, destroy_channel_elem, + grpc_call_next_get_peer, "client-auth"}; diff --git a/src/core/lib/security/credentials.c b/src/core/lib/security/credentials.c new file mode 100644 index 0000000000..99a07e5c13 --- /dev/null +++ b/src/core/lib/security/credentials.c @@ -0,0 +1,1281 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/security/credentials.h" + +#include <stdio.h> +#include <string.h> + +#include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/channel/http_client_filter.h" +#include "src/core/lib/http/httpcli.h" +#include "src/core/lib/http/parser.h" +#include "src/core/lib/iomgr/executor.h" +#include "src/core/lib/json/json.h" +#include "src/core/lib/support/string.h" +#include "src/core/lib/surface/api_trace.h" + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> +#include <grpc/support/sync.h> +#include <grpc/support/time.h> + +/* -- Common. -- */ + +struct grpc_credentials_metadata_request { + grpc_call_credentials *creds; + grpc_credentials_metadata_cb cb; + void *user_data; +}; + +static grpc_credentials_metadata_request * +grpc_credentials_metadata_request_create(grpc_call_credentials *creds, + grpc_credentials_metadata_cb cb, + void *user_data) { + grpc_credentials_metadata_request *r = + gpr_malloc(sizeof(grpc_credentials_metadata_request)); + r->creds = grpc_call_credentials_ref(creds); + r->cb = cb; + r->user_data = user_data; + return r; +} + +static void grpc_credentials_metadata_request_destroy( + grpc_credentials_metadata_request *r) { + grpc_call_credentials_unref(r->creds); + gpr_free(r); +} + +grpc_channel_credentials *grpc_channel_credentials_ref( + grpc_channel_credentials *creds) { + if (creds == NULL) return NULL; + gpr_ref(&creds->refcount); + return creds; +} + +void grpc_channel_credentials_unref(grpc_channel_credentials *creds) { + if (creds == NULL) return; + if (gpr_unref(&creds->refcount)) { + if (creds->vtable->destruct != NULL) creds->vtable->destruct(creds); + gpr_free(creds); + } +} + +void grpc_channel_credentials_release(grpc_channel_credentials *creds) { + GRPC_API_TRACE("grpc_channel_credentials_release(creds=%p)", 1, (creds)); + grpc_channel_credentials_unref(creds); +} + +grpc_call_credentials *grpc_call_credentials_ref(grpc_call_credentials *creds) { + if (creds == NULL) return NULL; + gpr_ref(&creds->refcount); + return creds; +} + +void grpc_call_credentials_unref(grpc_call_credentials *creds) { + if (creds == NULL) return; + if (gpr_unref(&creds->refcount)) { + if (creds->vtable->destruct != NULL) creds->vtable->destruct(creds); + gpr_free(creds); + } +} + +void grpc_call_credentials_release(grpc_call_credentials *creds) { + GRPC_API_TRACE("grpc_call_credentials_release(creds=%p)", 1, (creds)); + grpc_call_credentials_unref(creds); +} + +void grpc_call_credentials_get_request_metadata( + grpc_exec_ctx *exec_ctx, grpc_call_credentials *creds, + grpc_pollset *pollset, grpc_auth_metadata_context context, + grpc_credentials_metadata_cb cb, void *user_data) { + if (creds == NULL || creds->vtable->get_request_metadata == NULL) { + if (cb != NULL) { + cb(exec_ctx, user_data, NULL, 0, GRPC_CREDENTIALS_OK); + } + return; + } + creds->vtable->get_request_metadata(exec_ctx, creds, pollset, context, cb, + user_data); +} + +grpc_security_status grpc_channel_credentials_create_security_connector( + grpc_channel_credentials *channel_creds, const char *target, + const grpc_channel_args *args, grpc_channel_security_connector **sc, + grpc_channel_args **new_args) { + *new_args = NULL; + if (channel_creds == NULL) { + return GRPC_SECURITY_ERROR; + } + GPR_ASSERT(channel_creds->vtable->create_security_connector != NULL); + return channel_creds->vtable->create_security_connector( + channel_creds, NULL, target, args, sc, new_args); +} + +grpc_server_credentials *grpc_server_credentials_ref( + grpc_server_credentials *creds) { + if (creds == NULL) return NULL; + gpr_ref(&creds->refcount); + return creds; +} + +void grpc_server_credentials_unref(grpc_server_credentials *creds) { + if (creds == NULL) return; + if (gpr_unref(&creds->refcount)) { + if (creds->vtable->destruct != NULL) creds->vtable->destruct(creds); + if (creds->processor.destroy != NULL && creds->processor.state != NULL) { + creds->processor.destroy(creds->processor.state); + } + gpr_free(creds); + } +} + +void grpc_server_credentials_release(grpc_server_credentials *creds) { + GRPC_API_TRACE("grpc_server_credentials_release(creds=%p)", 1, (creds)); + grpc_server_credentials_unref(creds); +} + +grpc_security_status grpc_server_credentials_create_security_connector( + grpc_server_credentials *creds, grpc_server_security_connector **sc) { + if (creds == NULL || creds->vtable->create_security_connector == NULL) { + gpr_log(GPR_ERROR, "Server credentials cannot create security context."); + return GRPC_SECURITY_ERROR; + } + return creds->vtable->create_security_connector(creds, sc); +} + +void grpc_server_credentials_set_auth_metadata_processor( + grpc_server_credentials *creds, grpc_auth_metadata_processor processor) { + GRPC_API_TRACE( + "grpc_server_credentials_set_auth_metadata_processor(" + "creds=%p, " + "processor=grpc_auth_metadata_processor { process: %p, state: %p })", + 3, (creds, (void *)(intptr_t)processor.process, processor.state)); + if (creds == NULL) return; + if (creds->processor.destroy != NULL && creds->processor.state != NULL) { + creds->processor.destroy(creds->processor.state); + } + creds->processor = processor; +} + +static void server_credentials_pointer_arg_destroy(void *p) { + grpc_server_credentials_unref(p); +} + +static void *server_credentials_pointer_arg_copy(void *p) { + return grpc_server_credentials_ref(p); +} + +static int server_credentials_pointer_cmp(void *a, void *b) { + return GPR_ICMP(a, b); +} + +static const grpc_arg_pointer_vtable cred_ptr_vtable = { + server_credentials_pointer_arg_copy, server_credentials_pointer_arg_destroy, + server_credentials_pointer_cmp}; + +grpc_arg grpc_server_credentials_to_arg(grpc_server_credentials *p) { + grpc_arg arg; + memset(&arg, 0, sizeof(grpc_arg)); + arg.type = GRPC_ARG_POINTER; + arg.key = GRPC_SERVER_CREDENTIALS_ARG; + arg.value.pointer.p = p; + arg.value.pointer.vtable = &cred_ptr_vtable; + return arg; +} + +grpc_server_credentials *grpc_server_credentials_from_arg(const grpc_arg *arg) { + if (strcmp(arg->key, GRPC_SERVER_CREDENTIALS_ARG) != 0) return NULL; + if (arg->type != GRPC_ARG_POINTER) { + gpr_log(GPR_ERROR, "Invalid type %d for arg %s", arg->type, + GRPC_SERVER_CREDENTIALS_ARG); + return NULL; + } + return arg->value.pointer.p; +} + +grpc_server_credentials *grpc_find_server_credentials_in_args( + const grpc_channel_args *args) { + size_t i; + if (args == NULL) return NULL; + for (i = 0; i < args->num_args; i++) { + grpc_server_credentials *p = + grpc_server_credentials_from_arg(&args->args[i]); + if (p != NULL) return p; + } + return NULL; +} + +/* -- Ssl credentials. -- */ + +static void ssl_destruct(grpc_channel_credentials *creds) { + grpc_ssl_credentials *c = (grpc_ssl_credentials *)creds; + if (c->config.pem_root_certs != NULL) gpr_free(c->config.pem_root_certs); + if (c->config.pem_private_key != NULL) gpr_free(c->config.pem_private_key); + if (c->config.pem_cert_chain != NULL) gpr_free(c->config.pem_cert_chain); +} + +static void ssl_server_destruct(grpc_server_credentials *creds) { + grpc_ssl_server_credentials *c = (grpc_ssl_server_credentials *)creds; + size_t i; + for (i = 0; i < c->config.num_key_cert_pairs; i++) { + if (c->config.pem_private_keys[i] != NULL) { + gpr_free(c->config.pem_private_keys[i]); + } + if (c->config.pem_cert_chains[i] != NULL) { + gpr_free(c->config.pem_cert_chains[i]); + } + } + if (c->config.pem_private_keys != NULL) gpr_free(c->config.pem_private_keys); + if (c->config.pem_private_keys_sizes != NULL) { + gpr_free(c->config.pem_private_keys_sizes); + } + if (c->config.pem_cert_chains != NULL) gpr_free(c->config.pem_cert_chains); + if (c->config.pem_cert_chains_sizes != NULL) { + gpr_free(c->config.pem_cert_chains_sizes); + } + if (c->config.pem_root_certs != NULL) gpr_free(c->config.pem_root_certs); +} + +static grpc_security_status ssl_create_security_connector( + grpc_channel_credentials *creds, grpc_call_credentials *call_creds, + const char *target, const grpc_channel_args *args, + grpc_channel_security_connector **sc, grpc_channel_args **new_args) { + grpc_ssl_credentials *c = (grpc_ssl_credentials *)creds; + grpc_security_status status = GRPC_SECURITY_OK; + size_t i = 0; + const char *overridden_target_name = NULL; + grpc_arg new_arg; + + for (i = 0; args && i < args->num_args; i++) { + grpc_arg *arg = &args->args[i]; + if (strcmp(arg->key, GRPC_SSL_TARGET_NAME_OVERRIDE_ARG) == 0 && + arg->type == GRPC_ARG_STRING) { + overridden_target_name = arg->value.string; + break; + } + } + status = grpc_ssl_channel_security_connector_create( + call_creds, &c->config, target, overridden_target_name, sc); + if (status != GRPC_SECURITY_OK) { + return status; + } + new_arg.type = GRPC_ARG_STRING; + new_arg.key = GRPC_ARG_HTTP2_SCHEME; + new_arg.value.string = "https"; + *new_args = grpc_channel_args_copy_and_add(args, &new_arg, 1); + return status; +} + +static grpc_security_status ssl_server_create_security_connector( + grpc_server_credentials *creds, grpc_server_security_connector **sc) { + grpc_ssl_server_credentials *c = (grpc_ssl_server_credentials *)creds; + return grpc_ssl_server_security_connector_create(&c->config, sc); +} + +static grpc_channel_credentials_vtable ssl_vtable = { + ssl_destruct, ssl_create_security_connector}; + +static grpc_server_credentials_vtable ssl_server_vtable = { + ssl_server_destruct, ssl_server_create_security_connector}; + +static void ssl_copy_key_material(const char *input, unsigned char **output, + size_t *output_size) { + *output_size = strlen(input); + *output = gpr_malloc(*output_size); + memcpy(*output, input, *output_size); +} + +static void ssl_build_config(const char *pem_root_certs, + grpc_ssl_pem_key_cert_pair *pem_key_cert_pair, + grpc_ssl_config *config) { + if (pem_root_certs != NULL) { + ssl_copy_key_material(pem_root_certs, &config->pem_root_certs, + &config->pem_root_certs_size); + } + if (pem_key_cert_pair != NULL) { + GPR_ASSERT(pem_key_cert_pair->private_key != NULL); + GPR_ASSERT(pem_key_cert_pair->cert_chain != NULL); + ssl_copy_key_material(pem_key_cert_pair->private_key, + &config->pem_private_key, + &config->pem_private_key_size); + ssl_copy_key_material(pem_key_cert_pair->cert_chain, + &config->pem_cert_chain, + &config->pem_cert_chain_size); + } +} + +static void ssl_build_server_config( + const char *pem_root_certs, grpc_ssl_pem_key_cert_pair *pem_key_cert_pairs, + size_t num_key_cert_pairs, int force_client_auth, + grpc_ssl_server_config *config) { + size_t i; + config->force_client_auth = force_client_auth; + if (pem_root_certs != NULL) { + ssl_copy_key_material(pem_root_certs, &config->pem_root_certs, + &config->pem_root_certs_size); + } + if (num_key_cert_pairs > 0) { + GPR_ASSERT(pem_key_cert_pairs != NULL); + config->pem_private_keys = + gpr_malloc(num_key_cert_pairs * sizeof(unsigned char *)); + config->pem_cert_chains = + gpr_malloc(num_key_cert_pairs * sizeof(unsigned char *)); + config->pem_private_keys_sizes = + gpr_malloc(num_key_cert_pairs * sizeof(size_t)); + config->pem_cert_chains_sizes = + gpr_malloc(num_key_cert_pairs * sizeof(size_t)); + } + config->num_key_cert_pairs = num_key_cert_pairs; + for (i = 0; i < num_key_cert_pairs; i++) { + GPR_ASSERT(pem_key_cert_pairs[i].private_key != NULL); + GPR_ASSERT(pem_key_cert_pairs[i].cert_chain != NULL); + ssl_copy_key_material(pem_key_cert_pairs[i].private_key, + &config->pem_private_keys[i], + &config->pem_private_keys_sizes[i]); + ssl_copy_key_material(pem_key_cert_pairs[i].cert_chain, + &config->pem_cert_chains[i], + &config->pem_cert_chains_sizes[i]); + } +} + +grpc_channel_credentials *grpc_ssl_credentials_create( + const char *pem_root_certs, grpc_ssl_pem_key_cert_pair *pem_key_cert_pair, + void *reserved) { + grpc_ssl_credentials *c = gpr_malloc(sizeof(grpc_ssl_credentials)); + GRPC_API_TRACE( + "grpc_ssl_credentials_create(pem_root_certs=%s, " + "pem_key_cert_pair=%p, " + "reserved=%p)", + 3, (pem_root_certs, pem_key_cert_pair, reserved)); + GPR_ASSERT(reserved == NULL); + memset(c, 0, sizeof(grpc_ssl_credentials)); + c->base.type = GRPC_CHANNEL_CREDENTIALS_TYPE_SSL; + c->base.vtable = &ssl_vtable; + gpr_ref_init(&c->base.refcount, 1); + ssl_build_config(pem_root_certs, pem_key_cert_pair, &c->config); + return &c->base; +} + +grpc_server_credentials *grpc_ssl_server_credentials_create( + const char *pem_root_certs, grpc_ssl_pem_key_cert_pair *pem_key_cert_pairs, + size_t num_key_cert_pairs, int force_client_auth, void *reserved) { + grpc_ssl_server_credentials *c = + gpr_malloc(sizeof(grpc_ssl_server_credentials)); + GRPC_API_TRACE( + "grpc_ssl_server_credentials_create(" + "pem_root_certs=%s, pem_key_cert_pairs=%p, num_key_cert_pairs=%lu, " + "force_client_auth=%d, reserved=%p)", + 5, (pem_root_certs, pem_key_cert_pairs, (unsigned long)num_key_cert_pairs, + force_client_auth, reserved)); + GPR_ASSERT(reserved == NULL); + memset(c, 0, sizeof(grpc_ssl_server_credentials)); + c->base.type = GRPC_CHANNEL_CREDENTIALS_TYPE_SSL; + gpr_ref_init(&c->base.refcount, 1); + c->base.vtable = &ssl_server_vtable; + ssl_build_server_config(pem_root_certs, pem_key_cert_pairs, + num_key_cert_pairs, force_client_auth, &c->config); + return &c->base; +} + +/* -- Jwt credentials -- */ + +static void jwt_reset_cache(grpc_service_account_jwt_access_credentials *c) { + if (c->cached.jwt_md != NULL) { + grpc_credentials_md_store_unref(c->cached.jwt_md); + c->cached.jwt_md = NULL; + } + if (c->cached.service_url != NULL) { + gpr_free(c->cached.service_url); + c->cached.service_url = NULL; + } + c->cached.jwt_expiration = gpr_inf_past(GPR_CLOCK_REALTIME); +} + +static void jwt_destruct(grpc_call_credentials *creds) { + grpc_service_account_jwt_access_credentials *c = + (grpc_service_account_jwt_access_credentials *)creds; + grpc_auth_json_key_destruct(&c->key); + jwt_reset_cache(c); + gpr_mu_destroy(&c->cache_mu); +} + +static void jwt_get_request_metadata(grpc_exec_ctx *exec_ctx, + grpc_call_credentials *creds, + grpc_pollset *pollset, + grpc_auth_metadata_context context, + grpc_credentials_metadata_cb cb, + void *user_data) { + grpc_service_account_jwt_access_credentials *c = + (grpc_service_account_jwt_access_credentials *)creds; + gpr_timespec refresh_threshold = gpr_time_from_seconds( + GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS, GPR_TIMESPAN); + + /* See if we can return a cached jwt. */ + grpc_credentials_md_store *jwt_md = NULL; + { + gpr_mu_lock(&c->cache_mu); + if (c->cached.service_url != NULL && + strcmp(c->cached.service_url, context.service_url) == 0 && + c->cached.jwt_md != NULL && + (gpr_time_cmp(gpr_time_sub(c->cached.jwt_expiration, + gpr_now(GPR_CLOCK_REALTIME)), + refresh_threshold) > 0)) { + jwt_md = grpc_credentials_md_store_ref(c->cached.jwt_md); + } + gpr_mu_unlock(&c->cache_mu); + } + + if (jwt_md == NULL) { + char *jwt = NULL; + /* Generate a new jwt. */ + gpr_mu_lock(&c->cache_mu); + jwt_reset_cache(c); + jwt = grpc_jwt_encode_and_sign(&c->key, context.service_url, + c->jwt_lifetime, NULL); + if (jwt != NULL) { + char *md_value; + gpr_asprintf(&md_value, "Bearer %s", jwt); + gpr_free(jwt); + c->cached.jwt_expiration = + gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), c->jwt_lifetime); + c->cached.service_url = gpr_strdup(context.service_url); + c->cached.jwt_md = grpc_credentials_md_store_create(1); + grpc_credentials_md_store_add_cstrings( + c->cached.jwt_md, GRPC_AUTHORIZATION_METADATA_KEY, md_value); + gpr_free(md_value); + jwt_md = grpc_credentials_md_store_ref(c->cached.jwt_md); + } + gpr_mu_unlock(&c->cache_mu); + } + + if (jwt_md != NULL) { + cb(exec_ctx, user_data, jwt_md->entries, jwt_md->num_entries, + GRPC_CREDENTIALS_OK); + grpc_credentials_md_store_unref(jwt_md); + } else { + cb(exec_ctx, user_data, NULL, 0, GRPC_CREDENTIALS_ERROR); + } +} + +static grpc_call_credentials_vtable jwt_vtable = {jwt_destruct, + jwt_get_request_metadata}; + +grpc_call_credentials * +grpc_service_account_jwt_access_credentials_create_from_auth_json_key( + grpc_auth_json_key key, gpr_timespec token_lifetime) { + grpc_service_account_jwt_access_credentials *c; + if (!grpc_auth_json_key_is_valid(&key)) { + gpr_log(GPR_ERROR, "Invalid input for jwt credentials creation"); + return NULL; + } + c = gpr_malloc(sizeof(grpc_service_account_jwt_access_credentials)); + memset(c, 0, sizeof(grpc_service_account_jwt_access_credentials)); + c->base.type = GRPC_CALL_CREDENTIALS_TYPE_JWT; + gpr_ref_init(&c->base.refcount, 1); + c->base.vtable = &jwt_vtable; + c->key = key; + c->jwt_lifetime = token_lifetime; + gpr_mu_init(&c->cache_mu); + jwt_reset_cache(c); + return &c->base; +} + +grpc_call_credentials *grpc_service_account_jwt_access_credentials_create( + const char *json_key, gpr_timespec token_lifetime, void *reserved) { + GRPC_API_TRACE( + "grpc_service_account_jwt_access_credentials_create(" + "json_key=%s, " + "token_lifetime=" + "gpr_timespec { tv_sec: %lld, tv_nsec: %d, clock_type: %d }, " + "reserved=%p)", + 5, + (json_key, (long long)token_lifetime.tv_sec, (int)token_lifetime.tv_nsec, + (int)token_lifetime.clock_type, reserved)); + GPR_ASSERT(reserved == NULL); + return grpc_service_account_jwt_access_credentials_create_from_auth_json_key( + grpc_auth_json_key_create_from_string(json_key), token_lifetime); +} + +/* -- Oauth2TokenFetcher credentials -- */ + +static void oauth2_token_fetcher_destruct(grpc_call_credentials *creds) { + grpc_oauth2_token_fetcher_credentials *c = + (grpc_oauth2_token_fetcher_credentials *)creds; + grpc_credentials_md_store_unref(c->access_token_md); + gpr_mu_destroy(&c->mu); + grpc_httpcli_context_destroy(&c->httpcli_context); +} + +grpc_credentials_status +grpc_oauth2_token_fetcher_credentials_parse_server_response( + const grpc_http_response *response, grpc_credentials_md_store **token_md, + gpr_timespec *token_lifetime) { + char *null_terminated_body = NULL; + char *new_access_token = NULL; + grpc_credentials_status status = GRPC_CREDENTIALS_OK; + grpc_json *json = NULL; + + if (response == NULL) { + gpr_log(GPR_ERROR, "Received NULL response."); + status = GRPC_CREDENTIALS_ERROR; + goto end; + } + + if (response->body_length > 0) { + null_terminated_body = gpr_malloc(response->body_length + 1); + null_terminated_body[response->body_length] = '\0'; + memcpy(null_terminated_body, response->body, response->body_length); + } + + if (response->status != 200) { + gpr_log(GPR_ERROR, "Call to http server ended with error %d [%s].", + response->status, + null_terminated_body != NULL ? null_terminated_body : ""); + status = GRPC_CREDENTIALS_ERROR; + goto end; + } else { + grpc_json *access_token = NULL; + grpc_json *token_type = NULL; + grpc_json *expires_in = NULL; + grpc_json *ptr; + json = grpc_json_parse_string(null_terminated_body); + if (json == NULL) { + gpr_log(GPR_ERROR, "Could not parse JSON from %s", null_terminated_body); + status = GRPC_CREDENTIALS_ERROR; + goto end; + } + if (json->type != GRPC_JSON_OBJECT) { + gpr_log(GPR_ERROR, "Response should be a JSON object"); + status = GRPC_CREDENTIALS_ERROR; + goto end; + } + for (ptr = json->child; ptr; ptr = ptr->next) { + if (strcmp(ptr->key, "access_token") == 0) { + access_token = ptr; + } else if (strcmp(ptr->key, "token_type") == 0) { + token_type = ptr; + } else if (strcmp(ptr->key, "expires_in") == 0) { + expires_in = ptr; + } + } + if (access_token == NULL || access_token->type != GRPC_JSON_STRING) { + gpr_log(GPR_ERROR, "Missing or invalid access_token in JSON."); + status = GRPC_CREDENTIALS_ERROR; + goto end; + } + if (token_type == NULL || token_type->type != GRPC_JSON_STRING) { + gpr_log(GPR_ERROR, "Missing or invalid token_type in JSON."); + status = GRPC_CREDENTIALS_ERROR; + goto end; + } + if (expires_in == NULL || expires_in->type != GRPC_JSON_NUMBER) { + gpr_log(GPR_ERROR, "Missing or invalid expires_in in JSON."); + status = GRPC_CREDENTIALS_ERROR; + goto end; + } + gpr_asprintf(&new_access_token, "%s %s", token_type->value, + access_token->value); + token_lifetime->tv_sec = strtol(expires_in->value, NULL, 10); + token_lifetime->tv_nsec = 0; + token_lifetime->clock_type = GPR_TIMESPAN; + if (*token_md != NULL) grpc_credentials_md_store_unref(*token_md); + *token_md = grpc_credentials_md_store_create(1); + grpc_credentials_md_store_add_cstrings( + *token_md, GRPC_AUTHORIZATION_METADATA_KEY, new_access_token); + status = GRPC_CREDENTIALS_OK; + } + +end: + if (status != GRPC_CREDENTIALS_OK && (*token_md != NULL)) { + grpc_credentials_md_store_unref(*token_md); + *token_md = NULL; + } + if (null_terminated_body != NULL) gpr_free(null_terminated_body); + if (new_access_token != NULL) gpr_free(new_access_token); + if (json != NULL) grpc_json_destroy(json); + return status; +} + +static void on_oauth2_token_fetcher_http_response( + grpc_exec_ctx *exec_ctx, void *user_data, + const grpc_http_response *response) { + grpc_credentials_metadata_request *r = + (grpc_credentials_metadata_request *)user_data; + grpc_oauth2_token_fetcher_credentials *c = + (grpc_oauth2_token_fetcher_credentials *)r->creds; + gpr_timespec token_lifetime; + grpc_credentials_status status; + + gpr_mu_lock(&c->mu); + status = grpc_oauth2_token_fetcher_credentials_parse_server_response( + response, &c->access_token_md, &token_lifetime); + if (status == GRPC_CREDENTIALS_OK) { + c->token_expiration = + gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), token_lifetime); + r->cb(exec_ctx, r->user_data, c->access_token_md->entries, + c->access_token_md->num_entries, status); + } else { + c->token_expiration = gpr_inf_past(GPR_CLOCK_REALTIME); + r->cb(exec_ctx, r->user_data, NULL, 0, status); + } + gpr_mu_unlock(&c->mu); + grpc_credentials_metadata_request_destroy(r); +} + +static void oauth2_token_fetcher_get_request_metadata( + grpc_exec_ctx *exec_ctx, grpc_call_credentials *creds, + grpc_pollset *pollset, grpc_auth_metadata_context context, + grpc_credentials_metadata_cb cb, void *user_data) { + grpc_oauth2_token_fetcher_credentials *c = + (grpc_oauth2_token_fetcher_credentials *)creds; + gpr_timespec refresh_threshold = gpr_time_from_seconds( + GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS, GPR_TIMESPAN); + grpc_credentials_md_store *cached_access_token_md = NULL; + { + gpr_mu_lock(&c->mu); + if (c->access_token_md != NULL && + (gpr_time_cmp( + gpr_time_sub(c->token_expiration, gpr_now(GPR_CLOCK_REALTIME)), + refresh_threshold) > 0)) { + cached_access_token_md = + grpc_credentials_md_store_ref(c->access_token_md); + } + gpr_mu_unlock(&c->mu); + } + if (cached_access_token_md != NULL) { + cb(exec_ctx, user_data, cached_access_token_md->entries, + cached_access_token_md->num_entries, GRPC_CREDENTIALS_OK); + grpc_credentials_md_store_unref(cached_access_token_md); + } else { + c->fetch_func( + exec_ctx, + grpc_credentials_metadata_request_create(creds, cb, user_data), + &c->httpcli_context, pollset, on_oauth2_token_fetcher_http_response, + gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), refresh_threshold)); + } +} + +static void init_oauth2_token_fetcher(grpc_oauth2_token_fetcher_credentials *c, + grpc_fetch_oauth2_func fetch_func) { + memset(c, 0, sizeof(grpc_oauth2_token_fetcher_credentials)); + c->base.type = GRPC_CALL_CREDENTIALS_TYPE_OAUTH2; + gpr_ref_init(&c->base.refcount, 1); + gpr_mu_init(&c->mu); + c->token_expiration = gpr_inf_past(GPR_CLOCK_REALTIME); + c->fetch_func = fetch_func; + grpc_httpcli_context_init(&c->httpcli_context); +} + +/* -- GoogleComputeEngine credentials. -- */ + +static grpc_call_credentials_vtable compute_engine_vtable = { + oauth2_token_fetcher_destruct, oauth2_token_fetcher_get_request_metadata}; + +static void compute_engine_fetch_oauth2( + grpc_exec_ctx *exec_ctx, grpc_credentials_metadata_request *metadata_req, + grpc_httpcli_context *httpcli_context, grpc_pollset *pollset, + grpc_httpcli_response_cb response_cb, gpr_timespec deadline) { + grpc_http_header header = {"Metadata-Flavor", "Google"}; + grpc_httpcli_request request; + memset(&request, 0, sizeof(grpc_httpcli_request)); + request.host = GRPC_COMPUTE_ENGINE_METADATA_HOST; + request.http.path = GRPC_COMPUTE_ENGINE_METADATA_TOKEN_PATH; + request.http.hdr_count = 1; + request.http.hdrs = &header; + grpc_httpcli_get(exec_ctx, httpcli_context, pollset, &request, deadline, + response_cb, metadata_req); +} + +grpc_call_credentials *grpc_google_compute_engine_credentials_create( + void *reserved) { + grpc_oauth2_token_fetcher_credentials *c = + gpr_malloc(sizeof(grpc_oauth2_token_fetcher_credentials)); + GRPC_API_TRACE("grpc_compute_engine_credentials_create(reserved=%p)", 1, + (reserved)); + GPR_ASSERT(reserved == NULL); + init_oauth2_token_fetcher(c, compute_engine_fetch_oauth2); + c->base.vtable = &compute_engine_vtable; + return &c->base; +} + +/* -- GoogleRefreshToken credentials. -- */ + +static void refresh_token_destruct(grpc_call_credentials *creds) { + grpc_google_refresh_token_credentials *c = + (grpc_google_refresh_token_credentials *)creds; + grpc_auth_refresh_token_destruct(&c->refresh_token); + oauth2_token_fetcher_destruct(&c->base.base); +} + +static grpc_call_credentials_vtable refresh_token_vtable = { + refresh_token_destruct, oauth2_token_fetcher_get_request_metadata}; + +static void refresh_token_fetch_oauth2( + grpc_exec_ctx *exec_ctx, grpc_credentials_metadata_request *metadata_req, + grpc_httpcli_context *httpcli_context, grpc_pollset *pollset, + grpc_httpcli_response_cb response_cb, gpr_timespec deadline) { + grpc_google_refresh_token_credentials *c = + (grpc_google_refresh_token_credentials *)metadata_req->creds; + grpc_http_header header = {"Content-Type", + "application/x-www-form-urlencoded"}; + grpc_httpcli_request request; + char *body = NULL; + gpr_asprintf(&body, GRPC_REFRESH_TOKEN_POST_BODY_FORMAT_STRING, + c->refresh_token.client_id, c->refresh_token.client_secret, + c->refresh_token.refresh_token); + memset(&request, 0, sizeof(grpc_httpcli_request)); + request.host = GRPC_GOOGLE_OAUTH2_SERVICE_HOST; + request.http.path = GRPC_GOOGLE_OAUTH2_SERVICE_TOKEN_PATH; + request.http.hdr_count = 1; + request.http.hdrs = &header; + request.handshaker = &grpc_httpcli_ssl; + grpc_httpcli_post(exec_ctx, httpcli_context, pollset, &request, body, + strlen(body), deadline, response_cb, metadata_req); + gpr_free(body); +} + +grpc_call_credentials * +grpc_refresh_token_credentials_create_from_auth_refresh_token( + grpc_auth_refresh_token refresh_token) { + grpc_google_refresh_token_credentials *c; + if (!grpc_auth_refresh_token_is_valid(&refresh_token)) { + gpr_log(GPR_ERROR, "Invalid input for refresh token credentials creation"); + return NULL; + } + c = gpr_malloc(sizeof(grpc_google_refresh_token_credentials)); + memset(c, 0, sizeof(grpc_google_refresh_token_credentials)); + init_oauth2_token_fetcher(&c->base, refresh_token_fetch_oauth2); + c->base.base.vtable = &refresh_token_vtable; + c->refresh_token = refresh_token; + return &c->base.base; +} + +grpc_call_credentials *grpc_google_refresh_token_credentials_create( + const char *json_refresh_token, void *reserved) { + GRPC_API_TRACE( + "grpc_refresh_token_credentials_create(json_refresh_token=%s, " + "reserved=%p)", + 2, (json_refresh_token, reserved)); + GPR_ASSERT(reserved == NULL); + return grpc_refresh_token_credentials_create_from_auth_refresh_token( + grpc_auth_refresh_token_create_from_string(json_refresh_token)); +} + +/* -- Metadata-only credentials. -- */ + +static void md_only_test_destruct(grpc_call_credentials *creds) { + grpc_md_only_test_credentials *c = (grpc_md_only_test_credentials *)creds; + grpc_credentials_md_store_unref(c->md_store); +} + +static void on_simulated_token_fetch_done(grpc_exec_ctx *exec_ctx, + void *user_data, bool success) { + grpc_credentials_metadata_request *r = + (grpc_credentials_metadata_request *)user_data; + grpc_md_only_test_credentials *c = (grpc_md_only_test_credentials *)r->creds; + r->cb(exec_ctx, r->user_data, c->md_store->entries, c->md_store->num_entries, + GRPC_CREDENTIALS_OK); + grpc_credentials_metadata_request_destroy(r); +} + +static void md_only_test_get_request_metadata( + grpc_exec_ctx *exec_ctx, grpc_call_credentials *creds, + grpc_pollset *pollset, grpc_auth_metadata_context context, + grpc_credentials_metadata_cb cb, void *user_data) { + grpc_md_only_test_credentials *c = (grpc_md_only_test_credentials *)creds; + + if (c->is_async) { + grpc_credentials_metadata_request *cb_arg = + grpc_credentials_metadata_request_create(creds, cb, user_data); + grpc_executor_enqueue( + grpc_closure_create(on_simulated_token_fetch_done, cb_arg), true); + } else { + cb(exec_ctx, user_data, c->md_store->entries, 1, GRPC_CREDENTIALS_OK); + } +} + +static grpc_call_credentials_vtable md_only_test_vtable = { + md_only_test_destruct, md_only_test_get_request_metadata}; + +grpc_call_credentials *grpc_md_only_test_credentials_create( + const char *md_key, const char *md_value, int is_async) { + grpc_md_only_test_credentials *c = + gpr_malloc(sizeof(grpc_md_only_test_credentials)); + memset(c, 0, sizeof(grpc_md_only_test_credentials)); + c->base.type = GRPC_CALL_CREDENTIALS_TYPE_OAUTH2; + c->base.vtable = &md_only_test_vtable; + gpr_ref_init(&c->base.refcount, 1); + c->md_store = grpc_credentials_md_store_create(1); + grpc_credentials_md_store_add_cstrings(c->md_store, md_key, md_value); + c->is_async = is_async; + return &c->base; +} + +/* -- Oauth2 Access Token credentials. -- */ + +static void access_token_destruct(grpc_call_credentials *creds) { + grpc_access_token_credentials *c = (grpc_access_token_credentials *)creds; + grpc_credentials_md_store_unref(c->access_token_md); +} + +static void access_token_get_request_metadata( + grpc_exec_ctx *exec_ctx, grpc_call_credentials *creds, + grpc_pollset *pollset, grpc_auth_metadata_context context, + grpc_credentials_metadata_cb cb, void *user_data) { + grpc_access_token_credentials *c = (grpc_access_token_credentials *)creds; + cb(exec_ctx, user_data, c->access_token_md->entries, 1, GRPC_CREDENTIALS_OK); +} + +static grpc_call_credentials_vtable access_token_vtable = { + access_token_destruct, access_token_get_request_metadata}; + +grpc_call_credentials *grpc_access_token_credentials_create( + const char *access_token, void *reserved) { + grpc_access_token_credentials *c = + gpr_malloc(sizeof(grpc_access_token_credentials)); + char *token_md_value; + GRPC_API_TRACE( + "grpc_access_token_credentials_create(access_token=%s, " + "reserved=%p)", + 2, (access_token, reserved)); + GPR_ASSERT(reserved == NULL); + memset(c, 0, sizeof(grpc_access_token_credentials)); + c->base.type = GRPC_CALL_CREDENTIALS_TYPE_OAUTH2; + c->base.vtable = &access_token_vtable; + gpr_ref_init(&c->base.refcount, 1); + c->access_token_md = grpc_credentials_md_store_create(1); + gpr_asprintf(&token_md_value, "Bearer %s", access_token); + grpc_credentials_md_store_add_cstrings( + c->access_token_md, GRPC_AUTHORIZATION_METADATA_KEY, token_md_value); + gpr_free(token_md_value); + return &c->base; +} + +/* -- Fake transport security credentials. -- */ + +static grpc_security_status fake_transport_security_create_security_connector( + grpc_channel_credentials *c, grpc_call_credentials *call_creds, + const char *target, const grpc_channel_args *args, + grpc_channel_security_connector **sc, grpc_channel_args **new_args) { + *sc = grpc_fake_channel_security_connector_create(call_creds); + return GRPC_SECURITY_OK; +} + +static grpc_security_status +fake_transport_security_server_create_security_connector( + grpc_server_credentials *c, grpc_server_security_connector **sc) { + *sc = grpc_fake_server_security_connector_create(); + return GRPC_SECURITY_OK; +} + +static grpc_channel_credentials_vtable + fake_transport_security_credentials_vtable = { + NULL, fake_transport_security_create_security_connector}; + +static grpc_server_credentials_vtable + fake_transport_security_server_credentials_vtable = { + NULL, fake_transport_security_server_create_security_connector}; + +grpc_channel_credentials *grpc_fake_transport_security_credentials_create( + void) { + grpc_channel_credentials *c = gpr_malloc(sizeof(grpc_channel_credentials)); + memset(c, 0, sizeof(grpc_channel_credentials)); + c->type = GRPC_CHANNEL_CREDENTIALS_TYPE_FAKE_TRANSPORT_SECURITY; + c->vtable = &fake_transport_security_credentials_vtable; + gpr_ref_init(&c->refcount, 1); + return c; +} + +grpc_server_credentials *grpc_fake_transport_security_server_credentials_create( + void) { + grpc_server_credentials *c = gpr_malloc(sizeof(grpc_server_credentials)); + memset(c, 0, sizeof(grpc_server_credentials)); + c->type = GRPC_CHANNEL_CREDENTIALS_TYPE_FAKE_TRANSPORT_SECURITY; + gpr_ref_init(&c->refcount, 1); + c->vtable = &fake_transport_security_server_credentials_vtable; + return c; +} + +/* -- Composite call credentials. -- */ + +typedef struct { + grpc_composite_call_credentials *composite_creds; + size_t creds_index; + grpc_credentials_md_store *md_elems; + grpc_auth_metadata_context auth_md_context; + void *user_data; + grpc_pollset *pollset; + grpc_credentials_metadata_cb cb; +} grpc_composite_call_credentials_metadata_context; + +static void composite_call_destruct(grpc_call_credentials *creds) { + grpc_composite_call_credentials *c = (grpc_composite_call_credentials *)creds; + size_t i; + for (i = 0; i < c->inner.num_creds; i++) { + grpc_call_credentials_unref(c->inner.creds_array[i]); + } + gpr_free(c->inner.creds_array); +} + +static void composite_call_md_context_destroy( + grpc_composite_call_credentials_metadata_context *ctx) { + grpc_credentials_md_store_unref(ctx->md_elems); + gpr_free(ctx); +} + +static void composite_call_metadata_cb(grpc_exec_ctx *exec_ctx, void *user_data, + grpc_credentials_md *md_elems, + size_t num_md, + grpc_credentials_status status) { + grpc_composite_call_credentials_metadata_context *ctx = + (grpc_composite_call_credentials_metadata_context *)user_data; + if (status != GRPC_CREDENTIALS_OK) { + ctx->cb(exec_ctx, ctx->user_data, NULL, 0, status); + return; + } + + /* Copy the metadata in the context. */ + if (num_md > 0) { + size_t i; + for (i = 0; i < num_md; i++) { + grpc_credentials_md_store_add(ctx->md_elems, md_elems[i].key, + md_elems[i].value); + } + } + + /* See if we need to get some more metadata. */ + if (ctx->creds_index < ctx->composite_creds->inner.num_creds) { + grpc_call_credentials *inner_creds = + ctx->composite_creds->inner.creds_array[ctx->creds_index++]; + grpc_call_credentials_get_request_metadata( + exec_ctx, inner_creds, ctx->pollset, ctx->auth_md_context, + composite_call_metadata_cb, ctx); + return; + } + + /* We're done!. */ + ctx->cb(exec_ctx, ctx->user_data, ctx->md_elems->entries, + ctx->md_elems->num_entries, GRPC_CREDENTIALS_OK); + composite_call_md_context_destroy(ctx); +} + +static void composite_call_get_request_metadata( + grpc_exec_ctx *exec_ctx, grpc_call_credentials *creds, + grpc_pollset *pollset, grpc_auth_metadata_context auth_md_context, + grpc_credentials_metadata_cb cb, void *user_data) { + grpc_composite_call_credentials *c = (grpc_composite_call_credentials *)creds; + grpc_composite_call_credentials_metadata_context *ctx; + + ctx = gpr_malloc(sizeof(grpc_composite_call_credentials_metadata_context)); + memset(ctx, 0, sizeof(grpc_composite_call_credentials_metadata_context)); + ctx->auth_md_context = auth_md_context; + ctx->user_data = user_data; + ctx->cb = cb; + ctx->composite_creds = c; + ctx->pollset = pollset; + ctx->md_elems = grpc_credentials_md_store_create(c->inner.num_creds); + grpc_call_credentials_get_request_metadata( + exec_ctx, c->inner.creds_array[ctx->creds_index++], pollset, + auth_md_context, composite_call_metadata_cb, ctx); +} + +static grpc_call_credentials_vtable composite_call_credentials_vtable = { + composite_call_destruct, composite_call_get_request_metadata}; + +static grpc_call_credentials_array get_creds_array( + grpc_call_credentials **creds_addr) { + grpc_call_credentials_array result; + grpc_call_credentials *creds = *creds_addr; + result.creds_array = creds_addr; + result.num_creds = 1; + if (strcmp(creds->type, GRPC_CALL_CREDENTIALS_TYPE_COMPOSITE) == 0) { + result = *grpc_composite_call_credentials_get_credentials(creds); + } + return result; +} + +grpc_call_credentials *grpc_composite_call_credentials_create( + grpc_call_credentials *creds1, grpc_call_credentials *creds2, + void *reserved) { + size_t i; + size_t creds_array_byte_size; + grpc_call_credentials_array creds1_array; + grpc_call_credentials_array creds2_array; + grpc_composite_call_credentials *c; + GRPC_API_TRACE( + "grpc_composite_call_credentials_create(creds1=%p, creds2=%p, " + "reserved=%p)", + 3, (creds1, creds2, reserved)); + GPR_ASSERT(reserved == NULL); + GPR_ASSERT(creds1 != NULL); + GPR_ASSERT(creds2 != NULL); + c = gpr_malloc(sizeof(grpc_composite_call_credentials)); + memset(c, 0, sizeof(grpc_composite_call_credentials)); + c->base.type = GRPC_CALL_CREDENTIALS_TYPE_COMPOSITE; + c->base.vtable = &composite_call_credentials_vtable; + gpr_ref_init(&c->base.refcount, 1); + creds1_array = get_creds_array(&creds1); + creds2_array = get_creds_array(&creds2); + c->inner.num_creds = creds1_array.num_creds + creds2_array.num_creds; + creds_array_byte_size = c->inner.num_creds * sizeof(grpc_call_credentials *); + c->inner.creds_array = gpr_malloc(creds_array_byte_size); + memset(c->inner.creds_array, 0, creds_array_byte_size); + for (i = 0; i < creds1_array.num_creds; i++) { + grpc_call_credentials *cur_creds = creds1_array.creds_array[i]; + c->inner.creds_array[i] = grpc_call_credentials_ref(cur_creds); + } + for (i = 0; i < creds2_array.num_creds; i++) { + grpc_call_credentials *cur_creds = creds2_array.creds_array[i]; + c->inner.creds_array[i + creds1_array.num_creds] = + grpc_call_credentials_ref(cur_creds); + } + return &c->base; +} + +const grpc_call_credentials_array * +grpc_composite_call_credentials_get_credentials(grpc_call_credentials *creds) { + const grpc_composite_call_credentials *c = + (const grpc_composite_call_credentials *)creds; + GPR_ASSERT(strcmp(creds->type, GRPC_CALL_CREDENTIALS_TYPE_COMPOSITE) == 0); + return &c->inner; +} + +grpc_call_credentials *grpc_credentials_contains_type( + grpc_call_credentials *creds, const char *type, + grpc_call_credentials **composite_creds) { + size_t i; + if (strcmp(creds->type, type) == 0) { + if (composite_creds != NULL) *composite_creds = NULL; + return creds; + } else if (strcmp(creds->type, GRPC_CALL_CREDENTIALS_TYPE_COMPOSITE) == 0) { + const grpc_call_credentials_array *inner_creds_array = + grpc_composite_call_credentials_get_credentials(creds); + for (i = 0; i < inner_creds_array->num_creds; i++) { + if (strcmp(type, inner_creds_array->creds_array[i]->type) == 0) { + if (composite_creds != NULL) *composite_creds = creds; + return inner_creds_array->creds_array[i]; + } + } + } + return NULL; +} + +/* -- IAM credentials. -- */ + +static void iam_destruct(grpc_call_credentials *creds) { + grpc_google_iam_credentials *c = (grpc_google_iam_credentials *)creds; + grpc_credentials_md_store_unref(c->iam_md); +} + +static void iam_get_request_metadata(grpc_exec_ctx *exec_ctx, + grpc_call_credentials *creds, + grpc_pollset *pollset, + grpc_auth_metadata_context context, + grpc_credentials_metadata_cb cb, + void *user_data) { + grpc_google_iam_credentials *c = (grpc_google_iam_credentials *)creds; + cb(exec_ctx, user_data, c->iam_md->entries, c->iam_md->num_entries, + GRPC_CREDENTIALS_OK); +} + +static grpc_call_credentials_vtable iam_vtable = {iam_destruct, + iam_get_request_metadata}; + +grpc_call_credentials *grpc_google_iam_credentials_create( + const char *token, const char *authority_selector, void *reserved) { + grpc_google_iam_credentials *c; + GRPC_API_TRACE( + "grpc_iam_credentials_create(token=%s, authority_selector=%s, " + "reserved=%p)", + 3, (token, authority_selector, reserved)); + GPR_ASSERT(reserved == NULL); + GPR_ASSERT(token != NULL); + GPR_ASSERT(authority_selector != NULL); + c = gpr_malloc(sizeof(grpc_google_iam_credentials)); + memset(c, 0, sizeof(grpc_google_iam_credentials)); + c->base.type = GRPC_CALL_CREDENTIALS_TYPE_IAM; + c->base.vtable = &iam_vtable; + gpr_ref_init(&c->base.refcount, 1); + c->iam_md = grpc_credentials_md_store_create(2); + grpc_credentials_md_store_add_cstrings( + c->iam_md, GRPC_IAM_AUTHORIZATION_TOKEN_METADATA_KEY, token); + grpc_credentials_md_store_add_cstrings( + c->iam_md, GRPC_IAM_AUTHORITY_SELECTOR_METADATA_KEY, authority_selector); + return &c->base; +} + +/* -- Plugin credentials. -- */ + +typedef struct { + void *user_data; + grpc_credentials_metadata_cb cb; +} grpc_metadata_plugin_request; + +static void plugin_destruct(grpc_call_credentials *creds) { + grpc_plugin_credentials *c = (grpc_plugin_credentials *)creds; + if (c->plugin.state != NULL && c->plugin.destroy != NULL) { + c->plugin.destroy(c->plugin.state); + } +} + +static void plugin_md_request_metadata_ready(void *request, + const grpc_metadata *md, + size_t num_md, + grpc_status_code status, + const char *error_details) { + /* called from application code */ + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + grpc_metadata_plugin_request *r = (grpc_metadata_plugin_request *)request; + if (status != GRPC_STATUS_OK) { + if (error_details != NULL) { + gpr_log(GPR_ERROR, "Getting metadata from plugin failed with error: %s", + error_details); + } + r->cb(&exec_ctx, r->user_data, NULL, 0, GRPC_CREDENTIALS_ERROR); + } else { + size_t i; + grpc_credentials_md *md_array = NULL; + if (num_md > 0) { + md_array = gpr_malloc(num_md * sizeof(grpc_credentials_md)); + for (i = 0; i < num_md; i++) { + md_array[i].key = gpr_slice_from_copied_string(md[i].key); + md_array[i].value = + gpr_slice_from_copied_buffer(md[i].value, md[i].value_length); + } + } + r->cb(&exec_ctx, r->user_data, md_array, num_md, GRPC_CREDENTIALS_OK); + if (md_array != NULL) { + for (i = 0; i < num_md; i++) { + gpr_slice_unref(md_array[i].key); + gpr_slice_unref(md_array[i].value); + } + gpr_free(md_array); + } + } + gpr_free(r); + grpc_exec_ctx_finish(&exec_ctx); +} + +static void plugin_get_request_metadata(grpc_exec_ctx *exec_ctx, + grpc_call_credentials *creds, + grpc_pollset *pollset, + grpc_auth_metadata_context context, + grpc_credentials_metadata_cb cb, + void *user_data) { + grpc_plugin_credentials *c = (grpc_plugin_credentials *)creds; + if (c->plugin.get_metadata != NULL) { + grpc_metadata_plugin_request *request = gpr_malloc(sizeof(*request)); + memset(request, 0, sizeof(*request)); + request->user_data = user_data; + request->cb = cb; + c->plugin.get_metadata(c->plugin.state, context, + plugin_md_request_metadata_ready, request); + } else { + cb(exec_ctx, user_data, NULL, 0, GRPC_CREDENTIALS_OK); + } +} + +static grpc_call_credentials_vtable plugin_vtable = { + plugin_destruct, plugin_get_request_metadata}; + +grpc_call_credentials *grpc_metadata_credentials_create_from_plugin( + grpc_metadata_credentials_plugin plugin, void *reserved) { + grpc_plugin_credentials *c = gpr_malloc(sizeof(*c)); + GRPC_API_TRACE("grpc_metadata_credentials_create_from_plugin(reserved=%p)", 1, + (reserved)); + GPR_ASSERT(reserved == NULL); + memset(c, 0, sizeof(*c)); + c->base.type = plugin.type; + c->base.vtable = &plugin_vtable; + gpr_ref_init(&c->base.refcount, 1); + c->plugin = plugin; + return &c->base; +} + +/* -- Composite channel credentials. -- */ + +static void composite_channel_destruct(grpc_channel_credentials *creds) { + grpc_composite_channel_credentials *c = + (grpc_composite_channel_credentials *)creds; + grpc_channel_credentials_unref(c->inner_creds); + grpc_call_credentials_unref(c->call_creds); +} + +static grpc_security_status composite_channel_create_security_connector( + grpc_channel_credentials *creds, grpc_call_credentials *call_creds, + const char *target, const grpc_channel_args *args, + grpc_channel_security_connector **sc, grpc_channel_args **new_args) { + grpc_composite_channel_credentials *c = + (grpc_composite_channel_credentials *)creds; + grpc_security_status status = GRPC_SECURITY_ERROR; + + GPR_ASSERT(c->inner_creds != NULL && c->call_creds != NULL && + c->inner_creds->vtable != NULL && + c->inner_creds->vtable->create_security_connector != NULL); + /* If we are passed a call_creds, create a call composite to pass it + downstream. */ + if (call_creds != NULL) { + grpc_call_credentials *composite_call_creds = + grpc_composite_call_credentials_create(c->call_creds, call_creds, NULL); + status = c->inner_creds->vtable->create_security_connector( + c->inner_creds, composite_call_creds, target, args, sc, new_args); + grpc_call_credentials_unref(composite_call_creds); + } else { + status = c->inner_creds->vtable->create_security_connector( + c->inner_creds, c->call_creds, target, args, sc, new_args); + } + return status; +} + +static grpc_channel_credentials_vtable composite_channel_credentials_vtable = { + composite_channel_destruct, composite_channel_create_security_connector}; + +grpc_channel_credentials *grpc_composite_channel_credentials_create( + grpc_channel_credentials *channel_creds, grpc_call_credentials *call_creds, + void *reserved) { + grpc_composite_channel_credentials *c = gpr_malloc(sizeof(*c)); + memset(c, 0, sizeof(*c)); + GPR_ASSERT(channel_creds != NULL && call_creds != NULL && reserved == NULL); + GRPC_API_TRACE( + "grpc_composite_channel_credentials_create(channel_creds=%p, " + "call_creds=%p, reserved=%p)", + 3, (channel_creds, call_creds, reserved)); + c->base.type = channel_creds->type; + c->base.vtable = &composite_channel_credentials_vtable; + gpr_ref_init(&c->base.refcount, 1); + c->inner_creds = grpc_channel_credentials_ref(channel_creds); + c->call_creds = grpc_call_credentials_ref(call_creds); + return &c->base; +} diff --git a/src/core/lib/security/credentials.h b/src/core/lib/security/credentials.h new file mode 100644 index 0000000000..7168b98942 --- /dev/null +++ b/src/core/lib/security/credentials.h @@ -0,0 +1,377 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SECURITY_CREDENTIALS_H +#define GRPC_CORE_LIB_SECURITY_CREDENTIALS_H + +#include <grpc/grpc.h> +#include <grpc/grpc_security.h> +#include <grpc/support/sync.h> +#include "src/core/lib/transport/metadata_batch.h" + +#include "src/core/lib/http/httpcli.h" +#include "src/core/lib/http/parser.h" +#include "src/core/lib/security/json_token.h" +#include "src/core/lib/security/security_connector.h" + +struct grpc_http_response; + +/* --- Constants. --- */ + +typedef enum { + GRPC_CREDENTIALS_OK = 0, + GRPC_CREDENTIALS_ERROR +} grpc_credentials_status; + +#define GRPC_FAKE_TRANSPORT_SECURITY_TYPE "fake" + +#define GRPC_CHANNEL_CREDENTIALS_TYPE_SSL "Ssl" +#define GRPC_CHANNEL_CREDENTIALS_TYPE_FAKE_TRANSPORT_SECURITY \ + "FakeTransportSecurity" + +#define GRPC_CALL_CREDENTIALS_TYPE_OAUTH2 "Oauth2" +#define GRPC_CALL_CREDENTIALS_TYPE_JWT "Jwt" +#define GRPC_CALL_CREDENTIALS_TYPE_IAM "Iam" +#define GRPC_CALL_CREDENTIALS_TYPE_COMPOSITE "Composite" + +#define GRPC_AUTHORIZATION_METADATA_KEY "authorization" +#define GRPC_IAM_AUTHORIZATION_TOKEN_METADATA_KEY \ + "x-goog-iam-authorization-token" +#define GRPC_IAM_AUTHORITY_SELECTOR_METADATA_KEY "x-goog-iam-authority-selector" + +#define GRPC_GOOGLE_CLOUD_SDK_CONFIG_DIRECTORY "gcloud" +#define GRPC_GOOGLE_WELL_KNOWN_CREDENTIALS_FILE \ + "application_default_credentials.json" + +#define GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS 60 + +#define GRPC_COMPUTE_ENGINE_METADATA_HOST "metadata" +#define GRPC_COMPUTE_ENGINE_METADATA_TOKEN_PATH \ + "/computeMetadata/v1/instance/service-accounts/default/token" + +#define GRPC_GOOGLE_OAUTH2_SERVICE_HOST "www.googleapis.com" +#define GRPC_GOOGLE_OAUTH2_SERVICE_TOKEN_PATH "/oauth2/v3/token" + +#define GRPC_SERVICE_ACCOUNT_POST_BODY_PREFIX \ + "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&" \ + "assertion=" + +#define GRPC_REFRESH_TOKEN_POST_BODY_FORMAT_STRING \ + "client_id=%s&client_secret=%s&refresh_token=%s&grant_type=refresh_token" + +/* --- Google utils --- */ + +/* It is the caller's responsibility to gpr_free the result if not NULL. */ +char *grpc_get_well_known_google_credentials_file_path(void); + +/* Implementation function for the different platforms. */ +char *grpc_get_well_known_google_credentials_file_path_impl(void); + +/* Override for testing only. Not thread-safe */ +typedef char *(*grpc_well_known_credentials_path_getter)(void); +void grpc_override_well_known_credentials_path_getter( + grpc_well_known_credentials_path_getter getter); + +/* --- grpc_channel_credentials. --- */ + +typedef struct { + void (*destruct)(grpc_channel_credentials *c); + + grpc_security_status (*create_security_connector)( + grpc_channel_credentials *c, grpc_call_credentials *call_creds, + const char *target, const grpc_channel_args *args, + grpc_channel_security_connector **sc, grpc_channel_args **new_args); +} grpc_channel_credentials_vtable; + +struct grpc_channel_credentials { + const grpc_channel_credentials_vtable *vtable; + const char *type; + gpr_refcount refcount; +}; + +grpc_channel_credentials *grpc_channel_credentials_ref( + grpc_channel_credentials *creds); +void grpc_channel_credentials_unref(grpc_channel_credentials *creds); + +/* Creates a security connector for the channel. May also create new channel + args for the channel to be used in place of the passed in const args if + returned non NULL. In that case the caller is responsible for destroying + new_args after channel creation. */ +grpc_security_status grpc_channel_credentials_create_security_connector( + grpc_channel_credentials *creds, const char *target, + const grpc_channel_args *args, grpc_channel_security_connector **sc, + grpc_channel_args **new_args); + +/* --- grpc_credentials_md. --- */ + +typedef struct { + gpr_slice key; + gpr_slice value; +} grpc_credentials_md; + +typedef struct { + grpc_credentials_md *entries; + size_t num_entries; + size_t allocated; + gpr_refcount refcount; +} grpc_credentials_md_store; + +grpc_credentials_md_store *grpc_credentials_md_store_create( + size_t initial_capacity); + +/* Will ref key and value. */ +void grpc_credentials_md_store_add(grpc_credentials_md_store *store, + gpr_slice key, gpr_slice value); +void grpc_credentials_md_store_add_cstrings(grpc_credentials_md_store *store, + const char *key, const char *value); +grpc_credentials_md_store *grpc_credentials_md_store_ref( + grpc_credentials_md_store *store); +void grpc_credentials_md_store_unref(grpc_credentials_md_store *store); + +/* --- grpc_call_credentials. --- */ + +typedef void (*grpc_credentials_metadata_cb)(grpc_exec_ctx *exec_ctx, + void *user_data, + grpc_credentials_md *md_elems, + size_t num_md, + grpc_credentials_status status); + +typedef struct { + void (*destruct)(grpc_call_credentials *c); + void (*get_request_metadata)(grpc_exec_ctx *exec_ctx, + grpc_call_credentials *c, grpc_pollset *pollset, + grpc_auth_metadata_context context, + grpc_credentials_metadata_cb cb, + void *user_data); +} grpc_call_credentials_vtable; + +struct grpc_call_credentials { + const grpc_call_credentials_vtable *vtable; + const char *type; + gpr_refcount refcount; +}; + +grpc_call_credentials *grpc_call_credentials_ref(grpc_call_credentials *creds); +void grpc_call_credentials_unref(grpc_call_credentials *creds); +void grpc_call_credentials_get_request_metadata( + grpc_exec_ctx *exec_ctx, grpc_call_credentials *creds, + grpc_pollset *pollset, grpc_auth_metadata_context context, + grpc_credentials_metadata_cb cb, void *user_data); + +typedef struct { + grpc_call_credentials **creds_array; + size_t num_creds; +} grpc_call_credentials_array; + +const grpc_call_credentials_array * +grpc_composite_call_credentials_get_credentials( + grpc_call_credentials *composite_creds); + +/* Returns creds if creds is of the specified type or the inner creds of the + specified type (if found), if the creds is of type COMPOSITE. + If composite_creds is not NULL, *composite_creds will point to creds if of + type COMPOSITE in case of success. */ +grpc_call_credentials *grpc_credentials_contains_type( + grpc_call_credentials *creds, const char *type, + grpc_call_credentials **composite_creds); + +/* Exposed for testing only. */ +grpc_credentials_status +grpc_oauth2_token_fetcher_credentials_parse_server_response( + const struct grpc_http_response *response, + grpc_credentials_md_store **token_md, gpr_timespec *token_lifetime); + +void grpc_flush_cached_google_default_credentials(void); + +/* Metadata-only credentials with the specified key and value where + asynchronicity can be simulated for testing. */ +grpc_call_credentials *grpc_md_only_test_credentials_create( + const char *md_key, const char *md_value, int is_async); + +/* Private constructor for jwt credentials from an already parsed json key. + Takes ownership of the key. */ +grpc_call_credentials * +grpc_service_account_jwt_access_credentials_create_from_auth_json_key( + grpc_auth_json_key key, gpr_timespec token_lifetime); + +/* Private constructor for refresh token credentials from an already parsed + refresh token. Takes ownership of the refresh token. */ +grpc_call_credentials * +grpc_refresh_token_credentials_create_from_auth_refresh_token( + grpc_auth_refresh_token token); + +/* --- grpc_server_credentials. --- */ + +typedef struct { + void (*destruct)(grpc_server_credentials *c); + grpc_security_status (*create_security_connector)( + grpc_server_credentials *c, grpc_server_security_connector **sc); +} grpc_server_credentials_vtable; + +struct grpc_server_credentials { + const grpc_server_credentials_vtable *vtable; + const char *type; + gpr_refcount refcount; + grpc_auth_metadata_processor processor; +}; + +grpc_security_status grpc_server_credentials_create_security_connector( + grpc_server_credentials *creds, grpc_server_security_connector **sc); + +grpc_server_credentials *grpc_server_credentials_ref( + grpc_server_credentials *creds); + +void grpc_server_credentials_unref(grpc_server_credentials *creds); + +#define GRPC_SERVER_CREDENTIALS_ARG "grpc.server_credentials" + +grpc_arg grpc_server_credentials_to_arg(grpc_server_credentials *c); +grpc_server_credentials *grpc_server_credentials_from_arg(const grpc_arg *arg); +grpc_server_credentials *grpc_find_server_credentials_in_args( + const grpc_channel_args *args); + +/* -- Fake transport security credentials. -- */ + +/* Creates a fake transport security credentials object for testing. */ +grpc_channel_credentials *grpc_fake_transport_security_credentials_create(void); +/* Creates a fake server transport security credentials object for testing. */ +grpc_server_credentials *grpc_fake_transport_security_server_credentials_create( + void); + +/* -- Ssl credentials. -- */ + +typedef struct { + grpc_channel_credentials base; + grpc_ssl_config config; +} grpc_ssl_credentials; + +typedef struct { + grpc_server_credentials base; + grpc_ssl_server_config config; +} grpc_ssl_server_credentials; + +/* -- Channel composite credentials. -- */ + +typedef struct { + grpc_channel_credentials base; + grpc_channel_credentials *inner_creds; + grpc_call_credentials *call_creds; +} grpc_composite_channel_credentials; + +/* -- Jwt credentials -- */ + +typedef struct { + grpc_call_credentials base; + + /* Have a simple cache for now with just 1 entry. We could have a map based on + the service_url for a more sophisticated one. */ + gpr_mu cache_mu; + struct { + grpc_credentials_md_store *jwt_md; + char *service_url; + gpr_timespec jwt_expiration; + } cached; + + grpc_auth_json_key key; + gpr_timespec jwt_lifetime; +} grpc_service_account_jwt_access_credentials; + +/* -- Oauth2TokenFetcher credentials -- + + This object is a base for credentials that need to acquire an oauth2 token + from an http service. */ + +typedef struct grpc_credentials_metadata_request + grpc_credentials_metadata_request; + +typedef void (*grpc_fetch_oauth2_func)(grpc_exec_ctx *exec_ctx, + grpc_credentials_metadata_request *req, + grpc_httpcli_context *http_context, + grpc_pollset *pollset, + grpc_httpcli_response_cb response_cb, + gpr_timespec deadline); + +typedef struct { + grpc_call_credentials base; + gpr_mu mu; + grpc_credentials_md_store *access_token_md; + gpr_timespec token_expiration; + grpc_httpcli_context httpcli_context; + grpc_fetch_oauth2_func fetch_func; +} grpc_oauth2_token_fetcher_credentials; + +/* -- GoogleRefreshToken credentials. -- */ + +typedef struct { + grpc_oauth2_token_fetcher_credentials base; + grpc_auth_refresh_token refresh_token; +} grpc_google_refresh_token_credentials; + +/* -- Oauth2 Access Token credentials. -- */ + +typedef struct { + grpc_call_credentials base; + grpc_credentials_md_store *access_token_md; +} grpc_access_token_credentials; + +/* -- Metadata-only Test credentials. -- */ + +typedef struct { + grpc_call_credentials base; + grpc_credentials_md_store *md_store; + int is_async; +} grpc_md_only_test_credentials; + +/* -- GoogleIAM credentials. -- */ + +typedef struct { + grpc_call_credentials base; + grpc_credentials_md_store *iam_md; +} grpc_google_iam_credentials; + +/* -- Composite credentials. -- */ + +typedef struct { + grpc_call_credentials base; + grpc_call_credentials_array inner; +} grpc_composite_call_credentials; + +/* -- Plugin credentials. -- */ + +typedef struct { + grpc_call_credentials base; + grpc_metadata_credentials_plugin plugin; + grpc_credentials_md_store *plugin_md; +} grpc_plugin_credentials; + +#endif /* GRPC_CORE_LIB_SECURITY_CREDENTIALS_H */ diff --git a/src/core/lib/security/credentials_metadata.c b/src/core/lib/security/credentials_metadata.c new file mode 100644 index 0000000000..c3bfcb11b5 --- /dev/null +++ b/src/core/lib/security/credentials_metadata.c @@ -0,0 +1,101 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/security/credentials.h" + +#include <grpc/support/alloc.h> + +#include <string.h> + +static void store_ensure_capacity(grpc_credentials_md_store *store) { + if (store->num_entries == store->allocated) { + store->allocated = (store->allocated == 0) ? 1 : store->allocated * 2; + store->entries = gpr_realloc( + store->entries, store->allocated * sizeof(grpc_credentials_md)); + } +} + +grpc_credentials_md_store *grpc_credentials_md_store_create( + size_t initial_capacity) { + grpc_credentials_md_store *store = + gpr_malloc(sizeof(grpc_credentials_md_store)); + memset(store, 0, sizeof(grpc_credentials_md_store)); + if (initial_capacity > 0) { + store->entries = gpr_malloc(initial_capacity * sizeof(grpc_credentials_md)); + store->allocated = initial_capacity; + } + gpr_ref_init(&store->refcount, 1); + return store; +} + +void grpc_credentials_md_store_add(grpc_credentials_md_store *store, + gpr_slice key, gpr_slice value) { + if (store == NULL) return; + store_ensure_capacity(store); + store->entries[store->num_entries].key = gpr_slice_ref(key); + store->entries[store->num_entries].value = gpr_slice_ref(value); + store->num_entries++; +} + +void grpc_credentials_md_store_add_cstrings(grpc_credentials_md_store *store, + const char *key, + const char *value) { + if (store == NULL) return; + store_ensure_capacity(store); + store->entries[store->num_entries].key = gpr_slice_from_copied_string(key); + store->entries[store->num_entries].value = + gpr_slice_from_copied_string(value); + store->num_entries++; +} + +grpc_credentials_md_store *grpc_credentials_md_store_ref( + grpc_credentials_md_store *store) { + if (store == NULL) return NULL; + gpr_ref(&store->refcount); + return store; +} + +void grpc_credentials_md_store_unref(grpc_credentials_md_store *store) { + if (store == NULL) return; + if (gpr_unref(&store->refcount)) { + if (store->entries != NULL) { + size_t i; + for (i = 0; i < store->num_entries; i++) { + gpr_slice_unref(store->entries[i].key); + gpr_slice_unref(store->entries[i].value); + } + gpr_free(store->entries); + } + gpr_free(store); + } +} diff --git a/src/core/lib/security/credentials_posix.c b/src/core/lib/security/credentials_posix.c new file mode 100644 index 0000000000..b758cd0a1a --- /dev/null +++ b/src/core/lib/security/credentials_posix.c @@ -0,0 +1,61 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_FILE + +#include "src/core/lib/security/credentials.h" + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> + +#include "src/core/lib/support/env.h" +#include "src/core/lib/support/string.h" + +char *grpc_get_well_known_google_credentials_file_path_impl(void) { + char *result = NULL; + char *home = gpr_getenv("HOME"); + if (home == NULL) { + gpr_log(GPR_ERROR, "Could not get HOME environment variable."); + return NULL; + } + gpr_asprintf(&result, "%s/.config/%s/%s", home, + GRPC_GOOGLE_CLOUD_SDK_CONFIG_DIRECTORY, + GRPC_GOOGLE_WELL_KNOWN_CREDENTIALS_FILE); + gpr_free(home); + return result; +} + +#endif /* GPR_POSIX_FILE */ diff --git a/src/core/lib/security/credentials_win32.c b/src/core/lib/security/credentials_win32.c new file mode 100644 index 0000000000..a225ab0d7d --- /dev/null +++ b/src/core/lib/security/credentials_win32.c @@ -0,0 +1,61 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WIN32 + +#include "src/core/lib/security/credentials.h" + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> + +#include "src/core/lib/support/env.h" +#include "src/core/lib/support/string.h" + +char *grpc_get_well_known_google_credentials_file_path_impl(void) { + char *result = NULL; + char *appdata_path = gpr_getenv("APPDATA"); + if (appdata_path == NULL) { + gpr_log(GPR_ERROR, "Could not get APPDATA environment variable."); + return NULL; + } + gpr_asprintf(&result, "%s/%s/%s", appdata_path, + GRPC_GOOGLE_CLOUD_SDK_CONFIG_DIRECTORY, + GRPC_GOOGLE_WELL_KNOWN_CREDENTIALS_FILE); + gpr_free(appdata_path); + return result; +} + +#endif /* GPR_WIN32 */ diff --git a/src/core/lib/security/google_default_credentials.c b/src/core/lib/security/google_default_credentials.c new file mode 100644 index 0000000000..5c342288cc --- /dev/null +++ b/src/core/lib/security/google_default_credentials.c @@ -0,0 +1,266 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/security/credentials.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/sync.h> + +#include "src/core/lib/http/httpcli.h" +#include "src/core/lib/http/parser.h" +#include "src/core/lib/support/env.h" +#include "src/core/lib/support/load_file.h" +#include "src/core/lib/surface/api_trace.h" + +/* -- Constants. -- */ + +#define GRPC_COMPUTE_ENGINE_DETECTION_HOST "metadata.google.internal" + +/* -- Default credentials. -- */ + +static grpc_channel_credentials *default_credentials = NULL; +static int compute_engine_detection_done = 0; +static gpr_mu g_state_mu; +static gpr_mu *g_polling_mu; +static gpr_once g_once = GPR_ONCE_INIT; + +static void init_default_credentials(void) { gpr_mu_init(&g_state_mu); } + +typedef struct { + grpc_pollset *pollset; + int is_done; + int success; +} compute_engine_detector; + +static void on_compute_engine_detection_http_response( + grpc_exec_ctx *exec_ctx, void *user_data, + const grpc_http_response *response) { + compute_engine_detector *detector = (compute_engine_detector *)user_data; + if (response != NULL && response->status == 200 && response->hdr_count > 0) { + /* Internet providers can return a generic response to all requests, so + it is necessary to check that metadata header is present also. */ + size_t i; + for (i = 0; i < response->hdr_count; i++) { + grpc_http_header *header = &response->hdrs[i]; + if (strcmp(header->key, "Metadata-Flavor") == 0 && + strcmp(header->value, "Google") == 0) { + detector->success = 1; + break; + } + } + } + gpr_mu_lock(g_polling_mu); + detector->is_done = 1; + grpc_pollset_kick(detector->pollset, NULL); + gpr_mu_unlock(g_polling_mu); +} + +static void destroy_pollset(grpc_exec_ctx *exec_ctx, void *p, bool s) { + grpc_pollset_destroy(p); +} + +static int is_stack_running_on_compute_engine(void) { + compute_engine_detector detector; + grpc_httpcli_request request; + grpc_httpcli_context context; + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + grpc_closure destroy_closure; + + /* The http call is local. If it takes more than one sec, it is for sure not + on compute engine. */ + gpr_timespec max_detection_delay = gpr_time_from_seconds(1, GPR_TIMESPAN); + + detector.pollset = gpr_malloc(grpc_pollset_size()); + grpc_pollset_init(detector.pollset, &g_polling_mu); + detector.is_done = 0; + detector.success = 0; + + memset(&request, 0, sizeof(grpc_httpcli_request)); + request.host = GRPC_COMPUTE_ENGINE_DETECTION_HOST; + request.http.path = "/"; + + grpc_httpcli_context_init(&context); + + grpc_httpcli_get( + &exec_ctx, &context, detector.pollset, &request, + gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), max_detection_delay), + on_compute_engine_detection_http_response, &detector); + + grpc_exec_ctx_finish(&exec_ctx); + + /* Block until we get the response. This is not ideal but this should only be + called once for the lifetime of the process by the default credentials. */ + gpr_mu_lock(g_polling_mu); + while (!detector.is_done) { + grpc_pollset_worker *worker = NULL; + grpc_pollset_work(&exec_ctx, detector.pollset, &worker, + gpr_now(GPR_CLOCK_MONOTONIC), + gpr_inf_future(GPR_CLOCK_MONOTONIC)); + } + gpr_mu_unlock(g_polling_mu); + + grpc_httpcli_context_destroy(&context); + grpc_closure_init(&destroy_closure, destroy_pollset, detector.pollset); + grpc_pollset_shutdown(&exec_ctx, detector.pollset, &destroy_closure); + grpc_exec_ctx_finish(&exec_ctx); + g_polling_mu = NULL; + + gpr_free(detector.pollset); + + return detector.success; +} + +/* Takes ownership of creds_path if not NULL. */ +static grpc_call_credentials *create_default_creds_from_path(char *creds_path) { + grpc_json *json = NULL; + grpc_auth_json_key key; + grpc_auth_refresh_token token; + grpc_call_credentials *result = NULL; + gpr_slice creds_data = gpr_empty_slice(); + int file_ok = 0; + if (creds_path == NULL) goto end; + creds_data = gpr_load_file(creds_path, 0, &file_ok); + if (!file_ok) goto end; + json = grpc_json_parse_string_with_len( + (char *)GPR_SLICE_START_PTR(creds_data), GPR_SLICE_LENGTH(creds_data)); + if (json == NULL) goto end; + + /* First, try an auth json key. */ + key = grpc_auth_json_key_create_from_json(json); + if (grpc_auth_json_key_is_valid(&key)) { + result = + grpc_service_account_jwt_access_credentials_create_from_auth_json_key( + key, grpc_max_auth_token_lifetime()); + goto end; + } + + /* Then try a refresh token if the auth json key was invalid. */ + token = grpc_auth_refresh_token_create_from_json(json); + if (grpc_auth_refresh_token_is_valid(&token)) { + result = + grpc_refresh_token_credentials_create_from_auth_refresh_token(token); + goto end; + } + +end: + if (creds_path != NULL) gpr_free(creds_path); + gpr_slice_unref(creds_data); + if (json != NULL) grpc_json_destroy(json); + return result; +} + +grpc_channel_credentials *grpc_google_default_credentials_create(void) { + grpc_channel_credentials *result = NULL; + grpc_call_credentials *call_creds = NULL; + + GRPC_API_TRACE("grpc_google_default_credentials_create(void)", 0, ()); + + gpr_once_init(&g_once, init_default_credentials); + + gpr_mu_lock(&g_state_mu); + + if (default_credentials != NULL) { + result = grpc_channel_credentials_ref(default_credentials); + goto end; + } + + /* First, try the environment variable. */ + call_creds = create_default_creds_from_path( + gpr_getenv(GRPC_GOOGLE_CREDENTIALS_ENV_VAR)); + if (call_creds != NULL) goto end; + + /* Then the well-known file. */ + call_creds = create_default_creds_from_path( + grpc_get_well_known_google_credentials_file_path()); + if (call_creds != NULL) goto end; + + /* At last try to see if we're on compute engine (do the detection only once + since it requires a network test). */ + if (!compute_engine_detection_done) { + int need_compute_engine_creds = is_stack_running_on_compute_engine(); + compute_engine_detection_done = 1; + if (need_compute_engine_creds) { + call_creds = grpc_google_compute_engine_credentials_create(NULL); + } + } + +end: + if (result == NULL) { + if (call_creds != NULL) { + /* Blend with default ssl credentials and add a global reference so that + it + can be cached and re-served. */ + grpc_channel_credentials *ssl_creds = + grpc_ssl_credentials_create(NULL, NULL, NULL); + default_credentials = grpc_channel_credentials_ref( + grpc_composite_channel_credentials_create(ssl_creds, call_creds, + NULL)); + GPR_ASSERT(default_credentials != NULL); + grpc_channel_credentials_unref(ssl_creds); + grpc_call_credentials_unref(call_creds); + result = default_credentials; + } else { + gpr_log(GPR_ERROR, "Could not create google default credentials."); + } + } + gpr_mu_unlock(&g_state_mu); + return result; +} + +void grpc_flush_cached_google_default_credentials(void) { + gpr_once_init(&g_once, init_default_credentials); + gpr_mu_lock(&g_state_mu); + if (default_credentials != NULL) { + grpc_channel_credentials_unref(default_credentials); + default_credentials = NULL; + } + compute_engine_detection_done = 0; + gpr_mu_unlock(&g_state_mu); +} + +/* -- Well known credentials path. -- */ + +static grpc_well_known_credentials_path_getter creds_path_getter = NULL; + +char *grpc_get_well_known_google_credentials_file_path(void) { + if (creds_path_getter != NULL) return creds_path_getter(); + return grpc_get_well_known_google_credentials_file_path_impl(); +} + +void grpc_override_well_known_credentials_path_getter( + grpc_well_known_credentials_path_getter getter) { + creds_path_getter = getter; +} diff --git a/src/core/lib/security/handshake.c b/src/core/lib/security/handshake.c new file mode 100644 index 0000000000..adb6d7fe4e --- /dev/null +++ b/src/core/lib/security/handshake.c @@ -0,0 +1,336 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/security/handshake.h" + +#include <stdbool.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/slice_buffer.h> +#include "src/core/lib/security/secure_endpoint.h" +#include "src/core/lib/security/security_context.h" + +#define GRPC_INITIAL_HANDSHAKE_BUFFER_SIZE 256 + +typedef struct { + grpc_security_connector *connector; + tsi_handshaker *handshaker; + bool is_client_side; + unsigned char *handshake_buffer; + size_t handshake_buffer_size; + grpc_endpoint *wrapped_endpoint; + grpc_endpoint *secure_endpoint; + gpr_slice_buffer left_overs; + gpr_slice_buffer incoming; + gpr_slice_buffer outgoing; + grpc_security_handshake_done_cb cb; + void *user_data; + grpc_closure on_handshake_data_sent_to_peer; + grpc_closure on_handshake_data_received_from_peer; + grpc_auth_context *auth_context; +} grpc_security_handshake; + +static void on_handshake_data_received_from_peer(grpc_exec_ctx *exec_ctx, + void *setup, bool success); + +static void on_handshake_data_sent_to_peer(grpc_exec_ctx *exec_ctx, void *setup, + bool success); + +static void security_connector_remove_handshake(grpc_security_handshake *h) { + GPR_ASSERT(!h->is_client_side); + grpc_security_connector_handshake_list *node; + grpc_security_connector_handshake_list *tmp; + grpc_server_security_connector *sc = + (grpc_server_security_connector *)h->connector; + gpr_mu_lock(&sc->mu); + node = sc->handshaking_handshakes; + if (node && node->handshake == h) { + sc->handshaking_handshakes = node->next; + gpr_free(node); + gpr_mu_unlock(&sc->mu); + return; + } + while (node) { + if (node->next->handshake == h) { + tmp = node->next; + node->next = node->next->next; + gpr_free(tmp); + gpr_mu_unlock(&sc->mu); + return; + } + node = node->next; + } + gpr_mu_unlock(&sc->mu); +} + +static void security_handshake_done(grpc_exec_ctx *exec_ctx, + grpc_security_handshake *h, + int is_success) { + if (!h->is_client_side) { + security_connector_remove_handshake(h); + } + if (is_success) { + h->cb(exec_ctx, h->user_data, GRPC_SECURITY_OK, h->secure_endpoint, + h->auth_context); + } else { + if (h->secure_endpoint != NULL) { + grpc_endpoint_shutdown(exec_ctx, h->secure_endpoint); + grpc_endpoint_destroy(exec_ctx, h->secure_endpoint); + } else { + grpc_endpoint_destroy(exec_ctx, h->wrapped_endpoint); + } + h->cb(exec_ctx, h->user_data, GRPC_SECURITY_ERROR, NULL, NULL); + } + if (h->handshaker != NULL) tsi_handshaker_destroy(h->handshaker); + if (h->handshake_buffer != NULL) gpr_free(h->handshake_buffer); + gpr_slice_buffer_destroy(&h->left_overs); + gpr_slice_buffer_destroy(&h->outgoing); + gpr_slice_buffer_destroy(&h->incoming); + GRPC_AUTH_CONTEXT_UNREF(h->auth_context, "handshake"); + GRPC_SECURITY_CONNECTOR_UNREF(h->connector, "handshake"); + gpr_free(h); +} + +static void on_peer_checked(grpc_exec_ctx *exec_ctx, void *user_data, + grpc_security_status status, + grpc_auth_context *auth_context) { + grpc_security_handshake *h = user_data; + tsi_frame_protector *protector; + tsi_result result; + if (status != GRPC_SECURITY_OK) { + gpr_log(GPR_ERROR, "Error checking peer."); + security_handshake_done(exec_ctx, h, 0); + return; + } + h->auth_context = GRPC_AUTH_CONTEXT_REF(auth_context, "handshake"); + result = + tsi_handshaker_create_frame_protector(h->handshaker, NULL, &protector); + if (result != TSI_OK) { + gpr_log(GPR_ERROR, "Frame protector creation failed with error %s.", + tsi_result_to_string(result)); + security_handshake_done(exec_ctx, h, 0); + return; + } + h->secure_endpoint = + grpc_secure_endpoint_create(protector, h->wrapped_endpoint, + h->left_overs.slices, h->left_overs.count); + h->left_overs.count = 0; + h->left_overs.length = 0; + security_handshake_done(exec_ctx, h, 1); + return; +} + +static void check_peer(grpc_exec_ctx *exec_ctx, grpc_security_handshake *h) { + tsi_peer peer; + tsi_result result = tsi_handshaker_extract_peer(h->handshaker, &peer); + + if (result != TSI_OK) { + gpr_log(GPR_ERROR, "Peer extraction failed with error %s", + tsi_result_to_string(result)); + security_handshake_done(exec_ctx, h, 0); + return; + } + grpc_security_connector_check_peer(exec_ctx, h->connector, peer, + on_peer_checked, h); +} + +static void send_handshake_bytes_to_peer(grpc_exec_ctx *exec_ctx, + grpc_security_handshake *h) { + size_t offset = 0; + tsi_result result = TSI_OK; + gpr_slice to_send; + + do { + size_t to_send_size = h->handshake_buffer_size - offset; + result = tsi_handshaker_get_bytes_to_send_to_peer( + h->handshaker, h->handshake_buffer + offset, &to_send_size); + offset += to_send_size; + if (result == TSI_INCOMPLETE_DATA) { + h->handshake_buffer_size *= 2; + h->handshake_buffer = + gpr_realloc(h->handshake_buffer, h->handshake_buffer_size); + } + } while (result == TSI_INCOMPLETE_DATA); + + if (result != TSI_OK) { + gpr_log(GPR_ERROR, "Handshake failed with error %s", + tsi_result_to_string(result)); + security_handshake_done(exec_ctx, h, 0); + return; + } + + to_send = + gpr_slice_from_copied_buffer((const char *)h->handshake_buffer, offset); + gpr_slice_buffer_reset_and_unref(&h->outgoing); + gpr_slice_buffer_add(&h->outgoing, to_send); + /* TODO(klempner,jboeuf): This should probably use the client setup + deadline */ + grpc_endpoint_write(exec_ctx, h->wrapped_endpoint, &h->outgoing, + &h->on_handshake_data_sent_to_peer); +} + +static void on_handshake_data_received_from_peer(grpc_exec_ctx *exec_ctx, + void *handshake, + bool success) { + grpc_security_handshake *h = handshake; + size_t consumed_slice_size = 0; + tsi_result result = TSI_OK; + size_t i; + size_t num_left_overs; + int has_left_overs_in_current_slice = 0; + + if (!success) { + gpr_log(GPR_ERROR, "Read failed."); + security_handshake_done(exec_ctx, h, 0); + return; + } + + for (i = 0; i < h->incoming.count; i++) { + consumed_slice_size = GPR_SLICE_LENGTH(h->incoming.slices[i]); + result = tsi_handshaker_process_bytes_from_peer( + h->handshaker, GPR_SLICE_START_PTR(h->incoming.slices[i]), + &consumed_slice_size); + if (!tsi_handshaker_is_in_progress(h->handshaker)) break; + } + + if (tsi_handshaker_is_in_progress(h->handshaker)) { + /* We may need more data. */ + if (result == TSI_INCOMPLETE_DATA) { + grpc_endpoint_read(exec_ctx, h->wrapped_endpoint, &h->incoming, + &h->on_handshake_data_received_from_peer); + return; + } else { + send_handshake_bytes_to_peer(exec_ctx, h); + return; + } + } + + if (result != TSI_OK) { + gpr_log(GPR_ERROR, "Handshake failed with error %s", + tsi_result_to_string(result)); + security_handshake_done(exec_ctx, h, 0); + return; + } + + /* Handshake is done and successful this point. */ + has_left_overs_in_current_slice = + (consumed_slice_size < GPR_SLICE_LENGTH(h->incoming.slices[i])); + num_left_overs = + (has_left_overs_in_current_slice ? 1 : 0) + h->incoming.count - i - 1; + if (num_left_overs == 0) { + check_peer(exec_ctx, h); + return; + } + + /* Put the leftovers in our buffer (ownership transfered). */ + if (has_left_overs_in_current_slice) { + gpr_slice_buffer_add( + &h->left_overs, + gpr_slice_split_tail(&h->incoming.slices[i], consumed_slice_size)); + gpr_slice_unref( + h->incoming.slices[i]); /* split_tail above increments refcount. */ + } + gpr_slice_buffer_addn( + &h->left_overs, &h->incoming.slices[i + 1], + num_left_overs - (size_t)has_left_overs_in_current_slice); + check_peer(exec_ctx, h); +} + +/* If handshake is NULL, the handshake is done. */ +static void on_handshake_data_sent_to_peer(grpc_exec_ctx *exec_ctx, + void *handshake, bool success) { + grpc_security_handshake *h = handshake; + + /* Make sure that write is OK. */ + if (!success) { + gpr_log(GPR_ERROR, "Write failed."); + if (handshake != NULL) security_handshake_done(exec_ctx, h, 0); + return; + } + + /* We may be done. */ + if (tsi_handshaker_is_in_progress(h->handshaker)) { + /* TODO(klempner,jboeuf): This should probably use the client setup + deadline */ + grpc_endpoint_read(exec_ctx, h->wrapped_endpoint, &h->incoming, + &h->on_handshake_data_received_from_peer); + } else { + check_peer(exec_ctx, h); + } +} + +void grpc_do_security_handshake(grpc_exec_ctx *exec_ctx, + tsi_handshaker *handshaker, + grpc_security_connector *connector, + bool is_client_side, + grpc_endpoint *nonsecure_endpoint, + grpc_security_handshake_done_cb cb, + void *user_data) { + grpc_security_connector_handshake_list *handshake_node; + grpc_security_handshake *h = gpr_malloc(sizeof(grpc_security_handshake)); + memset(h, 0, sizeof(grpc_security_handshake)); + h->handshaker = handshaker; + h->connector = GRPC_SECURITY_CONNECTOR_REF(connector, "handshake"); + h->is_client_side = is_client_side; + h->handshake_buffer_size = GRPC_INITIAL_HANDSHAKE_BUFFER_SIZE; + h->handshake_buffer = gpr_malloc(h->handshake_buffer_size); + h->wrapped_endpoint = nonsecure_endpoint; + h->user_data = user_data; + h->cb = cb; + grpc_closure_init(&h->on_handshake_data_sent_to_peer, + on_handshake_data_sent_to_peer, h); + grpc_closure_init(&h->on_handshake_data_received_from_peer, + on_handshake_data_received_from_peer, h); + gpr_slice_buffer_init(&h->left_overs); + gpr_slice_buffer_init(&h->outgoing); + gpr_slice_buffer_init(&h->incoming); + if (!is_client_side) { + grpc_server_security_connector *server_connector = + (grpc_server_security_connector *)connector; + handshake_node = gpr_malloc(sizeof(grpc_security_connector_handshake_list)); + handshake_node->handshake = h; + gpr_mu_lock(&server_connector->mu); + handshake_node->next = server_connector->handshaking_handshakes; + server_connector->handshaking_handshakes = handshake_node; + gpr_mu_unlock(&server_connector->mu); + } + send_handshake_bytes_to_peer(exec_ctx, h); +} + +void grpc_security_handshake_shutdown(grpc_exec_ctx *exec_ctx, + void *handshake) { + grpc_security_handshake *h = handshake; + grpc_endpoint_shutdown(exec_ctx, h->wrapped_endpoint); +} diff --git a/src/core/lib/security/handshake.h b/src/core/lib/security/handshake.h new file mode 100644 index 0000000000..b5d7bb3282 --- /dev/null +++ b/src/core/lib/security/handshake.h @@ -0,0 +1,51 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SECURITY_HANDSHAKE_H +#define GRPC_CORE_LIB_SECURITY_HANDSHAKE_H + +#include "src/core/lib/iomgr/endpoint.h" +#include "src/core/lib/security/security_connector.h" + +/* Calls the callback upon completion. Takes owership of handshaker. */ +void grpc_do_security_handshake(grpc_exec_ctx *exec_ctx, + tsi_handshaker *handshaker, + grpc_security_connector *connector, + bool is_client_side, + grpc_endpoint *nonsecure_endpoint, + grpc_security_handshake_done_cb cb, + void *user_data); + +void grpc_security_handshake_shutdown(grpc_exec_ctx *exec_ctx, void *handshake); + +#endif /* GRPC_CORE_LIB_SECURITY_HANDSHAKE_H */ diff --git a/src/core/lib/security/json_token.c b/src/core/lib/security/json_token.c new file mode 100644 index 0000000000..97054286d9 --- /dev/null +++ b/src/core/lib/security/json_token.c @@ -0,0 +1,411 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/security/json_token.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> + +#include "src/core/lib/security/b64.h" +#include "src/core/lib/support/string.h" + +#include <openssl/bio.h> +#include <openssl/evp.h> +#include <openssl/pem.h> + +/* --- Constants. --- */ + +/* 1 hour max. */ +gpr_timespec grpc_max_auth_token_lifetime() { + gpr_timespec out; + out.tv_sec = 3600; + out.tv_nsec = 0; + out.clock_type = GPR_TIMESPAN; + return out; +} + +#define GRPC_JWT_RSA_SHA256_ALGORITHM "RS256" +#define GRPC_JWT_TYPE "JWT" + +/* --- Override for testing. --- */ + +static grpc_jwt_encode_and_sign_override g_jwt_encode_and_sign_override = NULL; + +/* --- grpc_auth_json_key. --- */ + +static const char *json_get_string_property(const grpc_json *json, + const char *prop_name) { + grpc_json *child; + for (child = json->child; child != NULL; child = child->next) { + if (strcmp(child->key, prop_name) == 0) break; + } + if (child == NULL || child->type != GRPC_JSON_STRING) { + gpr_log(GPR_ERROR, "Invalid or missing %s property.", prop_name); + return NULL; + } + return child->value; +} + +static int set_json_key_string_property(const grpc_json *json, + const char *prop_name, + char **json_key_field) { + const char *prop_value = json_get_string_property(json, prop_name); + if (prop_value == NULL) return 0; + *json_key_field = gpr_strdup(prop_value); + return 1; +} + +int grpc_auth_json_key_is_valid(const grpc_auth_json_key *json_key) { + return (json_key != NULL) && + strcmp(json_key->type, GRPC_AUTH_JSON_TYPE_INVALID); +} + +grpc_auth_json_key grpc_auth_json_key_create_from_json(const grpc_json *json) { + grpc_auth_json_key result; + BIO *bio = NULL; + const char *prop_value; + int success = 0; + + memset(&result, 0, sizeof(grpc_auth_json_key)); + result.type = GRPC_AUTH_JSON_TYPE_INVALID; + if (json == NULL) { + gpr_log(GPR_ERROR, "Invalid json."); + goto end; + } + + prop_value = json_get_string_property(json, "type"); + if (prop_value == NULL || + strcmp(prop_value, GRPC_AUTH_JSON_TYPE_SERVICE_ACCOUNT)) { + goto end; + } + result.type = GRPC_AUTH_JSON_TYPE_SERVICE_ACCOUNT; + + if (!set_json_key_string_property(json, "private_key_id", + &result.private_key_id) || + !set_json_key_string_property(json, "client_id", &result.client_id) || + !set_json_key_string_property(json, "client_email", + &result.client_email)) { + goto end; + } + + prop_value = json_get_string_property(json, "private_key"); + if (prop_value == NULL) { + goto end; + } + bio = BIO_new(BIO_s_mem()); + success = BIO_puts(bio, prop_value); + if ((success < 0) || ((size_t)success != strlen(prop_value))) { + gpr_log(GPR_ERROR, "Could not write into openssl BIO."); + goto end; + } + result.private_key = PEM_read_bio_RSAPrivateKey(bio, NULL, NULL, ""); + if (result.private_key == NULL) { + gpr_log(GPR_ERROR, "Could not deserialize private key."); + goto end; + } + success = 1; + +end: + if (bio != NULL) BIO_free(bio); + if (!success) grpc_auth_json_key_destruct(&result); + return result; +} + +grpc_auth_json_key grpc_auth_json_key_create_from_string( + const char *json_string) { + char *scratchpad = gpr_strdup(json_string); + grpc_json *json = grpc_json_parse_string(scratchpad); + grpc_auth_json_key result = grpc_auth_json_key_create_from_json(json); + if (json != NULL) grpc_json_destroy(json); + gpr_free(scratchpad); + return result; +} + +void grpc_auth_json_key_destruct(grpc_auth_json_key *json_key) { + if (json_key == NULL) return; + json_key->type = GRPC_AUTH_JSON_TYPE_INVALID; + if (json_key->client_id != NULL) { + gpr_free(json_key->client_id); + json_key->client_id = NULL; + } + if (json_key->private_key_id != NULL) { + gpr_free(json_key->private_key_id); + json_key->private_key_id = NULL; + } + if (json_key->client_email != NULL) { + gpr_free(json_key->client_email); + json_key->client_email = NULL; + } + if (json_key->private_key != NULL) { + RSA_free(json_key->private_key); + json_key->private_key = NULL; + } +} + +/* --- jwt encoding and signature. --- */ + +static grpc_json *create_child(grpc_json *brother, grpc_json *parent, + const char *key, const char *value, + grpc_json_type type) { + grpc_json *child = grpc_json_create(type); + if (brother) brother->next = child; + if (!parent->child) parent->child = child; + child->parent = parent; + child->value = value; + child->key = key; + return child; +} + +static char *encoded_jwt_header(const char *key_id, const char *algorithm) { + grpc_json *json = grpc_json_create(GRPC_JSON_OBJECT); + grpc_json *child = NULL; + char *json_str = NULL; + char *result = NULL; + + child = create_child(NULL, json, "alg", algorithm, GRPC_JSON_STRING); + child = create_child(child, json, "typ", GRPC_JWT_TYPE, GRPC_JSON_STRING); + create_child(child, json, "kid", key_id, GRPC_JSON_STRING); + + json_str = grpc_json_dump_to_string(json, 0); + result = grpc_base64_encode(json_str, strlen(json_str), 1, 0); + gpr_free(json_str); + grpc_json_destroy(json); + return result; +} + +static char *encoded_jwt_claim(const grpc_auth_json_key *json_key, + const char *audience, + gpr_timespec token_lifetime, const char *scope) { + grpc_json *json = grpc_json_create(GRPC_JSON_OBJECT); + grpc_json *child = NULL; + char *json_str = NULL; + char *result = NULL; + gpr_timespec now = gpr_now(GPR_CLOCK_REALTIME); + gpr_timespec expiration = gpr_time_add(now, token_lifetime); + char now_str[GPR_LTOA_MIN_BUFSIZE]; + char expiration_str[GPR_LTOA_MIN_BUFSIZE]; + if (gpr_time_cmp(token_lifetime, grpc_max_auth_token_lifetime()) > 0) { + gpr_log(GPR_INFO, "Cropping token lifetime to maximum allowed value."); + expiration = gpr_time_add(now, grpc_max_auth_token_lifetime()); + } + int64_ttoa(now.tv_sec, now_str); + int64_ttoa(expiration.tv_sec, expiration_str); + + child = + create_child(NULL, json, "iss", json_key->client_email, GRPC_JSON_STRING); + if (scope != NULL) { + child = create_child(child, json, "scope", scope, GRPC_JSON_STRING); + } else { + /* Unscoped JWTs need a sub field. */ + child = create_child(child, json, "sub", json_key->client_email, + GRPC_JSON_STRING); + } + + child = create_child(child, json, "aud", audience, GRPC_JSON_STRING); + child = create_child(child, json, "iat", now_str, GRPC_JSON_NUMBER); + create_child(child, json, "exp", expiration_str, GRPC_JSON_NUMBER); + + json_str = grpc_json_dump_to_string(json, 0); + result = grpc_base64_encode(json_str, strlen(json_str), 1, 0); + gpr_free(json_str); + grpc_json_destroy(json); + return result; +} + +static char *dot_concat_and_free_strings(char *str1, char *str2) { + size_t str1_len = strlen(str1); + size_t str2_len = strlen(str2); + size_t result_len = str1_len + 1 /* dot */ + str2_len; + char *result = gpr_malloc(result_len + 1 /* NULL terminated */); + char *current = result; + memcpy(current, str1, str1_len); + current += str1_len; + *(current++) = '.'; + memcpy(current, str2, str2_len); + current += str2_len; + GPR_ASSERT(current >= result); + GPR_ASSERT((uintptr_t)(current - result) == result_len); + *current = '\0'; + gpr_free(str1); + gpr_free(str2); + return result; +} + +const EVP_MD *openssl_digest_from_algorithm(const char *algorithm) { + if (strcmp(algorithm, GRPC_JWT_RSA_SHA256_ALGORITHM) == 0) { + return EVP_sha256(); + } else { + gpr_log(GPR_ERROR, "Unknown algorithm %s.", algorithm); + return NULL; + } +} + +char *compute_and_encode_signature(const grpc_auth_json_key *json_key, + const char *signature_algorithm, + const char *to_sign) { + const EVP_MD *md = openssl_digest_from_algorithm(signature_algorithm); + EVP_MD_CTX *md_ctx = NULL; + EVP_PKEY *key = EVP_PKEY_new(); + size_t sig_len = 0; + unsigned char *sig = NULL; + char *result = NULL; + if (md == NULL) return NULL; + md_ctx = EVP_MD_CTX_create(); + if (md_ctx == NULL) { + gpr_log(GPR_ERROR, "Could not create MD_CTX"); + goto end; + } + EVP_PKEY_set1_RSA(key, json_key->private_key); + if (EVP_DigestSignInit(md_ctx, NULL, md, NULL, key) != 1) { + gpr_log(GPR_ERROR, "DigestInit failed."); + goto end; + } + if (EVP_DigestSignUpdate(md_ctx, to_sign, strlen(to_sign)) != 1) { + gpr_log(GPR_ERROR, "DigestUpdate failed."); + goto end; + } + if (EVP_DigestSignFinal(md_ctx, NULL, &sig_len) != 1) { + gpr_log(GPR_ERROR, "DigestFinal (get signature length) failed."); + goto end; + } + sig = gpr_malloc(sig_len); + if (EVP_DigestSignFinal(md_ctx, sig, &sig_len) != 1) { + gpr_log(GPR_ERROR, "DigestFinal (signature compute) failed."); + goto end; + } + result = grpc_base64_encode(sig, sig_len, 1, 0); + +end: + if (key != NULL) EVP_PKEY_free(key); + if (md_ctx != NULL) EVP_MD_CTX_destroy(md_ctx); + if (sig != NULL) gpr_free(sig); + return result; +} + +char *grpc_jwt_encode_and_sign(const grpc_auth_json_key *json_key, + const char *audience, + gpr_timespec token_lifetime, const char *scope) { + if (g_jwt_encode_and_sign_override != NULL) { + return g_jwt_encode_and_sign_override(json_key, audience, token_lifetime, + scope); + } else { + const char *sig_algo = GRPC_JWT_RSA_SHA256_ALGORITHM; + char *to_sign = dot_concat_and_free_strings( + encoded_jwt_header(json_key->private_key_id, sig_algo), + encoded_jwt_claim(json_key, audience, token_lifetime, scope)); + char *sig = compute_and_encode_signature(json_key, sig_algo, to_sign); + if (sig == NULL) { + gpr_free(to_sign); + return NULL; + } + return dot_concat_and_free_strings(to_sign, sig); + } +} + +void grpc_jwt_encode_and_sign_set_override( + grpc_jwt_encode_and_sign_override func) { + g_jwt_encode_and_sign_override = func; +} + +/* --- grpc_auth_refresh_token --- */ + +int grpc_auth_refresh_token_is_valid( + const grpc_auth_refresh_token *refresh_token) { + return (refresh_token != NULL) && + strcmp(refresh_token->type, GRPC_AUTH_JSON_TYPE_INVALID); +} + +grpc_auth_refresh_token grpc_auth_refresh_token_create_from_json( + const grpc_json *json) { + grpc_auth_refresh_token result; + const char *prop_value; + int success = 0; + + memset(&result, 0, sizeof(grpc_auth_refresh_token)); + result.type = GRPC_AUTH_JSON_TYPE_INVALID; + if (json == NULL) { + gpr_log(GPR_ERROR, "Invalid json."); + goto end; + } + + prop_value = json_get_string_property(json, "type"); + if (prop_value == NULL || + strcmp(prop_value, GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER)) { + goto end; + } + result.type = GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER; + + if (!set_json_key_string_property(json, "client_secret", + &result.client_secret) || + !set_json_key_string_property(json, "client_id", &result.client_id) || + !set_json_key_string_property(json, "refresh_token", + &result.refresh_token)) { + goto end; + } + success = 1; + +end: + if (!success) grpc_auth_refresh_token_destruct(&result); + return result; +} + +grpc_auth_refresh_token grpc_auth_refresh_token_create_from_string( + const char *json_string) { + char *scratchpad = gpr_strdup(json_string); + grpc_json *json = grpc_json_parse_string(scratchpad); + grpc_auth_refresh_token result = + grpc_auth_refresh_token_create_from_json(json); + if (json != NULL) grpc_json_destroy(json); + gpr_free(scratchpad); + return result; +} + +void grpc_auth_refresh_token_destruct(grpc_auth_refresh_token *refresh_token) { + if (refresh_token == NULL) return; + refresh_token->type = GRPC_AUTH_JSON_TYPE_INVALID; + if (refresh_token->client_id != NULL) { + gpr_free(refresh_token->client_id); + refresh_token->client_id = NULL; + } + if (refresh_token->client_secret != NULL) { + gpr_free(refresh_token->client_secret); + refresh_token->client_secret = NULL; + } + if (refresh_token->refresh_token != NULL) { + gpr_free(refresh_token->refresh_token); + refresh_token->refresh_token = NULL; + } +} diff --git a/src/core/lib/security/json_token.h b/src/core/lib/security/json_token.h new file mode 100644 index 0000000000..376fb03875 --- /dev/null +++ b/src/core/lib/security/json_token.h @@ -0,0 +1,118 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SECURITY_JSON_TOKEN_H +#define GRPC_CORE_LIB_SECURITY_JSON_TOKEN_H + +#include <grpc/support/slice.h> +#include <openssl/rsa.h> + +#include "src/core/lib/json/json.h" + +/* --- Constants. --- */ + +#define GRPC_JWT_OAUTH2_AUDIENCE "https://www.googleapis.com/oauth2/v3/token" + +#define GRPC_AUTH_JSON_TYPE_INVALID "invalid" +#define GRPC_AUTH_JSON_TYPE_SERVICE_ACCOUNT "service_account" +#define GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER "authorized_user" + +/* --- auth_json_key parsing. --- */ + +typedef struct { + const char *type; + char *private_key_id; + char *client_id; + char *client_email; + RSA *private_key; +} grpc_auth_json_key; + +/* Returns 1 if the object is valid, 0 otherwise. */ +int grpc_auth_json_key_is_valid(const grpc_auth_json_key *json_key); + +/* Creates a json_key object from string. Returns an invalid object if a parsing + error has been encountered. */ +grpc_auth_json_key grpc_auth_json_key_create_from_string( + const char *json_string); + +/* Creates a json_key object from parsed json. Returns an invalid object if a + parsing error has been encountered. */ +grpc_auth_json_key grpc_auth_json_key_create_from_json(const grpc_json *json); + +/* Destructs the object. */ +void grpc_auth_json_key_destruct(grpc_auth_json_key *json_key); + +/* --- json token encoding and signing. --- */ + +/* Caller is responsible for calling gpr_free on the returned value. May return + NULL on invalid input. The scope parameter may be NULL. */ +char *grpc_jwt_encode_and_sign(const grpc_auth_json_key *json_key, + const char *audience, + gpr_timespec token_lifetime, const char *scope); + +/* Override encode_and_sign function for testing. */ +typedef char *(*grpc_jwt_encode_and_sign_override)( + const grpc_auth_json_key *json_key, const char *audience, + gpr_timespec token_lifetime, const char *scope); + +/* Set a custom encode_and_sign override for testing. */ +void grpc_jwt_encode_and_sign_set_override( + grpc_jwt_encode_and_sign_override func); + +/* --- auth_refresh_token parsing. --- */ + +typedef struct { + const char *type; + char *client_id; + char *client_secret; + char *refresh_token; +} grpc_auth_refresh_token; + +/* Returns 1 if the object is valid, 0 otherwise. */ +int grpc_auth_refresh_token_is_valid( + const grpc_auth_refresh_token *refresh_token); + +/* Creates a refresh token object from string. Returns an invalid object if a + parsing error has been encountered. */ +grpc_auth_refresh_token grpc_auth_refresh_token_create_from_string( + const char *json_string); + +/* Creates a refresh token object from parsed json. Returns an invalid object if + a parsing error has been encountered. */ +grpc_auth_refresh_token grpc_auth_refresh_token_create_from_json( + const grpc_json *json); + +/* Destructs the object. */ +void grpc_auth_refresh_token_destruct(grpc_auth_refresh_token *refresh_token); + +#endif /* GRPC_CORE_LIB_SECURITY_JSON_TOKEN_H */ diff --git a/src/core/lib/security/jwt_verifier.c b/src/core/lib/security/jwt_verifier.c new file mode 100644 index 0000000000..460b92f9a0 --- /dev/null +++ b/src/core/lib/security/jwt_verifier.c @@ -0,0 +1,843 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/security/jwt_verifier.h" + +#include <limits.h> +#include <string.h> + +#include "src/core/lib/http/httpcli.h" +#include "src/core/lib/security/b64.h" +#include "src/core/lib/tsi/ssl_types.h" + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> +#include <grpc/support/sync.h> +#include <openssl/pem.h> + +/* --- Utils. --- */ + +const char *grpc_jwt_verifier_status_to_string( + grpc_jwt_verifier_status status) { + switch (status) { + case GRPC_JWT_VERIFIER_OK: + return "OK"; + case GRPC_JWT_VERIFIER_BAD_SIGNATURE: + return "BAD_SIGNATURE"; + case GRPC_JWT_VERIFIER_BAD_FORMAT: + return "BAD_FORMAT"; + case GRPC_JWT_VERIFIER_BAD_AUDIENCE: + return "BAD_AUDIENCE"; + case GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR: + return "KEY_RETRIEVAL_ERROR"; + case GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE: + return "TIME_CONSTRAINT_FAILURE"; + case GRPC_JWT_VERIFIER_GENERIC_ERROR: + return "GENERIC_ERROR"; + default: + return "UNKNOWN"; + } +} + +static const EVP_MD *evp_md_from_alg(const char *alg) { + if (strcmp(alg, "RS256") == 0) { + return EVP_sha256(); + } else if (strcmp(alg, "RS384") == 0) { + return EVP_sha384(); + } else if (strcmp(alg, "RS512") == 0) { + return EVP_sha512(); + } else { + return NULL; + } +} + +static grpc_json *parse_json_part_from_jwt(const char *str, size_t len, + gpr_slice *buffer) { + grpc_json *json; + + *buffer = grpc_base64_decode_with_len(str, len, 1); + if (GPR_SLICE_IS_EMPTY(*buffer)) { + gpr_log(GPR_ERROR, "Invalid base64."); + return NULL; + } + json = grpc_json_parse_string_with_len((char *)GPR_SLICE_START_PTR(*buffer), + GPR_SLICE_LENGTH(*buffer)); + if (json == NULL) { + gpr_slice_unref(*buffer); + gpr_log(GPR_ERROR, "JSON parsing error."); + } + return json; +} + +static const char *validate_string_field(const grpc_json *json, + const char *key) { + if (json->type != GRPC_JSON_STRING) { + gpr_log(GPR_ERROR, "Invalid %s field [%s]", key, json->value); + return NULL; + } + return json->value; +} + +static gpr_timespec validate_time_field(const grpc_json *json, + const char *key) { + gpr_timespec result = gpr_time_0(GPR_CLOCK_REALTIME); + if (json->type != GRPC_JSON_NUMBER) { + gpr_log(GPR_ERROR, "Invalid %s field [%s]", key, json->value); + return result; + } + result.tv_sec = strtol(json->value, NULL, 10); + return result; +} + +/* --- JOSE header. see http://tools.ietf.org/html/rfc7515#section-4 --- */ + +typedef struct { + const char *alg; + const char *kid; + const char *typ; + /* TODO(jboeuf): Add others as needed (jku, jwk, x5u, x5c and so on...). */ + gpr_slice buffer; +} jose_header; + +static void jose_header_destroy(jose_header *h) { + gpr_slice_unref(h->buffer); + gpr_free(h); +} + +/* Takes ownership of json and buffer. */ +static jose_header *jose_header_from_json(grpc_json *json, gpr_slice buffer) { + grpc_json *cur; + jose_header *h = gpr_malloc(sizeof(jose_header)); + memset(h, 0, sizeof(jose_header)); + h->buffer = buffer; + for (cur = json->child; cur != NULL; cur = cur->next) { + if (strcmp(cur->key, "alg") == 0) { + /* We only support RSA-1.5 signatures for now. + Beware of this if we add HMAC support: + https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/ + */ + if (cur->type != GRPC_JSON_STRING || strncmp(cur->value, "RS", 2) || + evp_md_from_alg(cur->value) == NULL) { + gpr_log(GPR_ERROR, "Invalid alg field [%s]", cur->value); + goto error; + } + h->alg = cur->value; + } else if (strcmp(cur->key, "typ") == 0) { + h->typ = validate_string_field(cur, "typ"); + if (h->typ == NULL) goto error; + } else if (strcmp(cur->key, "kid") == 0) { + h->kid = validate_string_field(cur, "kid"); + if (h->kid == NULL) goto error; + } + } + if (h->alg == NULL) { + gpr_log(GPR_ERROR, "Missing alg field."); + goto error; + } + grpc_json_destroy(json); + h->buffer = buffer; + return h; + +error: + grpc_json_destroy(json); + jose_header_destroy(h); + return NULL; +} + +/* --- JWT claims. see http://tools.ietf.org/html/rfc7519#section-4.1 */ + +struct grpc_jwt_claims { + /* Well known properties already parsed. */ + const char *sub; + const char *iss; + const char *aud; + const char *jti; + gpr_timespec iat; + gpr_timespec exp; + gpr_timespec nbf; + + grpc_json *json; + gpr_slice buffer; +}; + +void grpc_jwt_claims_destroy(grpc_jwt_claims *claims) { + grpc_json_destroy(claims->json); + gpr_slice_unref(claims->buffer); + gpr_free(claims); +} + +const grpc_json *grpc_jwt_claims_json(const grpc_jwt_claims *claims) { + if (claims == NULL) return NULL; + return claims->json; +} + +const char *grpc_jwt_claims_subject(const grpc_jwt_claims *claims) { + if (claims == NULL) return NULL; + return claims->sub; +} + +const char *grpc_jwt_claims_issuer(const grpc_jwt_claims *claims) { + if (claims == NULL) return NULL; + return claims->iss; +} + +const char *grpc_jwt_claims_id(const grpc_jwt_claims *claims) { + if (claims == NULL) return NULL; + return claims->jti; +} + +const char *grpc_jwt_claims_audience(const grpc_jwt_claims *claims) { + if (claims == NULL) return NULL; + return claims->aud; +} + +gpr_timespec grpc_jwt_claims_issued_at(const grpc_jwt_claims *claims) { + if (claims == NULL) return gpr_inf_past(GPR_CLOCK_REALTIME); + return claims->iat; +} + +gpr_timespec grpc_jwt_claims_expires_at(const grpc_jwt_claims *claims) { + if (claims == NULL) return gpr_inf_future(GPR_CLOCK_REALTIME); + return claims->exp; +} + +gpr_timespec grpc_jwt_claims_not_before(const grpc_jwt_claims *claims) { + if (claims == NULL) return gpr_inf_past(GPR_CLOCK_REALTIME); + return claims->nbf; +} + +/* Takes ownership of json and buffer even in case of failure. */ +grpc_jwt_claims *grpc_jwt_claims_from_json(grpc_json *json, gpr_slice buffer) { + grpc_json *cur; + grpc_jwt_claims *claims = gpr_malloc(sizeof(grpc_jwt_claims)); + memset(claims, 0, sizeof(grpc_jwt_claims)); + claims->json = json; + claims->buffer = buffer; + claims->iat = gpr_inf_past(GPR_CLOCK_REALTIME); + claims->nbf = gpr_inf_past(GPR_CLOCK_REALTIME); + claims->exp = gpr_inf_future(GPR_CLOCK_REALTIME); + + /* Per the spec, all fields are optional. */ + for (cur = json->child; cur != NULL; cur = cur->next) { + if (strcmp(cur->key, "sub") == 0) { + claims->sub = validate_string_field(cur, "sub"); + if (claims->sub == NULL) goto error; + } else if (strcmp(cur->key, "iss") == 0) { + claims->iss = validate_string_field(cur, "iss"); + if (claims->iss == NULL) goto error; + } else if (strcmp(cur->key, "aud") == 0) { + claims->aud = validate_string_field(cur, "aud"); + if (claims->aud == NULL) goto error; + } else if (strcmp(cur->key, "jti") == 0) { + claims->jti = validate_string_field(cur, "jti"); + if (claims->jti == NULL) goto error; + } else if (strcmp(cur->key, "iat") == 0) { + claims->iat = validate_time_field(cur, "iat"); + if (gpr_time_cmp(claims->iat, gpr_time_0(GPR_CLOCK_REALTIME)) == 0) + goto error; + } else if (strcmp(cur->key, "exp") == 0) { + claims->exp = validate_time_field(cur, "exp"); + if (gpr_time_cmp(claims->exp, gpr_time_0(GPR_CLOCK_REALTIME)) == 0) + goto error; + } else if (strcmp(cur->key, "nbf") == 0) { + claims->nbf = validate_time_field(cur, "nbf"); + if (gpr_time_cmp(claims->nbf, gpr_time_0(GPR_CLOCK_REALTIME)) == 0) + goto error; + } + } + return claims; + +error: + grpc_jwt_claims_destroy(claims); + return NULL; +} + +grpc_jwt_verifier_status grpc_jwt_claims_check(const grpc_jwt_claims *claims, + const char *audience) { + gpr_timespec skewed_now; + int audience_ok; + + GPR_ASSERT(claims != NULL); + + skewed_now = + gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_clock_skew); + if (gpr_time_cmp(skewed_now, claims->nbf) < 0) { + gpr_log(GPR_ERROR, "JWT is not valid yet."); + return GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE; + } + skewed_now = + gpr_time_sub(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_clock_skew); + if (gpr_time_cmp(skewed_now, claims->exp) > 0) { + gpr_log(GPR_ERROR, "JWT is expired."); + return GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE; + } + + if (audience == NULL) { + audience_ok = claims->aud == NULL; + } else { + audience_ok = claims->aud != NULL && strcmp(audience, claims->aud) == 0; + } + if (!audience_ok) { + gpr_log(GPR_ERROR, "Audience mismatch: expected %s and found %s.", + audience == NULL ? "NULL" : audience, + claims->aud == NULL ? "NULL" : claims->aud); + return GRPC_JWT_VERIFIER_BAD_AUDIENCE; + } + return GRPC_JWT_VERIFIER_OK; +} + +/* --- verifier_cb_ctx object. --- */ + +typedef struct { + grpc_jwt_verifier *verifier; + grpc_pollset *pollset; + jose_header *header; + grpc_jwt_claims *claims; + char *audience; + gpr_slice signature; + gpr_slice signed_data; + void *user_data; + grpc_jwt_verification_done_cb user_cb; +} verifier_cb_ctx; + +/* Takes ownership of the header, claims and signature. */ +static verifier_cb_ctx *verifier_cb_ctx_create( + grpc_jwt_verifier *verifier, grpc_pollset *pollset, jose_header *header, + grpc_jwt_claims *claims, const char *audience, gpr_slice signature, + const char *signed_jwt, size_t signed_jwt_len, void *user_data, + grpc_jwt_verification_done_cb cb) { + verifier_cb_ctx *ctx = gpr_malloc(sizeof(verifier_cb_ctx)); + memset(ctx, 0, sizeof(verifier_cb_ctx)); + ctx->verifier = verifier; + ctx->pollset = pollset; + ctx->header = header; + ctx->audience = gpr_strdup(audience); + ctx->claims = claims; + ctx->signature = signature; + ctx->signed_data = gpr_slice_from_copied_buffer(signed_jwt, signed_jwt_len); + ctx->user_data = user_data; + ctx->user_cb = cb; + return ctx; +} + +void verifier_cb_ctx_destroy(verifier_cb_ctx *ctx) { + if (ctx->audience != NULL) gpr_free(ctx->audience); + if (ctx->claims != NULL) grpc_jwt_claims_destroy(ctx->claims); + gpr_slice_unref(ctx->signature); + gpr_slice_unref(ctx->signed_data); + jose_header_destroy(ctx->header); + /* TODO: see what to do with claims... */ + gpr_free(ctx); +} + +/* --- grpc_jwt_verifier object. --- */ + +/* Clock skew defaults to one minute. */ +gpr_timespec grpc_jwt_verifier_clock_skew = {60, 0, GPR_TIMESPAN}; + +/* Max delay defaults to one minute. */ +gpr_timespec grpc_jwt_verifier_max_delay = {60, 0, GPR_TIMESPAN}; + +typedef struct { + char *email_domain; + char *key_url_prefix; +} email_key_mapping; + +struct grpc_jwt_verifier { + email_key_mapping *mappings; + size_t num_mappings; /* Should be very few, linear search ok. */ + size_t allocated_mappings; + grpc_httpcli_context http_ctx; +}; + +static grpc_json *json_from_http(const grpc_httpcli_response *response) { + grpc_json *json = NULL; + + if (response == NULL) { + gpr_log(GPR_ERROR, "HTTP response is NULL."); + return NULL; + } + if (response->status != 200) { + gpr_log(GPR_ERROR, "Call to http server failed with error %d.", + response->status); + return NULL; + } + + json = grpc_json_parse_string_with_len(response->body, response->body_length); + if (json == NULL) { + gpr_log(GPR_ERROR, "Invalid JSON found in response."); + } + return json; +} + +static const grpc_json *find_property_by_name(const grpc_json *json, + const char *name) { + const grpc_json *cur; + for (cur = json->child; cur != NULL; cur = cur->next) { + if (strcmp(cur->key, name) == 0) return cur; + } + return NULL; +} + +static EVP_PKEY *extract_pkey_from_x509(const char *x509_str) { + X509 *x509 = NULL; + EVP_PKEY *result = NULL; + BIO *bio = BIO_new(BIO_s_mem()); + size_t len = strlen(x509_str); + GPR_ASSERT(len < INT_MAX); + BIO_write(bio, x509_str, (int)len); + x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); + if (x509 == NULL) { + gpr_log(GPR_ERROR, "Unable to parse x509 cert."); + goto end; + } + result = X509_get_pubkey(x509); + if (result == NULL) { + gpr_log(GPR_ERROR, "Cannot find public key in X509 cert."); + } + +end: + BIO_free(bio); + if (x509 != NULL) X509_free(x509); + return result; +} + +static BIGNUM *bignum_from_base64(const char *b64) { + BIGNUM *result = NULL; + gpr_slice bin; + + if (b64 == NULL) return NULL; + bin = grpc_base64_decode(b64, 1); + if (GPR_SLICE_IS_EMPTY(bin)) { + gpr_log(GPR_ERROR, "Invalid base64 for big num."); + return NULL; + } + result = BN_bin2bn(GPR_SLICE_START_PTR(bin), + TSI_SIZE_AS_SIZE(GPR_SLICE_LENGTH(bin)), NULL); + gpr_slice_unref(bin); + return result; +} + +static EVP_PKEY *pkey_from_jwk(const grpc_json *json, const char *kty) { + const grpc_json *key_prop; + RSA *rsa = NULL; + EVP_PKEY *result = NULL; + + GPR_ASSERT(kty != NULL && json != NULL); + if (strcmp(kty, "RSA") != 0) { + gpr_log(GPR_ERROR, "Unsupported key type %s.", kty); + goto end; + } + rsa = RSA_new(); + if (rsa == NULL) { + gpr_log(GPR_ERROR, "Could not create rsa key."); + goto end; + } + for (key_prop = json->child; key_prop != NULL; key_prop = key_prop->next) { + if (strcmp(key_prop->key, "n") == 0) { + rsa->n = bignum_from_base64(validate_string_field(key_prop, "n")); + if (rsa->n == NULL) goto end; + } else if (strcmp(key_prop->key, "e") == 0) { + rsa->e = bignum_from_base64(validate_string_field(key_prop, "e")); + if (rsa->e == NULL) goto end; + } + } + if (rsa->e == NULL || rsa->n == NULL) { + gpr_log(GPR_ERROR, "Missing RSA public key field."); + goto end; + } + result = EVP_PKEY_new(); + EVP_PKEY_set1_RSA(result, rsa); /* uprefs rsa. */ + +end: + if (rsa != NULL) RSA_free(rsa); + return result; +} + +static EVP_PKEY *find_verification_key(const grpc_json *json, + const char *header_alg, + const char *header_kid) { + const grpc_json *jkey; + const grpc_json *jwk_keys; + /* Try to parse the json as a JWK set: + https://tools.ietf.org/html/rfc7517#section-5. */ + jwk_keys = find_property_by_name(json, "keys"); + if (jwk_keys == NULL) { + /* Use the google proprietary format which is: + { <kid1>: <x5091>, <kid2>: <x5092>, ... } */ + const grpc_json *cur = find_property_by_name(json, header_kid); + if (cur == NULL) return NULL; + return extract_pkey_from_x509(cur->value); + } + + if (jwk_keys->type != GRPC_JSON_ARRAY) { + gpr_log(GPR_ERROR, + "Unexpected value type of keys property in jwks key set."); + return NULL; + } + /* Key format is specified in: + https://tools.ietf.org/html/rfc7518#section-6. */ + for (jkey = jwk_keys->child; jkey != NULL; jkey = jkey->next) { + grpc_json *key_prop; + const char *alg = NULL; + const char *kid = NULL; + const char *kty = NULL; + + if (jkey->type != GRPC_JSON_OBJECT) continue; + for (key_prop = jkey->child; key_prop != NULL; key_prop = key_prop->next) { + if (strcmp(key_prop->key, "alg") == 0 && + key_prop->type == GRPC_JSON_STRING) { + alg = key_prop->value; + } else if (strcmp(key_prop->key, "kid") == 0 && + key_prop->type == GRPC_JSON_STRING) { + kid = key_prop->value; + } else if (strcmp(key_prop->key, "kty") == 0 && + key_prop->type == GRPC_JSON_STRING) { + kty = key_prop->value; + } + } + if (alg != NULL && kid != NULL && kty != NULL && + strcmp(kid, header_kid) == 0 && strcmp(alg, header_alg) == 0) { + return pkey_from_jwk(jkey, kty); + } + } + gpr_log(GPR_ERROR, + "Could not find matching key in key set for kid=%s and alg=%s", + header_kid, header_alg); + return NULL; +} + +static int verify_jwt_signature(EVP_PKEY *key, const char *alg, + gpr_slice signature, gpr_slice signed_data) { + EVP_MD_CTX *md_ctx = EVP_MD_CTX_create(); + const EVP_MD *md = evp_md_from_alg(alg); + int result = 0; + + GPR_ASSERT(md != NULL); /* Checked before. */ + if (md_ctx == NULL) { + gpr_log(GPR_ERROR, "Could not create EVP_MD_CTX."); + goto end; + } + if (EVP_DigestVerifyInit(md_ctx, NULL, md, NULL, key) != 1) { + gpr_log(GPR_ERROR, "EVP_DigestVerifyInit failed."); + goto end; + } + if (EVP_DigestVerifyUpdate(md_ctx, GPR_SLICE_START_PTR(signed_data), + GPR_SLICE_LENGTH(signed_data)) != 1) { + gpr_log(GPR_ERROR, "EVP_DigestVerifyUpdate failed."); + goto end; + } + if (EVP_DigestVerifyFinal(md_ctx, GPR_SLICE_START_PTR(signature), + GPR_SLICE_LENGTH(signature)) != 1) { + gpr_log(GPR_ERROR, "JWT signature verification failed."); + goto end; + } + result = 1; + +end: + if (md_ctx != NULL) EVP_MD_CTX_destroy(md_ctx); + return result; +} + +static void on_keys_retrieved(grpc_exec_ctx *exec_ctx, void *user_data, + const grpc_httpcli_response *response) { + grpc_json *json = json_from_http(response); + verifier_cb_ctx *ctx = (verifier_cb_ctx *)user_data; + EVP_PKEY *verification_key = NULL; + grpc_jwt_verifier_status status = GRPC_JWT_VERIFIER_GENERIC_ERROR; + grpc_jwt_claims *claims = NULL; + + if (json == NULL) { + status = GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR; + goto end; + } + verification_key = + find_verification_key(json, ctx->header->alg, ctx->header->kid); + if (verification_key == NULL) { + gpr_log(GPR_ERROR, "Could not find verification key with kid %s.", + ctx->header->kid); + status = GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR; + goto end; + } + + if (!verify_jwt_signature(verification_key, ctx->header->alg, ctx->signature, + ctx->signed_data)) { + status = GRPC_JWT_VERIFIER_BAD_SIGNATURE; + goto end; + } + + status = grpc_jwt_claims_check(ctx->claims, ctx->audience); + if (status == GRPC_JWT_VERIFIER_OK) { + /* Pass ownership. */ + claims = ctx->claims; + ctx->claims = NULL; + } + +end: + if (json != NULL) grpc_json_destroy(json); + if (verification_key != NULL) EVP_PKEY_free(verification_key); + ctx->user_cb(ctx->user_data, status, claims); + verifier_cb_ctx_destroy(ctx); +} + +static void on_openid_config_retrieved(grpc_exec_ctx *exec_ctx, void *user_data, + const grpc_httpcli_response *response) { + const grpc_json *cur; + grpc_json *json = json_from_http(response); + verifier_cb_ctx *ctx = (verifier_cb_ctx *)user_data; + grpc_httpcli_request req; + const char *jwks_uri; + + /* TODO(jboeuf): Cache the jwks_uri in order to avoid this hop next time. */ + if (json == NULL) goto error; + cur = find_property_by_name(json, "jwks_uri"); + if (cur == NULL) { + gpr_log(GPR_ERROR, "Could not find jwks_uri in openid config."); + goto error; + } + jwks_uri = validate_string_field(cur, "jwks_uri"); + if (jwks_uri == NULL) goto error; + if (strstr(jwks_uri, "https://") != jwks_uri) { + gpr_log(GPR_ERROR, "Invalid non https jwks_uri: %s.", jwks_uri); + goto error; + } + jwks_uri += 8; + req.handshaker = &grpc_httpcli_ssl; + req.host = gpr_strdup(jwks_uri); + req.http.path = strchr(jwks_uri, '/'); + if (req.http.path == NULL) { + req.http.path = ""; + } else { + *(req.host + (req.http.path - jwks_uri)) = '\0'; + } + grpc_httpcli_get( + exec_ctx, &ctx->verifier->http_ctx, ctx->pollset, &req, + gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_max_delay), + on_keys_retrieved, ctx); + grpc_json_destroy(json); + gpr_free(req.host); + return; + +error: + if (json != NULL) grpc_json_destroy(json); + ctx->user_cb(ctx->user_data, GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR, NULL); + verifier_cb_ctx_destroy(ctx); +} + +static email_key_mapping *verifier_get_mapping(grpc_jwt_verifier *v, + const char *email_domain) { + size_t i; + if (v->mappings == NULL) return NULL; + for (i = 0; i < v->num_mappings; i++) { + if (strcmp(email_domain, v->mappings[i].email_domain) == 0) { + return &v->mappings[i]; + } + } + return NULL; +} + +static void verifier_put_mapping(grpc_jwt_verifier *v, const char *email_domain, + const char *key_url_prefix) { + email_key_mapping *mapping = verifier_get_mapping(v, email_domain); + GPR_ASSERT(v->num_mappings < v->allocated_mappings); + if (mapping != NULL) { + gpr_free(mapping->key_url_prefix); + mapping->key_url_prefix = gpr_strdup(key_url_prefix); + return; + } + v->mappings[v->num_mappings].email_domain = gpr_strdup(email_domain); + v->mappings[v->num_mappings].key_url_prefix = gpr_strdup(key_url_prefix); + v->num_mappings++; + GPR_ASSERT(v->num_mappings <= v->allocated_mappings); +} + +/* Takes ownership of ctx. */ +static void retrieve_key_and_verify(grpc_exec_ctx *exec_ctx, + verifier_cb_ctx *ctx) { + const char *at_sign; + grpc_httpcli_response_cb http_cb; + char *path_prefix = NULL; + const char *iss; + grpc_httpcli_request req; + memset(&req, 0, sizeof(grpc_httpcli_request)); + req.handshaker = &grpc_httpcli_ssl; + + GPR_ASSERT(ctx != NULL && ctx->header != NULL && ctx->claims != NULL); + iss = ctx->claims->iss; + if (ctx->header->kid == NULL) { + gpr_log(GPR_ERROR, "Missing kid in jose header."); + goto error; + } + if (iss == NULL) { + gpr_log(GPR_ERROR, "Missing iss in claims."); + goto error; + } + + /* This code relies on: + https://openid.net/specs/openid-connect-discovery-1_0.html + Nobody seems to implement the account/email/webfinger part 2. of the spec + so we will rely instead on email/url mappings if we detect such an issuer. + Part 4, on the other hand is implemented by both google and salesforce. */ + + /* Very non-sophisticated way to detect an email address. Should be good + enough for now... */ + at_sign = strchr(iss, '@'); + if (at_sign != NULL) { + email_key_mapping *mapping; + const char *email_domain = at_sign + 1; + GPR_ASSERT(ctx->verifier != NULL); + mapping = verifier_get_mapping(ctx->verifier, email_domain); + if (mapping == NULL) { + gpr_log(GPR_ERROR, "Missing mapping for issuer email."); + goto error; + } + req.host = gpr_strdup(mapping->key_url_prefix); + path_prefix = strchr(req.host, '/'); + if (path_prefix == NULL) { + gpr_asprintf(&req.http.path, "/%s", iss); + } else { + *(path_prefix++) = '\0'; + gpr_asprintf(&req.http.path, "/%s/%s", path_prefix, iss); + } + http_cb = on_keys_retrieved; + } else { + req.host = gpr_strdup(strstr(iss, "https://") == iss ? iss + 8 : iss); + path_prefix = strchr(req.host, '/'); + if (path_prefix == NULL) { + req.http.path = gpr_strdup(GRPC_OPENID_CONFIG_URL_SUFFIX); + } else { + *(path_prefix++) = 0; + gpr_asprintf(&req.http.path, "/%s%s", path_prefix, + GRPC_OPENID_CONFIG_URL_SUFFIX); + } + http_cb = on_openid_config_retrieved; + } + + grpc_httpcli_get( + exec_ctx, &ctx->verifier->http_ctx, ctx->pollset, &req, + gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), grpc_jwt_verifier_max_delay), + http_cb, ctx); + gpr_free(req.host); + gpr_free(req.http.path); + return; + +error: + ctx->user_cb(ctx->user_data, GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR, NULL); + verifier_cb_ctx_destroy(ctx); +} + +void grpc_jwt_verifier_verify(grpc_exec_ctx *exec_ctx, + grpc_jwt_verifier *verifier, + grpc_pollset *pollset, const char *jwt, + const char *audience, + grpc_jwt_verification_done_cb cb, + void *user_data) { + const char *dot = NULL; + grpc_json *json; + jose_header *header = NULL; + grpc_jwt_claims *claims = NULL; + gpr_slice header_buffer; + gpr_slice claims_buffer; + gpr_slice signature; + size_t signed_jwt_len; + const char *cur = jwt; + + GPR_ASSERT(verifier != NULL && jwt != NULL && audience != NULL && cb != NULL); + dot = strchr(cur, '.'); + if (dot == NULL) goto error; + json = parse_json_part_from_jwt(cur, (size_t)(dot - cur), &header_buffer); + if (json == NULL) goto error; + header = jose_header_from_json(json, header_buffer); + if (header == NULL) goto error; + + cur = dot + 1; + dot = strchr(cur, '.'); + if (dot == NULL) goto error; + json = parse_json_part_from_jwt(cur, (size_t)(dot - cur), &claims_buffer); + if (json == NULL) goto error; + claims = grpc_jwt_claims_from_json(json, claims_buffer); + if (claims == NULL) goto error; + + signed_jwt_len = (size_t)(dot - jwt); + cur = dot + 1; + signature = grpc_base64_decode(cur, 1); + if (GPR_SLICE_IS_EMPTY(signature)) goto error; + retrieve_key_and_verify( + exec_ctx, + verifier_cb_ctx_create(verifier, pollset, header, claims, audience, + signature, jwt, signed_jwt_len, user_data, cb)); + return; + +error: + if (header != NULL) jose_header_destroy(header); + if (claims != NULL) grpc_jwt_claims_destroy(claims); + cb(user_data, GRPC_JWT_VERIFIER_BAD_FORMAT, NULL); +} + +grpc_jwt_verifier *grpc_jwt_verifier_create( + const grpc_jwt_verifier_email_domain_key_url_mapping *mappings, + size_t num_mappings) { + grpc_jwt_verifier *v = gpr_malloc(sizeof(grpc_jwt_verifier)); + memset(v, 0, sizeof(grpc_jwt_verifier)); + grpc_httpcli_context_init(&v->http_ctx); + + /* We know at least of one mapping. */ + v->allocated_mappings = 1 + num_mappings; + v->mappings = gpr_malloc(v->allocated_mappings * sizeof(email_key_mapping)); + verifier_put_mapping(v, GRPC_GOOGLE_SERVICE_ACCOUNTS_EMAIL_DOMAIN, + GRPC_GOOGLE_SERVICE_ACCOUNTS_KEY_URL_PREFIX); + /* User-Provided mappings. */ + if (mappings != NULL) { + size_t i; + for (i = 0; i < num_mappings; i++) { + verifier_put_mapping(v, mappings[i].email_domain, + mappings[i].key_url_prefix); + } + } + return v; +} + +void grpc_jwt_verifier_destroy(grpc_jwt_verifier *v) { + size_t i; + if (v == NULL) return; + grpc_httpcli_context_destroy(&v->http_ctx); + if (v->mappings != NULL) { + for (i = 0; i < v->num_mappings; i++) { + gpr_free(v->mappings[i].email_domain); + gpr_free(v->mappings[i].key_url_prefix); + } + gpr_free(v->mappings); + } + gpr_free(v); +} diff --git a/src/core/lib/security/jwt_verifier.h b/src/core/lib/security/jwt_verifier.h new file mode 100644 index 0000000000..28a9eff048 --- /dev/null +++ b/src/core/lib/security/jwt_verifier.h @@ -0,0 +1,136 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SECURITY_JWT_VERIFIER_H +#define GRPC_CORE_LIB_SECURITY_JWT_VERIFIER_H + +#include "src/core/lib/iomgr/pollset.h" +#include "src/core/lib/json/json.h" + +#include <grpc/support/slice.h> +#include <grpc/support/time.h> + +/* --- Constants. --- */ + +#define GRPC_OPENID_CONFIG_URL_SUFFIX "/.well-known/openid-configuration" +#define GRPC_GOOGLE_SERVICE_ACCOUNTS_EMAIL_DOMAIN \ + "developer.gserviceaccount.com" +#define GRPC_GOOGLE_SERVICE_ACCOUNTS_KEY_URL_PREFIX \ + "www.googleapis.com/robot/v1/metadata/x509" + +/* --- grpc_jwt_verifier_status. --- */ + +typedef enum { + GRPC_JWT_VERIFIER_OK = 0, + GRPC_JWT_VERIFIER_BAD_SIGNATURE, + GRPC_JWT_VERIFIER_BAD_FORMAT, + GRPC_JWT_VERIFIER_BAD_AUDIENCE, + GRPC_JWT_VERIFIER_KEY_RETRIEVAL_ERROR, + GRPC_JWT_VERIFIER_TIME_CONSTRAINT_FAILURE, + GRPC_JWT_VERIFIER_GENERIC_ERROR +} grpc_jwt_verifier_status; + +const char *grpc_jwt_verifier_status_to_string(grpc_jwt_verifier_status status); + +/* --- grpc_jwt_claims. --- */ + +typedef struct grpc_jwt_claims grpc_jwt_claims; + +void grpc_jwt_claims_destroy(grpc_jwt_claims *claims); + +/* Returns the whole JSON tree of the claims. */ +const grpc_json *grpc_jwt_claims_json(const grpc_jwt_claims *claims); + +/* Access to registered claims in https://tools.ietf.org/html/rfc7519#page-9 */ +const char *grpc_jwt_claims_subject(const grpc_jwt_claims *claims); +const char *grpc_jwt_claims_issuer(const grpc_jwt_claims *claims); +const char *grpc_jwt_claims_id(const grpc_jwt_claims *claims); +const char *grpc_jwt_claims_audience(const grpc_jwt_claims *claims); +gpr_timespec grpc_jwt_claims_issued_at(const grpc_jwt_claims *claims); +gpr_timespec grpc_jwt_claims_expires_at(const grpc_jwt_claims *claims); +gpr_timespec grpc_jwt_claims_not_before(const grpc_jwt_claims *claims); + +/* --- grpc_jwt_verifier. --- */ + +typedef struct grpc_jwt_verifier grpc_jwt_verifier; + +typedef struct { + /* The email domain is the part after the @ sign. */ + const char *email_domain; + + /* The key url prefix will be used to get the public key from the issuer: + https://<key_url_prefix>/<issuer_email> + Therefore the key_url_prefix must NOT contain https://. */ + const char *key_url_prefix; +} grpc_jwt_verifier_email_domain_key_url_mapping; + +/* Globals to control the verifier. Not thread-safe. */ +extern gpr_timespec grpc_jwt_verifier_clock_skew; +extern gpr_timespec grpc_jwt_verifier_max_delay; + +/* The verifier can be created with some custom mappings to help with key + discovery in the case where the issuer is an email address. + mappings can be NULL in which case num_mappings MUST be 0. + A verifier object has one built-in mapping (unless overridden): + GRPC_GOOGLE_SERVICE_ACCOUNTS_EMAIL_DOMAIN -> + GRPC_GOOGLE_SERVICE_ACCOUNTS_KEY_URL_PREFIX.*/ +grpc_jwt_verifier *grpc_jwt_verifier_create( + const grpc_jwt_verifier_email_domain_key_url_mapping *mappings, + size_t num_mappings); + +/*The verifier must not be destroyed if there are still outstanding callbacks.*/ +void grpc_jwt_verifier_destroy(grpc_jwt_verifier *verifier); + +/* User provided callback that will be called when the verification of the JWT + is done (maybe in another thread). + It is the responsibility of the callee to call grpc_jwt_claims_destroy on + the claims. */ +typedef void (*grpc_jwt_verification_done_cb)(void *user_data, + grpc_jwt_verifier_status status, + grpc_jwt_claims *claims); + +/* Verifies for the JWT for the given expected audience. */ +void grpc_jwt_verifier_verify(grpc_exec_ctx *exec_ctx, + grpc_jwt_verifier *verifier, + grpc_pollset *pollset, const char *jwt, + const char *audience, + grpc_jwt_verification_done_cb cb, + void *user_data); + +/* --- TESTING ONLY exposed functions. --- */ + +grpc_jwt_claims *grpc_jwt_claims_from_json(grpc_json *json, gpr_slice buffer); +grpc_jwt_verifier_status grpc_jwt_claims_check(const grpc_jwt_claims *claims, + const char *audience); + +#endif /* GRPC_CORE_LIB_SECURITY_JWT_VERIFIER_H */ diff --git a/src/core/lib/security/secure_endpoint.c b/src/core/lib/security/secure_endpoint.c new file mode 100644 index 0000000000..e233b081ef --- /dev/null +++ b/src/core/lib/security/secure_endpoint.c @@ -0,0 +1,384 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/security/secure_endpoint.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/slice.h> +#include <grpc/support/slice_buffer.h> +#include <grpc/support/sync.h> +#include "src/core/lib/debug/trace.h" +#include "src/core/lib/support/string.h" +#include "src/core/lib/tsi/transport_security_interface.h" + +#define STAGING_BUFFER_SIZE 8192 + +typedef struct { + grpc_endpoint base; + grpc_endpoint *wrapped_ep; + struct tsi_frame_protector *protector; + gpr_mu protector_mu; + /* saved upper level callbacks and user_data. */ + grpc_closure *read_cb; + grpc_closure *write_cb; + grpc_closure on_read; + gpr_slice_buffer *read_buffer; + gpr_slice_buffer source_buffer; + /* saved handshaker leftover data to unprotect. */ + gpr_slice_buffer leftover_bytes; + /* buffers for read and write */ + gpr_slice read_staging_buffer; + + gpr_slice write_staging_buffer; + gpr_slice_buffer output_buffer; + + gpr_refcount ref; +} secure_endpoint; + +int grpc_trace_secure_endpoint = 0; + +static void destroy(grpc_exec_ctx *exec_ctx, secure_endpoint *secure_ep) { + secure_endpoint *ep = secure_ep; + grpc_endpoint_destroy(exec_ctx, ep->wrapped_ep); + tsi_frame_protector_destroy(ep->protector); + gpr_slice_buffer_destroy(&ep->leftover_bytes); + gpr_slice_unref(ep->read_staging_buffer); + gpr_slice_unref(ep->write_staging_buffer); + gpr_slice_buffer_destroy(&ep->output_buffer); + gpr_slice_buffer_destroy(&ep->source_buffer); + gpr_mu_destroy(&ep->protector_mu); + gpr_free(ep); +} + +/*#define GRPC_SECURE_ENDPOINT_REFCOUNT_DEBUG*/ +#ifdef GRPC_SECURE_ENDPOINT_REFCOUNT_DEBUG +#define SECURE_ENDPOINT_UNREF(exec_ctx, ep, reason) \ + secure_endpoint_unref((exec_ctx), (ep), (reason), __FILE__, __LINE__) +#define SECURE_ENDPOINT_REF(ep, reason) \ + secure_endpoint_ref((ep), (reason), __FILE__, __LINE__) +static void secure_endpoint_unref(secure_endpoint *ep, + grpc_closure_list *closure_list, + const char *reason, const char *file, + int line) { + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, "SECENDP unref %p : %s %d -> %d", + ep, reason, ep->ref.count, ep->ref.count - 1); + if (gpr_unref(&ep->ref)) { + destroy(exec_ctx, ep); + } +} + +static void secure_endpoint_ref(secure_endpoint *ep, const char *reason, + const char *file, int line) { + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, "SECENDP ref %p : %s %d -> %d", + ep, reason, ep->ref.count, ep->ref.count + 1); + gpr_ref(&ep->ref); +} +#else +#define SECURE_ENDPOINT_UNREF(exec_ctx, ep, reason) \ + secure_endpoint_unref((exec_ctx), (ep)) +#define SECURE_ENDPOINT_REF(ep, reason) secure_endpoint_ref((ep)) +static void secure_endpoint_unref(grpc_exec_ctx *exec_ctx, + secure_endpoint *ep) { + if (gpr_unref(&ep->ref)) { + destroy(exec_ctx, ep); + } +} + +static void secure_endpoint_ref(secure_endpoint *ep) { gpr_ref(&ep->ref); } +#endif + +static void flush_read_staging_buffer(secure_endpoint *ep, uint8_t **cur, + uint8_t **end) { + gpr_slice_buffer_add(ep->read_buffer, ep->read_staging_buffer); + ep->read_staging_buffer = gpr_slice_malloc(STAGING_BUFFER_SIZE); + *cur = GPR_SLICE_START_PTR(ep->read_staging_buffer); + *end = GPR_SLICE_END_PTR(ep->read_staging_buffer); +} + +static void call_read_cb(grpc_exec_ctx *exec_ctx, secure_endpoint *ep, + bool success) { + if (grpc_trace_secure_endpoint) { + size_t i; + for (i = 0; i < ep->read_buffer->count; i++) { + char *data = gpr_dump_slice(ep->read_buffer->slices[i], + GPR_DUMP_HEX | GPR_DUMP_ASCII); + gpr_log(GPR_DEBUG, "READ %p: %s", ep, data); + gpr_free(data); + } + } + ep->read_buffer = NULL; + grpc_exec_ctx_enqueue(exec_ctx, ep->read_cb, success, NULL); + SECURE_ENDPOINT_UNREF(exec_ctx, ep, "read"); +} + +static void on_read(grpc_exec_ctx *exec_ctx, void *user_data, bool success) { + unsigned i; + uint8_t keep_looping = 0; + tsi_result result = TSI_OK; + secure_endpoint *ep = (secure_endpoint *)user_data; + uint8_t *cur = GPR_SLICE_START_PTR(ep->read_staging_buffer); + uint8_t *end = GPR_SLICE_END_PTR(ep->read_staging_buffer); + + if (!success) { + gpr_slice_buffer_reset_and_unref(ep->read_buffer); + call_read_cb(exec_ctx, ep, 0); + return; + } + + /* TODO(yangg) check error, maybe bail out early */ + for (i = 0; i < ep->source_buffer.count; i++) { + gpr_slice encrypted = ep->source_buffer.slices[i]; + uint8_t *message_bytes = GPR_SLICE_START_PTR(encrypted); + size_t message_size = GPR_SLICE_LENGTH(encrypted); + + while (message_size > 0 || keep_looping) { + size_t unprotected_buffer_size_written = (size_t)(end - cur); + size_t processed_message_size = message_size; + gpr_mu_lock(&ep->protector_mu); + result = tsi_frame_protector_unprotect(ep->protector, message_bytes, + &processed_message_size, cur, + &unprotected_buffer_size_written); + gpr_mu_unlock(&ep->protector_mu); + if (result != TSI_OK) { + gpr_log(GPR_ERROR, "Decryption error: %s", + tsi_result_to_string(result)); + break; + } + message_bytes += processed_message_size; + message_size -= processed_message_size; + cur += unprotected_buffer_size_written; + + if (cur == end) { + flush_read_staging_buffer(ep, &cur, &end); + /* Force to enter the loop again to extract buffered bytes in protector. + The bytes could be buffered because of running out of staging_buffer. + If this happens at the end of all slices, doing another unprotect + avoids leaving data in the protector. */ + keep_looping = 1; + } else if (unprotected_buffer_size_written > 0) { + keep_looping = 1; + } else { + keep_looping = 0; + } + } + if (result != TSI_OK) break; + } + + if (cur != GPR_SLICE_START_PTR(ep->read_staging_buffer)) { + gpr_slice_buffer_add( + ep->read_buffer, + gpr_slice_split_head( + &ep->read_staging_buffer, + (size_t)(cur - GPR_SLICE_START_PTR(ep->read_staging_buffer)))); + } + + /* TODO(yangg) experiment with moving this block after read_cb to see if it + helps latency */ + gpr_slice_buffer_reset_and_unref(&ep->source_buffer); + + if (result != TSI_OK) { + gpr_slice_buffer_reset_and_unref(ep->read_buffer); + call_read_cb(exec_ctx, ep, 0); + return; + } + + call_read_cb(exec_ctx, ep, 1); +} + +static void endpoint_read(grpc_exec_ctx *exec_ctx, grpc_endpoint *secure_ep, + gpr_slice_buffer *slices, grpc_closure *cb) { + secure_endpoint *ep = (secure_endpoint *)secure_ep; + ep->read_cb = cb; + ep->read_buffer = slices; + gpr_slice_buffer_reset_and_unref(ep->read_buffer); + + SECURE_ENDPOINT_REF(ep, "read"); + if (ep->leftover_bytes.count) { + gpr_slice_buffer_swap(&ep->leftover_bytes, &ep->source_buffer); + GPR_ASSERT(ep->leftover_bytes.count == 0); + on_read(exec_ctx, ep, 1); + return; + } + + grpc_endpoint_read(exec_ctx, ep->wrapped_ep, &ep->source_buffer, + &ep->on_read); +} + +static void flush_write_staging_buffer(secure_endpoint *ep, uint8_t **cur, + uint8_t **end) { + gpr_slice_buffer_add(&ep->output_buffer, ep->write_staging_buffer); + ep->write_staging_buffer = gpr_slice_malloc(STAGING_BUFFER_SIZE); + *cur = GPR_SLICE_START_PTR(ep->write_staging_buffer); + *end = GPR_SLICE_END_PTR(ep->write_staging_buffer); +} + +static void endpoint_write(grpc_exec_ctx *exec_ctx, grpc_endpoint *secure_ep, + gpr_slice_buffer *slices, grpc_closure *cb) { + unsigned i; + tsi_result result = TSI_OK; + secure_endpoint *ep = (secure_endpoint *)secure_ep; + uint8_t *cur = GPR_SLICE_START_PTR(ep->write_staging_buffer); + uint8_t *end = GPR_SLICE_END_PTR(ep->write_staging_buffer); + + gpr_slice_buffer_reset_and_unref(&ep->output_buffer); + + if (grpc_trace_secure_endpoint) { + for (i = 0; i < slices->count; i++) { + char *data = + gpr_dump_slice(slices->slices[i], GPR_DUMP_HEX | GPR_DUMP_ASCII); + gpr_log(GPR_DEBUG, "WRITE %p: %s", ep, data); + gpr_free(data); + } + } + + for (i = 0; i < slices->count; i++) { + gpr_slice plain = slices->slices[i]; + uint8_t *message_bytes = GPR_SLICE_START_PTR(plain); + size_t message_size = GPR_SLICE_LENGTH(plain); + while (message_size > 0) { + size_t protected_buffer_size_to_send = (size_t)(end - cur); + size_t processed_message_size = message_size; + gpr_mu_lock(&ep->protector_mu); + result = tsi_frame_protector_protect(ep->protector, message_bytes, + &processed_message_size, cur, + &protected_buffer_size_to_send); + gpr_mu_unlock(&ep->protector_mu); + if (result != TSI_OK) { + gpr_log(GPR_ERROR, "Encryption error: %s", + tsi_result_to_string(result)); + break; + } + message_bytes += processed_message_size; + message_size -= processed_message_size; + cur += protected_buffer_size_to_send; + + if (cur == end) { + flush_write_staging_buffer(ep, &cur, &end); + } + } + if (result != TSI_OK) break; + } + if (result == TSI_OK) { + size_t still_pending_size; + do { + size_t protected_buffer_size_to_send = (size_t)(end - cur); + gpr_mu_lock(&ep->protector_mu); + result = tsi_frame_protector_protect_flush(ep->protector, cur, + &protected_buffer_size_to_send, + &still_pending_size); + gpr_mu_unlock(&ep->protector_mu); + if (result != TSI_OK) break; + cur += protected_buffer_size_to_send; + if (cur == end) { + flush_write_staging_buffer(ep, &cur, &end); + } + } while (still_pending_size > 0); + if (cur != GPR_SLICE_START_PTR(ep->write_staging_buffer)) { + gpr_slice_buffer_add( + &ep->output_buffer, + gpr_slice_split_head( + &ep->write_staging_buffer, + (size_t)(cur - GPR_SLICE_START_PTR(ep->write_staging_buffer)))); + } + } + + if (result != TSI_OK) { + /* TODO(yangg) do different things according to the error type? */ + gpr_slice_buffer_reset_and_unref(&ep->output_buffer); + grpc_exec_ctx_enqueue(exec_ctx, cb, false, NULL); + return; + } + + grpc_endpoint_write(exec_ctx, ep->wrapped_ep, &ep->output_buffer, cb); +} + +static void endpoint_shutdown(grpc_exec_ctx *exec_ctx, + grpc_endpoint *secure_ep) { + secure_endpoint *ep = (secure_endpoint *)secure_ep; + grpc_endpoint_shutdown(exec_ctx, ep->wrapped_ep); +} + +static void endpoint_destroy(grpc_exec_ctx *exec_ctx, + grpc_endpoint *secure_ep) { + secure_endpoint *ep = (secure_endpoint *)secure_ep; + SECURE_ENDPOINT_UNREF(exec_ctx, ep, "destroy"); +} + +static void endpoint_add_to_pollset(grpc_exec_ctx *exec_ctx, + grpc_endpoint *secure_ep, + grpc_pollset *pollset) { + secure_endpoint *ep = (secure_endpoint *)secure_ep; + grpc_endpoint_add_to_pollset(exec_ctx, ep->wrapped_ep, pollset); +} + +static void endpoint_add_to_pollset_set(grpc_exec_ctx *exec_ctx, + grpc_endpoint *secure_ep, + grpc_pollset_set *pollset_set) { + secure_endpoint *ep = (secure_endpoint *)secure_ep; + grpc_endpoint_add_to_pollset_set(exec_ctx, ep->wrapped_ep, pollset_set); +} + +static char *endpoint_get_peer(grpc_endpoint *secure_ep) { + secure_endpoint *ep = (secure_endpoint *)secure_ep; + return grpc_endpoint_get_peer(ep->wrapped_ep); +} + +static const grpc_endpoint_vtable vtable = { + endpoint_read, endpoint_write, + endpoint_add_to_pollset, endpoint_add_to_pollset_set, + endpoint_shutdown, endpoint_destroy, + endpoint_get_peer}; + +grpc_endpoint *grpc_secure_endpoint_create( + struct tsi_frame_protector *protector, grpc_endpoint *transport, + gpr_slice *leftover_slices, size_t leftover_nslices) { + size_t i; + secure_endpoint *ep = (secure_endpoint *)gpr_malloc(sizeof(secure_endpoint)); + ep->base.vtable = &vtable; + ep->wrapped_ep = transport; + ep->protector = protector; + gpr_slice_buffer_init(&ep->leftover_bytes); + for (i = 0; i < leftover_nslices; i++) { + gpr_slice_buffer_add(&ep->leftover_bytes, + gpr_slice_ref(leftover_slices[i])); + } + ep->write_staging_buffer = gpr_slice_malloc(STAGING_BUFFER_SIZE); + ep->read_staging_buffer = gpr_slice_malloc(STAGING_BUFFER_SIZE); + gpr_slice_buffer_init(&ep->output_buffer); + gpr_slice_buffer_init(&ep->source_buffer); + ep->read_buffer = NULL; + grpc_closure_init(&ep->on_read, on_read, ep); + gpr_mu_init(&ep->protector_mu); + gpr_ref_init(&ep->ref, 1); + return &ep->base; +} diff --git a/src/core/lib/security/secure_endpoint.h b/src/core/lib/security/secure_endpoint.h new file mode 100644 index 0000000000..57bd160a52 --- /dev/null +++ b/src/core/lib/security/secure_endpoint.h @@ -0,0 +1,49 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SECURITY_SECURE_ENDPOINT_H +#define GRPC_CORE_LIB_SECURITY_SECURE_ENDPOINT_H + +#include <grpc/support/slice.h> +#include "src/core/lib/iomgr/endpoint.h" + +struct tsi_frame_protector; + +extern int grpc_trace_secure_endpoint; + +/* Takes ownership of protector and to_wrap, and refs leftover_slices. */ +grpc_endpoint *grpc_secure_endpoint_create( + struct tsi_frame_protector *protector, grpc_endpoint *to_wrap, + gpr_slice *leftover_slices, size_t leftover_nslices); + +#endif /* GRPC_CORE_LIB_SECURITY_SECURE_ENDPOINT_H */ diff --git a/src/core/lib/security/security_connector.c b/src/core/lib/security/security_connector.c new file mode 100644 index 0000000000..5474bc3a9e --- /dev/null +++ b/src/core/lib/security/security_connector.c @@ -0,0 +1,812 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/security/security_connector.h" + +#include <stdbool.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/host_port.h> +#include <grpc/support/log.h> +#include <grpc/support/slice_buffer.h> +#include <grpc/support/string_util.h> + +#include "src/core/lib/security/credentials.h" +#include "src/core/lib/security/handshake.h" +#include "src/core/lib/security/secure_endpoint.h" +#include "src/core/lib/security/security_context.h" +#include "src/core/lib/support/env.h" +#include "src/core/lib/support/load_file.h" +#include "src/core/lib/support/string.h" +#include "src/core/lib/transport/chttp2/alpn.h" +#include "src/core/lib/tsi/fake_transport_security.h" +#include "src/core/lib/tsi/ssl_transport_security.h" + +/* -- Constants. -- */ + +#ifndef INSTALL_PREFIX +static const char *installed_roots_path = "/usr/share/grpc/roots.pem"; +#else +static const char *installed_roots_path = + INSTALL_PREFIX "/share/grpc/roots.pem"; +#endif + +/* -- Overridden default roots. -- */ + +static grpc_ssl_roots_override_callback ssl_roots_override_cb = NULL; + +void grpc_set_ssl_roots_override_callback(grpc_ssl_roots_override_callback cb) { + ssl_roots_override_cb = cb; +} + +/* -- Cipher suites. -- */ + +/* Defines the cipher suites that we accept by default. All these cipher suites + are compliant with HTTP2. */ +#define GRPC_SSL_CIPHER_SUITES \ + "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-" \ + "SHA384:ECDHE-RSA-AES256-GCM-SHA384" + +static gpr_once cipher_suites_once = GPR_ONCE_INIT; +static const char *cipher_suites = NULL; + +static void init_cipher_suites(void) { + char *overridden = gpr_getenv("GRPC_SSL_CIPHER_SUITES"); + cipher_suites = overridden != NULL ? overridden : GRPC_SSL_CIPHER_SUITES; +} + +static const char *ssl_cipher_suites(void) { + gpr_once_init(&cipher_suites_once, init_cipher_suites); + return cipher_suites; +} + +/* -- Common methods. -- */ + +/* Returns the first property with that name. */ +const tsi_peer_property *tsi_peer_get_property_by_name(const tsi_peer *peer, + const char *name) { + size_t i; + if (peer == NULL) return NULL; + for (i = 0; i < peer->property_count; i++) { + const tsi_peer_property *property = &peer->properties[i]; + if (name == NULL && property->name == NULL) { + return property; + } + if (name != NULL && property->name != NULL && + strcmp(property->name, name) == 0) { + return property; + } + } + return NULL; +} + +void grpc_server_security_connector_shutdown( + grpc_exec_ctx *exec_ctx, grpc_server_security_connector *connector) { + grpc_security_connector_handshake_list *tmp; + gpr_mu_lock(&connector->mu); + while (connector->handshaking_handshakes) { + tmp = connector->handshaking_handshakes; + grpc_security_handshake_shutdown( + exec_ctx, connector->handshaking_handshakes->handshake); + connector->handshaking_handshakes = tmp->next; + gpr_free(tmp); + } + gpr_mu_unlock(&connector->mu); +} + +void grpc_channel_security_connector_do_handshake( + grpc_exec_ctx *exec_ctx, grpc_channel_security_connector *sc, + grpc_endpoint *nonsecure_endpoint, grpc_security_handshake_done_cb cb, + void *user_data) { + if (sc == NULL || nonsecure_endpoint == NULL) { + cb(exec_ctx, user_data, GRPC_SECURITY_ERROR, NULL, NULL); + } else { + sc->do_handshake(exec_ctx, sc, nonsecure_endpoint, cb, user_data); + } +} + +void grpc_server_security_connector_do_handshake( + grpc_exec_ctx *exec_ctx, grpc_server_security_connector *sc, + grpc_tcp_server_acceptor *acceptor, grpc_endpoint *nonsecure_endpoint, + grpc_security_handshake_done_cb cb, void *user_data) { + if (sc == NULL || nonsecure_endpoint == NULL) { + cb(exec_ctx, user_data, GRPC_SECURITY_ERROR, NULL, NULL); + } else { + sc->do_handshake(exec_ctx, sc, acceptor, nonsecure_endpoint, cb, user_data); + } +} + +void grpc_security_connector_check_peer(grpc_exec_ctx *exec_ctx, + grpc_security_connector *sc, + tsi_peer peer, + grpc_security_peer_check_cb cb, + void *user_data) { + if (sc == NULL) { + cb(exec_ctx, user_data, GRPC_SECURITY_ERROR, NULL); + tsi_peer_destruct(&peer); + } else { + sc->vtable->check_peer(exec_ctx, sc, peer, cb, user_data); + } +} + +void grpc_channel_security_connector_check_call_host( + grpc_exec_ctx *exec_ctx, grpc_channel_security_connector *sc, + const char *host, grpc_auth_context *auth_context, + grpc_security_call_host_check_cb cb, void *user_data) { + if (sc == NULL || sc->check_call_host == NULL) { + cb(exec_ctx, user_data, GRPC_SECURITY_ERROR); + } else { + sc->check_call_host(exec_ctx, sc, host, auth_context, cb, user_data); + } +} + +#ifdef GRPC_SECURITY_CONNECTOR_REFCOUNT_DEBUG +grpc_security_connector *grpc_security_connector_ref( + grpc_security_connector *sc, const char *file, int line, + const char *reason) { + if (sc == NULL) return NULL; + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, + "SECURITY_CONNECTOR:%p ref %d -> %d %s", sc, + (int)sc->refcount.count, (int)sc->refcount.count + 1, reason); +#else +grpc_security_connector *grpc_security_connector_ref( + grpc_security_connector *sc) { + if (sc == NULL) return NULL; +#endif + gpr_ref(&sc->refcount); + return sc; +} + +#ifdef GRPC_SECURITY_CONNECTOR_REFCOUNT_DEBUG +void grpc_security_connector_unref(grpc_security_connector *sc, + const char *file, int line, + const char *reason) { + if (sc == NULL) return; + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, + "SECURITY_CONNECTOR:%p unref %d -> %d %s", sc, + (int)sc->refcount.count, (int)sc->refcount.count - 1, reason); +#else +void grpc_security_connector_unref(grpc_security_connector *sc) { + if (sc == NULL) return; +#endif + if (gpr_unref(&sc->refcount)) sc->vtable->destroy(sc); +} + +static void connector_pointer_arg_destroy(void *p) { + GRPC_SECURITY_CONNECTOR_UNREF(p, "connector_pointer_arg"); +} + +static void *connector_pointer_arg_copy(void *p) { + return GRPC_SECURITY_CONNECTOR_REF(p, "connector_pointer_arg"); +} + +static int connector_pointer_cmp(void *a, void *b) { return GPR_ICMP(a, b); } + +static const grpc_arg_pointer_vtable connector_pointer_vtable = { + connector_pointer_arg_copy, connector_pointer_arg_destroy, + connector_pointer_cmp}; + +grpc_arg grpc_security_connector_to_arg(grpc_security_connector *sc) { + grpc_arg result; + result.type = GRPC_ARG_POINTER; + result.key = GRPC_SECURITY_CONNECTOR_ARG; + result.value.pointer.vtable = &connector_pointer_vtable; + result.value.pointer.p = sc; + return result; +} + +grpc_security_connector *grpc_security_connector_from_arg(const grpc_arg *arg) { + if (strcmp(arg->key, GRPC_SECURITY_CONNECTOR_ARG)) return NULL; + if (arg->type != GRPC_ARG_POINTER) { + gpr_log(GPR_ERROR, "Invalid type %d for arg %s", arg->type, + GRPC_SECURITY_CONNECTOR_ARG); + return NULL; + } + return arg->value.pointer.p; +} + +grpc_security_connector *grpc_find_security_connector_in_args( + const grpc_channel_args *args) { + size_t i; + if (args == NULL) return NULL; + for (i = 0; i < args->num_args; i++) { + grpc_security_connector *sc = + grpc_security_connector_from_arg(&args->args[i]); + if (sc != NULL) return sc; + } + return NULL; +} + +/* -- Fake implementation. -- */ + +static void fake_channel_destroy(grpc_security_connector *sc) { + grpc_channel_security_connector *c = (grpc_channel_security_connector *)sc; + grpc_call_credentials_unref(c->request_metadata_creds); + gpr_free(sc); +} + +static void fake_server_destroy(grpc_security_connector *sc) { + grpc_server_security_connector *c = (grpc_server_security_connector *)sc; + gpr_mu_destroy(&c->mu); + gpr_free(sc); +} + +static void fake_check_peer(grpc_exec_ctx *exec_ctx, + grpc_security_connector *sc, tsi_peer peer, + grpc_security_peer_check_cb cb, void *user_data) { + const char *prop_name; + grpc_security_status status = GRPC_SECURITY_OK; + grpc_auth_context *auth_context = NULL; + if (peer.property_count != 1) { + gpr_log(GPR_ERROR, "Fake peers should only have 1 property."); + status = GRPC_SECURITY_ERROR; + goto end; + } + prop_name = peer.properties[0].name; + if (prop_name == NULL || + strcmp(prop_name, TSI_CERTIFICATE_TYPE_PEER_PROPERTY)) { + gpr_log(GPR_ERROR, "Unexpected property in fake peer: %s.", + prop_name == NULL ? "<EMPTY>" : prop_name); + status = GRPC_SECURITY_ERROR; + goto end; + } + if (strncmp(peer.properties[0].value.data, TSI_FAKE_CERTIFICATE_TYPE, + peer.properties[0].value.length)) { + gpr_log(GPR_ERROR, "Invalid value for cert type property."); + status = GRPC_SECURITY_ERROR; + goto end; + } + auth_context = grpc_auth_context_create(NULL); + grpc_auth_context_add_cstring_property( + auth_context, GRPC_TRANSPORT_SECURITY_TYPE_PROPERTY_NAME, + GRPC_FAKE_TRANSPORT_SECURITY_TYPE); + +end: + cb(exec_ctx, user_data, status, auth_context); + grpc_auth_context_unref(auth_context); + tsi_peer_destruct(&peer); +} + +static void fake_channel_check_call_host(grpc_exec_ctx *exec_ctx, + grpc_channel_security_connector *sc, + const char *host, + grpc_auth_context *auth_context, + grpc_security_call_host_check_cb cb, + void *user_data) { + cb(exec_ctx, user_data, GRPC_SECURITY_OK); +} + +static void fake_channel_do_handshake(grpc_exec_ctx *exec_ctx, + grpc_channel_security_connector *sc, + grpc_endpoint *nonsecure_endpoint, + grpc_security_handshake_done_cb cb, + void *user_data) { + grpc_do_security_handshake(exec_ctx, tsi_create_fake_handshaker(1), &sc->base, + true, nonsecure_endpoint, cb, user_data); +} + +static void fake_server_do_handshake(grpc_exec_ctx *exec_ctx, + grpc_server_security_connector *sc, + grpc_tcp_server_acceptor *acceptor, + grpc_endpoint *nonsecure_endpoint, + grpc_security_handshake_done_cb cb, + void *user_data) { + grpc_do_security_handshake(exec_ctx, tsi_create_fake_handshaker(0), &sc->base, + false, nonsecure_endpoint, cb, user_data); +} + +static grpc_security_connector_vtable fake_channel_vtable = { + fake_channel_destroy, fake_check_peer}; + +static grpc_security_connector_vtable fake_server_vtable = {fake_server_destroy, + fake_check_peer}; + +grpc_channel_security_connector *grpc_fake_channel_security_connector_create( + grpc_call_credentials *request_metadata_creds) { + grpc_channel_security_connector *c = gpr_malloc(sizeof(*c)); + memset(c, 0, sizeof(*c)); + gpr_ref_init(&c->base.refcount, 1); + c->base.url_scheme = GRPC_FAKE_SECURITY_URL_SCHEME; + c->base.vtable = &fake_channel_vtable; + c->request_metadata_creds = grpc_call_credentials_ref(request_metadata_creds); + c->check_call_host = fake_channel_check_call_host; + c->do_handshake = fake_channel_do_handshake; + return c; +} + +grpc_server_security_connector *grpc_fake_server_security_connector_create( + void) { + grpc_server_security_connector *c = + gpr_malloc(sizeof(grpc_server_security_connector)); + memset(c, 0, sizeof(*c)); + gpr_ref_init(&c->base.refcount, 1); + c->base.vtable = &fake_server_vtable; + c->base.url_scheme = GRPC_FAKE_SECURITY_URL_SCHEME; + c->do_handshake = fake_server_do_handshake; + gpr_mu_init(&c->mu); + return c; +} + +/* --- Ssl implementation. --- */ + +typedef struct { + grpc_channel_security_connector base; + tsi_ssl_handshaker_factory *handshaker_factory; + char *target_name; + char *overridden_target_name; +} grpc_ssl_channel_security_connector; + +typedef struct { + grpc_server_security_connector base; + tsi_ssl_handshaker_factory *handshaker_factory; +} grpc_ssl_server_security_connector; + +static void ssl_channel_destroy(grpc_security_connector *sc) { + grpc_ssl_channel_security_connector *c = + (grpc_ssl_channel_security_connector *)sc; + grpc_call_credentials_unref(c->base.request_metadata_creds); + if (c->handshaker_factory != NULL) { + tsi_ssl_handshaker_factory_destroy(c->handshaker_factory); + } + if (c->target_name != NULL) gpr_free(c->target_name); + if (c->overridden_target_name != NULL) gpr_free(c->overridden_target_name); + gpr_free(sc); +} + +static void ssl_server_destroy(grpc_security_connector *sc) { + grpc_ssl_server_security_connector *c = + (grpc_ssl_server_security_connector *)sc; + + if (c->handshaker_factory != NULL) { + tsi_ssl_handshaker_factory_destroy(c->handshaker_factory); + } + gpr_mu_destroy(&c->base.mu); + gpr_free(sc); +} + +static grpc_security_status ssl_create_handshaker( + tsi_ssl_handshaker_factory *handshaker_factory, bool is_client, + const char *peer_name, tsi_handshaker **handshaker) { + tsi_result result = TSI_OK; + if (handshaker_factory == NULL) return GRPC_SECURITY_ERROR; + result = tsi_ssl_handshaker_factory_create_handshaker( + handshaker_factory, is_client ? peer_name : NULL, handshaker); + if (result != TSI_OK) { + gpr_log(GPR_ERROR, "Handshaker creation failed with error %s.", + tsi_result_to_string(result)); + return GRPC_SECURITY_ERROR; + } + return GRPC_SECURITY_OK; +} + +static void ssl_channel_do_handshake(grpc_exec_ctx *exec_ctx, + grpc_channel_security_connector *sc, + grpc_endpoint *nonsecure_endpoint, + grpc_security_handshake_done_cb cb, + void *user_data) { + grpc_ssl_channel_security_connector *c = + (grpc_ssl_channel_security_connector *)sc; + tsi_handshaker *handshaker; + grpc_security_status status = ssl_create_handshaker( + c->handshaker_factory, true, + c->overridden_target_name != NULL ? c->overridden_target_name + : c->target_name, + &handshaker); + if (status != GRPC_SECURITY_OK) { + cb(exec_ctx, user_data, status, NULL, NULL); + } else { + grpc_do_security_handshake(exec_ctx, handshaker, &sc->base, true, + nonsecure_endpoint, cb, user_data); + } +} + +static void ssl_server_do_handshake(grpc_exec_ctx *exec_ctx, + grpc_server_security_connector *sc, + grpc_tcp_server_acceptor *acceptor, + grpc_endpoint *nonsecure_endpoint, + grpc_security_handshake_done_cb cb, + void *user_data) { + grpc_ssl_server_security_connector *c = + (grpc_ssl_server_security_connector *)sc; + tsi_handshaker *handshaker; + grpc_security_status status = + ssl_create_handshaker(c->handshaker_factory, false, NULL, &handshaker); + if (status != GRPC_SECURITY_OK) { + cb(exec_ctx, user_data, status, NULL, NULL); + } else { + grpc_do_security_handshake(exec_ctx, handshaker, &sc->base, false, + nonsecure_endpoint, cb, user_data); + } +} + +static int ssl_host_matches_name(const tsi_peer *peer, const char *peer_name) { + char *allocated_name = NULL; + int r; + + if (strchr(peer_name, ':') != NULL) { + char *ignored_port; + gpr_split_host_port(peer_name, &allocated_name, &ignored_port); + gpr_free(ignored_port); + peer_name = allocated_name; + if (!peer_name) return 0; + } + r = tsi_ssl_peer_matches_name(peer, peer_name); + gpr_free(allocated_name); + return r; +} + +grpc_auth_context *tsi_ssl_peer_to_auth_context(const tsi_peer *peer) { + size_t i; + grpc_auth_context *ctx = NULL; + const char *peer_identity_property_name = NULL; + + /* The caller has checked the certificate type property. */ + GPR_ASSERT(peer->property_count >= 1); + ctx = grpc_auth_context_create(NULL); + grpc_auth_context_add_cstring_property( + ctx, GRPC_TRANSPORT_SECURITY_TYPE_PROPERTY_NAME, + GRPC_SSL_TRANSPORT_SECURITY_TYPE); + for (i = 0; i < peer->property_count; i++) { + const tsi_peer_property *prop = &peer->properties[i]; + if (prop->name == NULL) continue; + if (strcmp(prop->name, TSI_X509_SUBJECT_COMMON_NAME_PEER_PROPERTY) == 0) { + /* If there is no subject alt name, have the CN as the identity. */ + if (peer_identity_property_name == NULL) { + peer_identity_property_name = GRPC_X509_CN_PROPERTY_NAME; + } + grpc_auth_context_add_property(ctx, GRPC_X509_CN_PROPERTY_NAME, + prop->value.data, prop->value.length); + } else if (strcmp(prop->name, + TSI_X509_SUBJECT_ALTERNATIVE_NAME_PEER_PROPERTY) == 0) { + peer_identity_property_name = GRPC_X509_SAN_PROPERTY_NAME; + grpc_auth_context_add_property(ctx, GRPC_X509_SAN_PROPERTY_NAME, + prop->value.data, prop->value.length); + } else if (strcmp(prop->name, TSI_X509_PEM_CERT_PROPERTY) == 0) { + grpc_auth_context_add_property(ctx, GRPC_X509_PEM_CERT_PROPERTY_NAME, + prop->value.data, prop->value.length); + } + } + if (peer_identity_property_name != NULL) { + GPR_ASSERT(grpc_auth_context_set_peer_identity_property_name( + ctx, peer_identity_property_name) == 1); + } + return ctx; +} + +static grpc_security_status ssl_check_peer(grpc_security_connector *sc, + const char *peer_name, + const tsi_peer *peer, + grpc_auth_context **auth_context) { + /* Check the ALPN. */ + const tsi_peer_property *p = + tsi_peer_get_property_by_name(peer, TSI_SSL_ALPN_SELECTED_PROTOCOL); + if (p == NULL) { + gpr_log(GPR_ERROR, "Missing selected ALPN property."); + return GRPC_SECURITY_ERROR; + } + if (!grpc_chttp2_is_alpn_version_supported(p->value.data, p->value.length)) { + gpr_log(GPR_ERROR, "Invalid ALPN value."); + return GRPC_SECURITY_ERROR; + } + + /* Check the peer name if specified. */ + if (peer_name != NULL && !ssl_host_matches_name(peer, peer_name)) { + gpr_log(GPR_ERROR, "Peer name %s is not in peer certificate", peer_name); + return GRPC_SECURITY_ERROR; + } + *auth_context = tsi_ssl_peer_to_auth_context(peer); + return GRPC_SECURITY_OK; +} + +static void ssl_channel_check_peer(grpc_exec_ctx *exec_ctx, + grpc_security_connector *sc, tsi_peer peer, + grpc_security_peer_check_cb cb, + void *user_data) { + grpc_ssl_channel_security_connector *c = + (grpc_ssl_channel_security_connector *)sc; + grpc_security_status status; + grpc_auth_context *auth_context = NULL; + status = ssl_check_peer(sc, c->overridden_target_name != NULL + ? c->overridden_target_name + : c->target_name, + &peer, &auth_context); + cb(exec_ctx, user_data, status, auth_context); + grpc_auth_context_unref(auth_context); + tsi_peer_destruct(&peer); +} + +static void ssl_server_check_peer(grpc_exec_ctx *exec_ctx, + grpc_security_connector *sc, tsi_peer peer, + grpc_security_peer_check_cb cb, + void *user_data) { + grpc_auth_context *auth_context = NULL; + grpc_security_status status = ssl_check_peer(sc, NULL, &peer, &auth_context); + tsi_peer_destruct(&peer); + cb(exec_ctx, user_data, status, auth_context); + grpc_auth_context_unref(auth_context); +} + +static void add_shallow_auth_property_to_peer(tsi_peer *peer, + const grpc_auth_property *prop, + const char *tsi_prop_name) { + tsi_peer_property *tsi_prop = &peer->properties[peer->property_count++]; + tsi_prop->name = (char *)tsi_prop_name; + tsi_prop->value.data = prop->value; + tsi_prop->value.length = prop->value_length; +} + +tsi_peer tsi_shallow_peer_from_ssl_auth_context( + const grpc_auth_context *auth_context) { + size_t max_num_props = 0; + grpc_auth_property_iterator it; + const grpc_auth_property *prop; + tsi_peer peer; + memset(&peer, 0, sizeof(peer)); + + it = grpc_auth_context_property_iterator(auth_context); + while (grpc_auth_property_iterator_next(&it) != NULL) max_num_props++; + + if (max_num_props > 0) { + peer.properties = gpr_malloc(max_num_props * sizeof(tsi_peer_property)); + it = grpc_auth_context_property_iterator(auth_context); + while ((prop = grpc_auth_property_iterator_next(&it)) != NULL) { + if (strcmp(prop->name, GRPC_X509_SAN_PROPERTY_NAME) == 0) { + add_shallow_auth_property_to_peer( + &peer, prop, TSI_X509_SUBJECT_ALTERNATIVE_NAME_PEER_PROPERTY); + } else if (strcmp(prop->name, GRPC_X509_CN_PROPERTY_NAME) == 0) { + add_shallow_auth_property_to_peer( + &peer, prop, TSI_X509_SUBJECT_COMMON_NAME_PEER_PROPERTY); + } else if (strcmp(prop->name, GRPC_X509_PEM_CERT_PROPERTY_NAME) == 0) { + add_shallow_auth_property_to_peer(&peer, prop, + TSI_X509_PEM_CERT_PROPERTY); + } + } + } + return peer; +} + +void tsi_shallow_peer_destruct(tsi_peer *peer) { + if (peer->properties != NULL) gpr_free(peer->properties); +} + +static void ssl_channel_check_call_host(grpc_exec_ctx *exec_ctx, + grpc_channel_security_connector *sc, + const char *host, + grpc_auth_context *auth_context, + grpc_security_call_host_check_cb cb, + void *user_data) { + grpc_ssl_channel_security_connector *c = + (grpc_ssl_channel_security_connector *)sc; + grpc_security_status status = GRPC_SECURITY_ERROR; + tsi_peer peer = tsi_shallow_peer_from_ssl_auth_context(auth_context); + if (ssl_host_matches_name(&peer, host)) status = GRPC_SECURITY_OK; + + /* If the target name was overridden, then the original target_name was + 'checked' transitively during the previous peer check at the end of the + handshake. */ + if (c->overridden_target_name != NULL && strcmp(host, c->target_name) == 0) { + status = GRPC_SECURITY_OK; + } + cb(exec_ctx, user_data, status); + tsi_shallow_peer_destruct(&peer); +} + +static grpc_security_connector_vtable ssl_channel_vtable = { + ssl_channel_destroy, ssl_channel_check_peer}; + +static grpc_security_connector_vtable ssl_server_vtable = { + ssl_server_destroy, ssl_server_check_peer}; + +static gpr_slice compute_default_pem_root_certs_once(void) { + gpr_slice result = gpr_empty_slice(); + + /* First try to load the roots from the environment. */ + char *default_root_certs_path = + gpr_getenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR); + if (default_root_certs_path != NULL) { + result = gpr_load_file(default_root_certs_path, 0, NULL); + gpr_free(default_root_certs_path); + } + + /* Try overridden roots if needed. */ + grpc_ssl_roots_override_result ovrd_res = GRPC_SSL_ROOTS_OVERRIDE_FAIL; + if (GPR_SLICE_IS_EMPTY(result) && ssl_roots_override_cb != NULL) { + char *pem_root_certs = NULL; + ovrd_res = ssl_roots_override_cb(&pem_root_certs); + if (ovrd_res == GRPC_SSL_ROOTS_OVERRIDE_OK) { + GPR_ASSERT(pem_root_certs != NULL); + result = gpr_slice_new(pem_root_certs, strlen(pem_root_certs), gpr_free); + } + } + + /* Fall back to installed certs if needed. */ + if (GPR_SLICE_IS_EMPTY(result) && + ovrd_res != GRPC_SSL_ROOTS_OVERRIDE_FAIL_PERMANENTLY) { + result = gpr_load_file(installed_roots_path, 0, NULL); + } + return result; +} + +static gpr_slice default_pem_root_certs; + +static void init_default_pem_root_certs(void) { + default_pem_root_certs = compute_default_pem_root_certs_once(); +} + +gpr_slice grpc_get_default_ssl_roots_for_testing(void) { + return compute_default_pem_root_certs_once(); +} + +size_t grpc_get_default_ssl_roots(const unsigned char **pem_root_certs) { + /* TODO(jboeuf@google.com): Maybe revisit the approach which consists in + loading all the roots once for the lifetime of the process. */ + static gpr_once once = GPR_ONCE_INIT; + gpr_once_init(&once, init_default_pem_root_certs); + *pem_root_certs = GPR_SLICE_START_PTR(default_pem_root_certs); + return GPR_SLICE_LENGTH(default_pem_root_certs); +} + +grpc_security_status grpc_ssl_channel_security_connector_create( + grpc_call_credentials *request_metadata_creds, + const grpc_ssl_config *config, const char *target_name, + const char *overridden_target_name, grpc_channel_security_connector **sc) { + size_t num_alpn_protocols = grpc_chttp2_num_alpn_versions(); + const unsigned char **alpn_protocol_strings = + gpr_malloc(sizeof(const char *) * num_alpn_protocols); + unsigned char *alpn_protocol_string_lengths = + gpr_malloc(sizeof(unsigned char) * num_alpn_protocols); + tsi_result result = TSI_OK; + grpc_ssl_channel_security_connector *c; + size_t i; + const unsigned char *pem_root_certs; + size_t pem_root_certs_size; + char *port; + + for (i = 0; i < num_alpn_protocols; i++) { + alpn_protocol_strings[i] = + (const unsigned char *)grpc_chttp2_get_alpn_version_index(i); + alpn_protocol_string_lengths[i] = + (unsigned char)strlen(grpc_chttp2_get_alpn_version_index(i)); + } + + if (config == NULL || target_name == NULL) { + gpr_log(GPR_ERROR, "An ssl channel needs a config and a target name."); + goto error; + } + if (config->pem_root_certs == NULL) { + pem_root_certs_size = grpc_get_default_ssl_roots(&pem_root_certs); + if (pem_root_certs == NULL || pem_root_certs_size == 0) { + gpr_log(GPR_ERROR, "Could not get default pem root certs."); + goto error; + } + } else { + pem_root_certs = config->pem_root_certs; + pem_root_certs_size = config->pem_root_certs_size; + } + + c = gpr_malloc(sizeof(grpc_ssl_channel_security_connector)); + memset(c, 0, sizeof(grpc_ssl_channel_security_connector)); + + gpr_ref_init(&c->base.base.refcount, 1); + c->base.base.vtable = &ssl_channel_vtable; + c->base.base.url_scheme = GRPC_SSL_URL_SCHEME; + c->base.request_metadata_creds = + grpc_call_credentials_ref(request_metadata_creds); + c->base.check_call_host = ssl_channel_check_call_host; + c->base.do_handshake = ssl_channel_do_handshake; + gpr_split_host_port(target_name, &c->target_name, &port); + gpr_free(port); + if (overridden_target_name != NULL) { + c->overridden_target_name = gpr_strdup(overridden_target_name); + } + result = tsi_create_ssl_client_handshaker_factory( + config->pem_private_key, config->pem_private_key_size, + config->pem_cert_chain, config->pem_cert_chain_size, pem_root_certs, + pem_root_certs_size, ssl_cipher_suites(), alpn_protocol_strings, + alpn_protocol_string_lengths, (uint16_t)num_alpn_protocols, + &c->handshaker_factory); + if (result != TSI_OK) { + gpr_log(GPR_ERROR, "Handshaker factory creation failed with %s.", + tsi_result_to_string(result)); + ssl_channel_destroy(&c->base.base); + *sc = NULL; + goto error; + } + *sc = &c->base; + gpr_free((void *)alpn_protocol_strings); + gpr_free(alpn_protocol_string_lengths); + return GRPC_SECURITY_OK; + +error: + gpr_free((void *)alpn_protocol_strings); + gpr_free(alpn_protocol_string_lengths); + return GRPC_SECURITY_ERROR; +} + +grpc_security_status grpc_ssl_server_security_connector_create( + const grpc_ssl_server_config *config, grpc_server_security_connector **sc) { + size_t num_alpn_protocols = grpc_chttp2_num_alpn_versions(); + const unsigned char **alpn_protocol_strings = + gpr_malloc(sizeof(const char *) * num_alpn_protocols); + unsigned char *alpn_protocol_string_lengths = + gpr_malloc(sizeof(unsigned char) * num_alpn_protocols); + tsi_result result = TSI_OK; + grpc_ssl_server_security_connector *c; + size_t i; + + for (i = 0; i < num_alpn_protocols; i++) { + alpn_protocol_strings[i] = + (const unsigned char *)grpc_chttp2_get_alpn_version_index(i); + alpn_protocol_string_lengths[i] = + (unsigned char)strlen(grpc_chttp2_get_alpn_version_index(i)); + } + + if (config == NULL || config->num_key_cert_pairs == 0) { + gpr_log(GPR_ERROR, "An SSL server needs a key and a cert."); + goto error; + } + c = gpr_malloc(sizeof(grpc_ssl_server_security_connector)); + memset(c, 0, sizeof(grpc_ssl_server_security_connector)); + + gpr_ref_init(&c->base.base.refcount, 1); + c->base.base.url_scheme = GRPC_SSL_URL_SCHEME; + c->base.base.vtable = &ssl_server_vtable; + result = tsi_create_ssl_server_handshaker_factory( + (const unsigned char **)config->pem_private_keys, + config->pem_private_keys_sizes, + (const unsigned char **)config->pem_cert_chains, + config->pem_cert_chains_sizes, config->num_key_cert_pairs, + config->pem_root_certs, config->pem_root_certs_size, + config->force_client_auth, ssl_cipher_suites(), alpn_protocol_strings, + alpn_protocol_string_lengths, (uint16_t)num_alpn_protocols, + &c->handshaker_factory); + if (result != TSI_OK) { + gpr_log(GPR_ERROR, "Handshaker factory creation failed with %s.", + tsi_result_to_string(result)); + ssl_server_destroy(&c->base.base); + *sc = NULL; + goto error; + } + gpr_mu_init(&c->base.mu); + c->base.do_handshake = ssl_server_do_handshake; + *sc = &c->base; + gpr_free((void *)alpn_protocol_strings); + gpr_free(alpn_protocol_string_lengths); + return GRPC_SECURITY_OK; + +error: + gpr_free((void *)alpn_protocol_strings); + gpr_free(alpn_protocol_string_lengths); + return GRPC_SECURITY_ERROR; +} diff --git a/src/core/lib/security/security_connector.h b/src/core/lib/security/security_connector.h new file mode 100644 index 0000000000..d50091c628 --- /dev/null +++ b/src/core/lib/security/security_connector.h @@ -0,0 +1,266 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SECURITY_SECURITY_CONNECTOR_H +#define GRPC_CORE_LIB_SECURITY_SECURITY_CONNECTOR_H + +#include <grpc/grpc_security.h> +#include "src/core/lib/iomgr/endpoint.h" +#include "src/core/lib/iomgr/tcp_server.h" +#include "src/core/lib/tsi/transport_security_interface.h" + +/* --- status enum. --- */ + +typedef enum { GRPC_SECURITY_OK = 0, GRPC_SECURITY_ERROR } grpc_security_status; + +/* --- URL schemes. --- */ + +#define GRPC_SSL_URL_SCHEME "https" +#define GRPC_FAKE_SECURITY_URL_SCHEME "http+fake_security" + +/* --- security_connector object. --- + + A security connector object represents away to configure the underlying + transport security mechanism and check the resulting trusted peer. */ + +typedef struct grpc_security_connector grpc_security_connector; + +#define GRPC_SECURITY_CONNECTOR_ARG "grpc.security_connector" + +typedef void (*grpc_security_peer_check_cb)(grpc_exec_ctx *exec_ctx, + void *user_data, + grpc_security_status status, + grpc_auth_context *auth_context); + +/* Ownership of the secure_endpoint is transfered. */ +typedef void (*grpc_security_handshake_done_cb)( + grpc_exec_ctx *exec_ctx, void *user_data, grpc_security_status status, + grpc_endpoint *secure_endpoint, grpc_auth_context *auth_context); + +typedef struct { + void (*destroy)(grpc_security_connector *sc); + void (*check_peer)(grpc_exec_ctx *exec_ctx, grpc_security_connector *sc, + tsi_peer peer, grpc_security_peer_check_cb cb, + void *user_data); +} grpc_security_connector_vtable; + +typedef struct grpc_security_connector_handshake_list { + void *handshake; + struct grpc_security_connector_handshake_list *next; +} grpc_security_connector_handshake_list; + +struct grpc_security_connector { + const grpc_security_connector_vtable *vtable; + gpr_refcount refcount; + const char *url_scheme; +}; + +/* Refcounting. */ +#ifdef GRPC_SECURITY_CONNECTOR_REFCOUNT_DEBUG +#define GRPC_SECURITY_CONNECTOR_REF(p, r) \ + grpc_security_connector_ref((p), __FILE__, __LINE__, (r)) +#define GRPC_SECURITY_CONNECTOR_UNREF(p, r) \ + grpc_security_connector_unref((p), __FILE__, __LINE__, (r)) +grpc_security_connector *grpc_security_connector_ref( + grpc_security_connector *policy, const char *file, int line, + const char *reason); +void grpc_security_connector_unref(grpc_security_connector *policy, + const char *file, int line, + const char *reason); +#else +#define GRPC_SECURITY_CONNECTOR_REF(p, r) grpc_security_connector_ref((p)) +#define GRPC_SECURITY_CONNECTOR_UNREF(p, r) grpc_security_connector_unref((p)) +grpc_security_connector *grpc_security_connector_ref( + grpc_security_connector *policy); +void grpc_security_connector_unref(grpc_security_connector *policy); +#endif + +/* Check the peer. Callee takes ownership of the peer object. + The callback will include the resulting auth_context. */ +void grpc_security_connector_check_peer(grpc_exec_ctx *exec_ctx, + grpc_security_connector *sc, + tsi_peer peer, + grpc_security_peer_check_cb cb, + void *user_data); + +/* Util to encapsulate the connector in a channel arg. */ +grpc_arg grpc_security_connector_to_arg(grpc_security_connector *sc); + +/* Util to get the connector from a channel arg. */ +grpc_security_connector *grpc_security_connector_from_arg(const grpc_arg *arg); + +/* Util to find the connector from channel args. */ +grpc_security_connector *grpc_find_security_connector_in_args( + const grpc_channel_args *args); + +/* --- channel_security_connector object. --- + + A channel security connector object represents away to configure the + underlying transport security mechanism on the client side. */ + +typedef struct grpc_channel_security_connector grpc_channel_security_connector; + +typedef void (*grpc_security_call_host_check_cb)(grpc_exec_ctx *exec_ctx, + void *user_data, + grpc_security_status status); + +struct grpc_channel_security_connector { + grpc_security_connector base; + grpc_call_credentials *request_metadata_creds; + void (*check_call_host)(grpc_exec_ctx *exec_ctx, + grpc_channel_security_connector *sc, const char *host, + grpc_auth_context *auth_context, + grpc_security_call_host_check_cb cb, void *user_data); + void (*do_handshake)(grpc_exec_ctx *exec_ctx, + grpc_channel_security_connector *sc, + grpc_endpoint *nonsecure_endpoint, + grpc_security_handshake_done_cb cb, void *user_data); +}; + +/* Checks that the host that will be set for a call is acceptable. */ +void grpc_channel_security_connector_check_call_host( + grpc_exec_ctx *exec_ctx, grpc_channel_security_connector *sc, + const char *host, grpc_auth_context *auth_context, + grpc_security_call_host_check_cb cb, void *user_data); + +/* Handshake. */ +void grpc_channel_security_connector_do_handshake( + grpc_exec_ctx *exec_ctx, grpc_channel_security_connector *connector, + grpc_endpoint *nonsecure_endpoint, grpc_security_handshake_done_cb cb, + void *user_data); + +/* --- server_security_connector object. --- + + A server security connector object represents away to configure the + underlying transport security mechanism on the server side. */ + +typedef struct grpc_server_security_connector grpc_server_security_connector; + +struct grpc_server_security_connector { + grpc_security_connector base; + gpr_mu mu; + grpc_security_connector_handshake_list *handshaking_handshakes; + const grpc_channel_args *channel_args; + void (*do_handshake)(grpc_exec_ctx *exec_ctx, + grpc_server_security_connector *sc, + grpc_tcp_server_acceptor *acceptor, + grpc_endpoint *nonsecure_endpoint, + grpc_security_handshake_done_cb cb, void *user_data); +}; + +void grpc_server_security_connector_do_handshake( + grpc_exec_ctx *exec_ctx, grpc_server_security_connector *sc, + grpc_tcp_server_acceptor *acceptor, grpc_endpoint *nonsecure_endpoint, + grpc_security_handshake_done_cb cb, void *user_data); + +void grpc_server_security_connector_shutdown( + grpc_exec_ctx *exec_ctx, grpc_server_security_connector *connector); + +/* --- Creation security connectors. --- */ + +/* For TESTING ONLY! + Creates a fake connector that emulates real channel security. */ +grpc_channel_security_connector *grpc_fake_channel_security_connector_create( + grpc_call_credentials *request_metadata_creds); + +/* For TESTING ONLY! + Creates a fake connector that emulates real server security. */ +grpc_server_security_connector *grpc_fake_server_security_connector_create( + void); + +/* Config for ssl clients. */ +typedef struct { + unsigned char *pem_private_key; + size_t pem_private_key_size; + unsigned char *pem_cert_chain; + size_t pem_cert_chain_size; + unsigned char *pem_root_certs; + size_t pem_root_certs_size; +} grpc_ssl_config; + +/* Creates an SSL channel_security_connector. + - request_metadata_creds is the credentials object which metadata + will be sent with each request. This parameter can be NULL. + - config is the SSL config to be used for the SSL channel establishment. + - is_client should be 0 for a server or a non-0 value for a client. + - secure_peer_name is the secure peer name that should be checked in + grpc_channel_security_connector_check_peer. This parameter may be NULL in + which case the peer name will not be checked. Note that if this parameter + is not NULL, then, pem_root_certs should not be NULL either. + - sc is a pointer on the connector to be created. + This function returns GRPC_SECURITY_OK in case of success or a + specific error code otherwise. +*/ +grpc_security_status grpc_ssl_channel_security_connector_create( + grpc_call_credentials *request_metadata_creds, + const grpc_ssl_config *config, const char *target_name, + const char *overridden_target_name, grpc_channel_security_connector **sc); + +/* Gets the default ssl roots. */ +size_t grpc_get_default_ssl_roots(const unsigned char **pem_root_certs); + +/* Exposed for TESTING ONLY!. */ +gpr_slice grpc_get_default_ssl_roots_for_testing(void); + +/* Config for ssl servers. */ +typedef struct { + unsigned char **pem_private_keys; + size_t *pem_private_keys_sizes; + unsigned char **pem_cert_chains; + size_t *pem_cert_chains_sizes; + size_t num_key_cert_pairs; + unsigned char *pem_root_certs; + size_t pem_root_certs_size; + int force_client_auth; +} grpc_ssl_server_config; + +/* Creates an SSL server_security_connector. + - config is the SSL config to be used for the SSL channel establishment. + - sc is a pointer on the connector to be created. + This function returns GRPC_SECURITY_OK in case of success or a + specific error code otherwise. +*/ +grpc_security_status grpc_ssl_server_security_connector_create( + const grpc_ssl_server_config *config, grpc_server_security_connector **sc); + +/* Util. */ +const tsi_peer_property *tsi_peer_get_property_by_name(const tsi_peer *peer, + const char *name); + +/* Exposed for testing only. */ +grpc_auth_context *tsi_ssl_peer_to_auth_context(const tsi_peer *peer); +tsi_peer tsi_shallow_peer_from_ssl_auth_context( + const grpc_auth_context *auth_context); +void tsi_shallow_peer_destruct(tsi_peer *peer); + +#endif /* GRPC_CORE_LIB_SECURITY_SECURITY_CONNECTOR_H */ diff --git a/src/core/lib/security/security_context.c b/src/core/lib/security/security_context.c new file mode 100644 index 0000000000..0e66373bd8 --- /dev/null +++ b/src/core/lib/security/security_context.c @@ -0,0 +1,347 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <string.h> + +#include "src/core/lib/security/security_context.h" +#include "src/core/lib/support/string.h" +#include "src/core/lib/surface/api_trace.h" +#include "src/core/lib/surface/call.h" + +#include <grpc/grpc_security.h> +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> + +/* --- grpc_call --- */ + +grpc_call_error grpc_call_set_credentials(grpc_call *call, + grpc_call_credentials *creds) { + grpc_client_security_context *ctx = NULL; + GRPC_API_TRACE("grpc_call_set_credentials(call=%p, creds=%p)", 2, + (call, creds)); + if (!grpc_call_is_client(call)) { + gpr_log(GPR_ERROR, "Method is client-side only."); + return GRPC_CALL_ERROR_NOT_ON_SERVER; + } + ctx = (grpc_client_security_context *)grpc_call_context_get( + call, GRPC_CONTEXT_SECURITY); + if (ctx == NULL) { + ctx = grpc_client_security_context_create(); + ctx->creds = grpc_call_credentials_ref(creds); + grpc_call_context_set(call, GRPC_CONTEXT_SECURITY, ctx, + grpc_client_security_context_destroy); + } else { + grpc_call_credentials_unref(ctx->creds); + ctx->creds = grpc_call_credentials_ref(creds); + } + return GRPC_CALL_OK; +} + +grpc_auth_context *grpc_call_auth_context(grpc_call *call) { + void *sec_ctx = grpc_call_context_get(call, GRPC_CONTEXT_SECURITY); + GRPC_API_TRACE("grpc_call_auth_context(call=%p)", 1, (call)); + if (sec_ctx == NULL) return NULL; + return grpc_call_is_client(call) + ? GRPC_AUTH_CONTEXT_REF( + ((grpc_client_security_context *)sec_ctx)->auth_context, + "grpc_call_auth_context client") + : GRPC_AUTH_CONTEXT_REF( + ((grpc_server_security_context *)sec_ctx)->auth_context, + "grpc_call_auth_context server"); +} + +void grpc_auth_context_release(grpc_auth_context *context) { + GRPC_API_TRACE("grpc_auth_context_release(context=%p)", 1, (context)); + GRPC_AUTH_CONTEXT_UNREF(context, "grpc_auth_context_unref"); +} + +/* --- grpc_client_security_context --- */ + +grpc_client_security_context *grpc_client_security_context_create(void) { + grpc_client_security_context *ctx = + gpr_malloc(sizeof(grpc_client_security_context)); + memset(ctx, 0, sizeof(grpc_client_security_context)); + return ctx; +} + +void grpc_client_security_context_destroy(void *ctx) { + grpc_client_security_context *c = (grpc_client_security_context *)ctx; + grpc_call_credentials_unref(c->creds); + GRPC_AUTH_CONTEXT_UNREF(c->auth_context, "client_security_context"); + gpr_free(ctx); +} + +/* --- grpc_server_security_context --- */ + +grpc_server_security_context *grpc_server_security_context_create(void) { + grpc_server_security_context *ctx = + gpr_malloc(sizeof(grpc_server_security_context)); + memset(ctx, 0, sizeof(grpc_server_security_context)); + return ctx; +} + +void grpc_server_security_context_destroy(void *ctx) { + grpc_server_security_context *c = (grpc_server_security_context *)ctx; + GRPC_AUTH_CONTEXT_UNREF(c->auth_context, "server_security_context"); + gpr_free(ctx); +} + +/* --- grpc_auth_context --- */ + +static grpc_auth_property_iterator empty_iterator = {NULL, 0, NULL}; + +grpc_auth_context *grpc_auth_context_create(grpc_auth_context *chained) { + grpc_auth_context *ctx = gpr_malloc(sizeof(grpc_auth_context)); + memset(ctx, 0, sizeof(grpc_auth_context)); + gpr_ref_init(&ctx->refcount, 1); + if (chained != NULL) { + ctx->chained = GRPC_AUTH_CONTEXT_REF(chained, "chained"); + ctx->peer_identity_property_name = + ctx->chained->peer_identity_property_name; + } + return ctx; +} + +#ifdef GRPC_AUTH_CONTEXT_REFCOUNT_DEBUG +grpc_auth_context *grpc_auth_context_ref(grpc_auth_context *ctx, + const char *file, int line, + const char *reason) { + if (ctx == NULL) return NULL; + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, + "AUTH_CONTEXT:%p ref %d -> %d %s", ctx, (int)ctx->refcount.count, + (int)ctx->refcount.count + 1, reason); +#else +grpc_auth_context *grpc_auth_context_ref(grpc_auth_context *ctx) { + if (ctx == NULL) return NULL; +#endif + gpr_ref(&ctx->refcount); + return ctx; +} + +#ifdef GRPC_AUTH_CONTEXT_REFCOUNT_DEBUG +void grpc_auth_context_unref(grpc_auth_context *ctx, const char *file, int line, + const char *reason) { + if (ctx == NULL) return; + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, + "AUTH_CONTEXT:%p unref %d -> %d %s", ctx, (int)ctx->refcount.count, + (int)ctx->refcount.count - 1, reason); +#else +void grpc_auth_context_unref(grpc_auth_context *ctx) { + if (ctx == NULL) return; +#endif + if (gpr_unref(&ctx->refcount)) { + size_t i; + GRPC_AUTH_CONTEXT_UNREF(ctx->chained, "chained"); + if (ctx->properties.array != NULL) { + for (i = 0; i < ctx->properties.count; i++) { + grpc_auth_property_reset(&ctx->properties.array[i]); + } + gpr_free(ctx->properties.array); + } + gpr_free(ctx); + } +} + +const char *grpc_auth_context_peer_identity_property_name( + const grpc_auth_context *ctx) { + GRPC_API_TRACE("grpc_auth_context_peer_identity_property_name(ctx=%p)", 1, + (ctx)); + return ctx->peer_identity_property_name; +} + +int grpc_auth_context_set_peer_identity_property_name(grpc_auth_context *ctx, + const char *name) { + grpc_auth_property_iterator it = + grpc_auth_context_find_properties_by_name(ctx, name); + const grpc_auth_property *prop = grpc_auth_property_iterator_next(&it); + GRPC_API_TRACE( + "grpc_auth_context_set_peer_identity_property_name(ctx=%p, name=%s)", 2, + (ctx, name)); + if (prop == NULL) { + gpr_log(GPR_ERROR, "Property name %s not found in auth context.", + name != NULL ? name : "NULL"); + return 0; + } + ctx->peer_identity_property_name = prop->name; + return 1; +} + +int grpc_auth_context_peer_is_authenticated(const grpc_auth_context *ctx) { + GRPC_API_TRACE("grpc_auth_context_peer_is_authenticated(ctx=%p)", 1, (ctx)); + return ctx->peer_identity_property_name == NULL ? 0 : 1; +} + +grpc_auth_property_iterator grpc_auth_context_property_iterator( + const grpc_auth_context *ctx) { + grpc_auth_property_iterator it = empty_iterator; + GRPC_API_TRACE("grpc_auth_context_property_iterator(ctx=%p)", 1, (ctx)); + if (ctx == NULL) return it; + it.ctx = ctx; + return it; +} + +const grpc_auth_property *grpc_auth_property_iterator_next( + grpc_auth_property_iterator *it) { + GRPC_API_TRACE("grpc_auth_property_iterator_next(it=%p)", 1, (it)); + if (it == NULL || it->ctx == NULL) return NULL; + while (it->index == it->ctx->properties.count) { + if (it->ctx->chained == NULL) return NULL; + it->ctx = it->ctx->chained; + it->index = 0; + } + if (it->name == NULL) { + return &it->ctx->properties.array[it->index++]; + } else { + while (it->index < it->ctx->properties.count) { + const grpc_auth_property *prop = &it->ctx->properties.array[it->index++]; + GPR_ASSERT(prop->name != NULL); + if (strcmp(it->name, prop->name) == 0) { + return prop; + } + } + /* We could not find the name, try another round. */ + return grpc_auth_property_iterator_next(it); + } +} + +grpc_auth_property_iterator grpc_auth_context_find_properties_by_name( + const grpc_auth_context *ctx, const char *name) { + grpc_auth_property_iterator it = empty_iterator; + GRPC_API_TRACE("grpc_auth_context_find_properties_by_name(ctx=%p, name=%s)", + 2, (ctx, name)); + if (ctx == NULL || name == NULL) return empty_iterator; + it.ctx = ctx; + it.name = name; + return it; +} + +grpc_auth_property_iterator grpc_auth_context_peer_identity( + const grpc_auth_context *ctx) { + GRPC_API_TRACE("grpc_auth_context_peer_identity(ctx=%p)", 1, (ctx)); + if (ctx == NULL) return empty_iterator; + return grpc_auth_context_find_properties_by_name( + ctx, ctx->peer_identity_property_name); +} + +static void ensure_auth_context_capacity(grpc_auth_context *ctx) { + if (ctx->properties.count == ctx->properties.capacity) { + ctx->properties.capacity = + GPR_MAX(ctx->properties.capacity + 8, ctx->properties.capacity * 2); + ctx->properties.array = + gpr_realloc(ctx->properties.array, + ctx->properties.capacity * sizeof(grpc_auth_property)); + } +} + +void grpc_auth_context_add_property(grpc_auth_context *ctx, const char *name, + const char *value, size_t value_length) { + grpc_auth_property *prop; + GRPC_API_TRACE( + "grpc_auth_context_add_property(ctx=%p, name=%s, value=%*.*s, " + "value_length=%lu)", + 6, (ctx, name, (int)value_length, (int)value_length, value, + (unsigned long)value_length)); + ensure_auth_context_capacity(ctx); + prop = &ctx->properties.array[ctx->properties.count++]; + prop->name = gpr_strdup(name); + prop->value = gpr_malloc(value_length + 1); + memcpy(prop->value, value, value_length); + prop->value[value_length] = '\0'; + prop->value_length = value_length; +} + +void grpc_auth_context_add_cstring_property(grpc_auth_context *ctx, + const char *name, + const char *value) { + grpc_auth_property *prop; + GRPC_API_TRACE( + "grpc_auth_context_add_cstring_property(ctx=%p, name=%s, value=%s)", 3, + (ctx, name, value)); + ensure_auth_context_capacity(ctx); + prop = &ctx->properties.array[ctx->properties.count++]; + prop->name = gpr_strdup(name); + prop->value = gpr_strdup(value); + prop->value_length = strlen(value); +} + +void grpc_auth_property_reset(grpc_auth_property *property) { + gpr_free(property->name); + gpr_free(property->value); + memset(property, 0, sizeof(grpc_auth_property)); +} + +static void auth_context_pointer_arg_destroy(void *p) { + GRPC_AUTH_CONTEXT_UNREF(p, "auth_context_pointer_arg"); +} + +static void *auth_context_pointer_arg_copy(void *p) { + return GRPC_AUTH_CONTEXT_REF(p, "auth_context_pointer_arg"); +} + +static int auth_context_pointer_cmp(void *a, void *b) { return GPR_ICMP(a, b); } + +static const grpc_arg_pointer_vtable auth_context_pointer_vtable = { + auth_context_pointer_arg_copy, auth_context_pointer_arg_destroy, + auth_context_pointer_cmp}; + +grpc_arg grpc_auth_context_to_arg(grpc_auth_context *p) { + grpc_arg arg; + memset(&arg, 0, sizeof(grpc_arg)); + arg.type = GRPC_ARG_POINTER; + arg.key = GRPC_AUTH_CONTEXT_ARG; + arg.value.pointer.p = p; + arg.value.pointer.vtable = &auth_context_pointer_vtable; + return arg; +} + +grpc_auth_context *grpc_auth_context_from_arg(const grpc_arg *arg) { + if (strcmp(arg->key, GRPC_AUTH_CONTEXT_ARG) != 0) return NULL; + if (arg->type != GRPC_ARG_POINTER) { + gpr_log(GPR_ERROR, "Invalid type %d for arg %s", arg->type, + GRPC_AUTH_CONTEXT_ARG); + return NULL; + } + return arg->value.pointer.p; +} + +grpc_auth_context *grpc_find_auth_context_in_args( + const grpc_channel_args *args) { + size_t i; + if (args == NULL) return NULL; + for (i = 0; i < args->num_args; i++) { + grpc_auth_context *p = grpc_auth_context_from_arg(&args->args[i]); + if (p != NULL) return p; + } + return NULL; +} diff --git a/src/core/lib/security/security_context.h b/src/core/lib/security/security_context.h new file mode 100644 index 0000000000..e9e4e503bc --- /dev/null +++ b/src/core/lib/security/security_context.h @@ -0,0 +1,114 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SECURITY_SECURITY_CONTEXT_H +#define GRPC_CORE_LIB_SECURITY_SECURITY_CONTEXT_H + +#include "src/core/lib/iomgr/pollset.h" +#include "src/core/lib/security/credentials.h" + +/* --- grpc_auth_context --- + + High level authentication context object. Can optionally be chained. */ + +/* Property names are always NULL terminated. */ + +typedef struct { + grpc_auth_property *array; + size_t count; + size_t capacity; +} grpc_auth_property_array; + +struct grpc_auth_context { + struct grpc_auth_context *chained; + grpc_auth_property_array properties; + gpr_refcount refcount; + const char *peer_identity_property_name; + grpc_pollset *pollset; +}; + +/* Creation. */ +grpc_auth_context *grpc_auth_context_create(grpc_auth_context *chained); + +/* Refcounting. */ +#ifdef GRPC_AUTH_CONTEXT_REFCOUNT_DEBUG +#define GRPC_AUTH_CONTEXT_REF(p, r) \ + grpc_auth_context_ref((p), __FILE__, __LINE__, (r)) +#define GRPC_AUTH_CONTEXT_UNREF(p, r) \ + grpc_auth_context_unref((p), __FILE__, __LINE__, (r)) +grpc_auth_context *grpc_auth_context_ref(grpc_auth_context *policy, + const char *file, int line, + const char *reason); +void grpc_auth_context_unref(grpc_auth_context *policy, const char *file, + int line, const char *reason); +#else +#define GRPC_AUTH_CONTEXT_REF(p, r) grpc_auth_context_ref((p)) +#define GRPC_AUTH_CONTEXT_UNREF(p, r) grpc_auth_context_unref((p)) +grpc_auth_context *grpc_auth_context_ref(grpc_auth_context *policy); +void grpc_auth_context_unref(grpc_auth_context *policy); +#endif + +void grpc_auth_property_reset(grpc_auth_property *property); + +/* --- grpc_client_security_context --- + + Internal client-side security context. */ + +typedef struct { + grpc_call_credentials *creds; + grpc_auth_context *auth_context; +} grpc_client_security_context; + +grpc_client_security_context *grpc_client_security_context_create(void); +void grpc_client_security_context_destroy(void *ctx); + +/* --- grpc_server_security_context --- + + Internal server-side security context. */ + +typedef struct { + grpc_auth_context *auth_context; +} grpc_server_security_context; + +grpc_server_security_context *grpc_server_security_context_create(void); +void grpc_server_security_context_destroy(void *ctx); + +/* --- Channel args for auth context --- */ +#define GRPC_AUTH_CONTEXT_ARG "grpc.auth_context" + +grpc_arg grpc_auth_context_to_arg(grpc_auth_context *c); +grpc_auth_context *grpc_auth_context_from_arg(const grpc_arg *arg); +grpc_auth_context *grpc_find_auth_context_in_args( + const grpc_channel_args *args); + +#endif /* GRPC_CORE_LIB_SECURITY_SECURITY_CONTEXT_H */ diff --git a/src/core/lib/security/server_auth_filter.c b/src/core/lib/security/server_auth_filter.c new file mode 100644 index 0000000000..158cde0e2c --- /dev/null +++ b/src/core/lib/security/server_auth_filter.c @@ -0,0 +1,264 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <string.h> + +#include "src/core/lib/security/auth_filters.h" +#include "src/core/lib/security/credentials.h" +#include "src/core/lib/security/security_context.h" + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +typedef struct call_data { + grpc_metadata_batch *recv_initial_metadata; + /* Closure to call when finished with the auth_on_recv hook. */ + grpc_closure *on_done_recv; + /* Receive closures are chained: we inject this closure as the on_done_recv + up-call on transport_op, and remember to call our on_done_recv member after + handling it. */ + grpc_closure auth_on_recv; + grpc_transport_stream_op transport_op; + grpc_metadata_array md; + const grpc_metadata *consumed_md; + size_t num_consumed_md; + grpc_auth_context *auth_context; +} call_data; + +typedef struct channel_data { + grpc_auth_context *auth_context; + grpc_server_credentials *creds; +} channel_data; + +static grpc_metadata_array metadata_batch_to_md_array( + const grpc_metadata_batch *batch) { + grpc_linked_mdelem *l; + grpc_metadata_array result; + grpc_metadata_array_init(&result); + for (l = batch->list.head; l != NULL; l = l->next) { + grpc_metadata *usr_md = NULL; + grpc_mdelem *md = l->md; + grpc_mdstr *key = md->key; + grpc_mdstr *value = md->value; + if (result.count == result.capacity) { + result.capacity = GPR_MAX(result.capacity + 8, result.capacity * 2); + result.metadata = + gpr_realloc(result.metadata, result.capacity * sizeof(grpc_metadata)); + } + usr_md = &result.metadata[result.count++]; + usr_md->key = grpc_mdstr_as_c_string(key); + usr_md->value = grpc_mdstr_as_c_string(value); + usr_md->value_length = GPR_SLICE_LENGTH(value->slice); + } + return result; +} + +static grpc_mdelem *remove_consumed_md(void *user_data, grpc_mdelem *md) { + grpc_call_element *elem = user_data; + call_data *calld = elem->call_data; + size_t i; + for (i = 0; i < calld->num_consumed_md; i++) { + const grpc_metadata *consumed_md = &calld->consumed_md[i]; + /* Maybe we could do a pointer comparison but we do not have any guarantee + that the metadata processor used the same pointers for consumed_md in the + callback. */ + if (GPR_SLICE_LENGTH(md->key->slice) != strlen(consumed_md->key) || + GPR_SLICE_LENGTH(md->value->slice) != consumed_md->value_length) { + continue; + } + if (memcmp(GPR_SLICE_START_PTR(md->key->slice), consumed_md->key, + GPR_SLICE_LENGTH(md->key->slice)) == 0 && + memcmp(GPR_SLICE_START_PTR(md->value->slice), consumed_md->value, + GPR_SLICE_LENGTH(md->value->slice)) == 0) { + return NULL; /* Delete. */ + } + } + return md; +} + +/* called from application code */ +static void on_md_processing_done( + void *user_data, const grpc_metadata *consumed_md, size_t num_consumed_md, + const grpc_metadata *response_md, size_t num_response_md, + grpc_status_code status, const char *error_details) { + grpc_call_element *elem = user_data; + call_data *calld = elem->call_data; + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + + /* TODO(jboeuf): Implement support for response_md. */ + if (response_md != NULL && num_response_md > 0) { + gpr_log(GPR_INFO, + "response_md in auth metadata processing not supported for now. " + "Ignoring..."); + } + + if (status == GRPC_STATUS_OK) { + calld->consumed_md = consumed_md; + calld->num_consumed_md = num_consumed_md; + grpc_metadata_batch_filter(calld->recv_initial_metadata, remove_consumed_md, + elem); + grpc_metadata_array_destroy(&calld->md); + calld->on_done_recv->cb(&exec_ctx, calld->on_done_recv->cb_arg, 1); + } else { + gpr_slice message; + grpc_transport_stream_op close_op; + memset(&close_op, 0, sizeof(close_op)); + grpc_metadata_array_destroy(&calld->md); + error_details = error_details != NULL + ? error_details + : "Authentication metadata processing failed."; + message = gpr_slice_from_copied_string(error_details); + calld->transport_op.send_initial_metadata = NULL; + if (calld->transport_op.send_message != NULL) { + grpc_byte_stream_destroy(&exec_ctx, calld->transport_op.send_message); + calld->transport_op.send_message = NULL; + } + calld->transport_op.send_trailing_metadata = NULL; + grpc_transport_stream_op_add_close(&close_op, status, &message); + grpc_call_next_op(&exec_ctx, elem, &close_op); + calld->on_done_recv->cb(&exec_ctx, calld->on_done_recv->cb_arg, 0); + } + + grpc_exec_ctx_finish(&exec_ctx); +} + +static void auth_on_recv(grpc_exec_ctx *exec_ctx, void *user_data, + bool success) { + grpc_call_element *elem = user_data; + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + if (success) { + if (chand->creds->processor.process != NULL) { + calld->md = metadata_batch_to_md_array(calld->recv_initial_metadata); + chand->creds->processor.process( + chand->creds->processor.state, calld->auth_context, + calld->md.metadata, calld->md.count, on_md_processing_done, elem); + return; + } + } + calld->on_done_recv->cb(exec_ctx, calld->on_done_recv->cb_arg, success); +} + +static void set_recv_ops_md_callbacks(grpc_call_element *elem, + grpc_transport_stream_op *op) { + call_data *calld = elem->call_data; + + if (op->recv_initial_metadata != NULL) { + /* substitute our callback for the higher callback */ + calld->recv_initial_metadata = op->recv_initial_metadata; + calld->on_done_recv = op->recv_initial_metadata_ready; + op->recv_initial_metadata_ready = &calld->auth_on_recv; + calld->transport_op = *op; + } +} + +/* Called either: + - in response to an API call (or similar) from above, to send something + - a network event (or similar) from below, to receive something + op contains type and call direction information, in addition to the data + that is being sent or received. */ +static void auth_start_transport_op(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem, + grpc_transport_stream_op *op) { + set_recv_ops_md_callbacks(elem, op); + grpc_call_next_op(exec_ctx, elem, op); +} + +/* Constructor for call_data */ +static void init_call_elem(grpc_exec_ctx *exec_ctx, grpc_call_element *elem, + grpc_call_element_args *args) { + /* grab pointers to our data from the call element */ + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + grpc_server_security_context *server_ctx = NULL; + + /* initialize members */ + memset(calld, 0, sizeof(*calld)); + grpc_closure_init(&calld->auth_on_recv, auth_on_recv, elem); + + if (args->context[GRPC_CONTEXT_SECURITY].value != NULL) { + args->context[GRPC_CONTEXT_SECURITY].destroy( + args->context[GRPC_CONTEXT_SECURITY].value); + } + + server_ctx = grpc_server_security_context_create(); + server_ctx->auth_context = grpc_auth_context_create(chand->auth_context); + calld->auth_context = server_ctx->auth_context; + + args->context[GRPC_CONTEXT_SECURITY].value = server_ctx; + args->context[GRPC_CONTEXT_SECURITY].destroy = + grpc_server_security_context_destroy; +} + +static void set_pollset(grpc_exec_ctx *exec_ctx, grpc_call_element *elem, + grpc_pollset *pollset) {} + +/* Destructor for call_data */ +static void destroy_call_elem(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem) {} + +/* Constructor for channel_data */ +static void init_channel_elem(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem, + grpc_channel_element_args *args) { + grpc_auth_context *auth_context = + grpc_find_auth_context_in_args(args->channel_args); + grpc_server_credentials *creds = + grpc_find_server_credentials_in_args(args->channel_args); + /* grab pointers to our data from the channel element */ + channel_data *chand = elem->channel_data; + + GPR_ASSERT(!args->is_last); + GPR_ASSERT(auth_context != NULL); + GPR_ASSERT(creds != NULL); + + /* initialize members */ + chand->auth_context = + GRPC_AUTH_CONTEXT_REF(auth_context, "server_auth_filter"); + chand->creds = grpc_server_credentials_ref(creds); +} + +/* Destructor for channel data */ +static void destroy_channel_elem(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem) { + /* grab pointers to our data from the channel element */ + channel_data *chand = elem->channel_data; + GRPC_AUTH_CONTEXT_UNREF(chand->auth_context, "server_auth_filter"); + grpc_server_credentials_unref(chand->creds); +} + +const grpc_channel_filter grpc_server_auth_filter = { + auth_start_transport_op, grpc_channel_next_op, sizeof(call_data), + init_call_elem, set_pollset, destroy_call_elem, + sizeof(channel_data), init_channel_elem, destroy_channel_elem, + grpc_call_next_get_peer, "server-auth"}; diff --git a/src/core/lib/security/server_secure_chttp2.c b/src/core/lib/security/server_secure_chttp2.c new file mode 100644 index 0000000000..7c9dd221ed --- /dev/null +++ b/src/core/lib/security/server_secure_chttp2.c @@ -0,0 +1,264 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/grpc.h> + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/sync.h> +#include <grpc/support/useful.h> +#include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/channel/http_server_filter.h" +#include "src/core/lib/iomgr/endpoint.h" +#include "src/core/lib/iomgr/resolve_address.h" +#include "src/core/lib/iomgr/tcp_server.h" +#include "src/core/lib/security/auth_filters.h" +#include "src/core/lib/security/credentials.h" +#include "src/core/lib/security/security_connector.h" +#include "src/core/lib/security/security_context.h" +#include "src/core/lib/surface/api_trace.h" +#include "src/core/lib/surface/server.h" +#include "src/core/lib/transport/chttp2_transport.h" + +typedef struct grpc_server_secure_state { + grpc_server *server; + grpc_tcp_server *tcp; + grpc_server_security_connector *sc; + grpc_server_credentials *creds; + int is_shutdown; + gpr_mu mu; + gpr_refcount refcount; + grpc_closure destroy_closure; + grpc_closure *destroy_callback; +} grpc_server_secure_state; + +static void state_ref(grpc_server_secure_state *state) { + gpr_ref(&state->refcount); +} + +static void state_unref(grpc_server_secure_state *state) { + if (gpr_unref(&state->refcount)) { + /* ensure all threads have unlocked */ + gpr_mu_lock(&state->mu); + gpr_mu_unlock(&state->mu); + /* clean up */ + GRPC_SECURITY_CONNECTOR_UNREF(&state->sc->base, "server"); + grpc_server_credentials_unref(state->creds); + gpr_free(state); + } +} + +static void setup_transport(grpc_exec_ctx *exec_ctx, void *statep, + grpc_transport *transport, + grpc_auth_context *auth_context) { + grpc_server_secure_state *state = statep; + grpc_channel_args *args_copy; + grpc_arg args_to_add[2]; + args_to_add[0] = grpc_server_credentials_to_arg(state->creds); + args_to_add[1] = grpc_auth_context_to_arg(auth_context); + args_copy = grpc_channel_args_copy_and_add( + grpc_server_get_channel_args(state->server), args_to_add, + GPR_ARRAY_SIZE(args_to_add)); + grpc_server_setup_transport(exec_ctx, state->server, transport, args_copy); + grpc_channel_args_destroy(args_copy); +} + +static void on_secure_handshake_done(grpc_exec_ctx *exec_ctx, void *statep, + grpc_security_status status, + grpc_endpoint *secure_endpoint, + grpc_auth_context *auth_context) { + grpc_server_secure_state *state = statep; + grpc_transport *transport; + if (status == GRPC_SECURITY_OK) { + if (secure_endpoint) { + gpr_mu_lock(&state->mu); + if (!state->is_shutdown) { + transport = grpc_create_chttp2_transport( + exec_ctx, grpc_server_get_channel_args(state->server), + secure_endpoint, 0); + setup_transport(exec_ctx, state, transport, auth_context); + grpc_chttp2_transport_start_reading(exec_ctx, transport, NULL, 0); + } else { + /* We need to consume this here, because the server may already have + * gone away. */ + grpc_endpoint_destroy(exec_ctx, secure_endpoint); + } + gpr_mu_unlock(&state->mu); + } + } else { + gpr_log(GPR_ERROR, "Secure transport failed with error %d", status); + } + state_unref(state); +} + +static void on_accept(grpc_exec_ctx *exec_ctx, void *statep, grpc_endpoint *tcp, + grpc_tcp_server_acceptor *acceptor) { + grpc_server_secure_state *state = statep; + state_ref(state); + grpc_server_security_connector_do_handshake( + exec_ctx, state->sc, acceptor, tcp, on_secure_handshake_done, state); +} + +/* Server callback: start listening on our ports */ +static void start(grpc_exec_ctx *exec_ctx, grpc_server *server, void *statep, + grpc_pollset **pollsets, size_t pollset_count) { + grpc_server_secure_state *state = statep; + grpc_tcp_server_start(exec_ctx, state->tcp, pollsets, pollset_count, + on_accept, state); +} + +static void destroy_done(grpc_exec_ctx *exec_ctx, void *statep, bool success) { + grpc_server_secure_state *state = statep; + if (state->destroy_callback != NULL) { + state->destroy_callback->cb(exec_ctx, state->destroy_callback->cb_arg, + success); + } + grpc_server_security_connector_shutdown(exec_ctx, state->sc); + state_unref(state); +} + +/* Server callback: destroy the tcp listener (so we don't generate further + callbacks) */ +static void destroy(grpc_exec_ctx *exec_ctx, grpc_server *server, void *statep, + grpc_closure *callback) { + grpc_server_secure_state *state = statep; + grpc_tcp_server *tcp; + gpr_mu_lock(&state->mu); + state->is_shutdown = 1; + state->destroy_callback = callback; + tcp = state->tcp; + gpr_mu_unlock(&state->mu); + grpc_tcp_server_unref(exec_ctx, tcp); +} + +int grpc_server_add_secure_http2_port(grpc_server *server, const char *addr, + grpc_server_credentials *creds) { + grpc_resolved_addresses *resolved = NULL; + grpc_tcp_server *tcp = NULL; + grpc_server_secure_state *state = NULL; + size_t i; + unsigned count = 0; + int port_num = -1; + int port_temp; + grpc_security_status status = GRPC_SECURITY_ERROR; + grpc_server_security_connector *sc = NULL; + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + + GRPC_API_TRACE( + "grpc_server_add_secure_http2_port(" + "server=%p, addr=%s, creds=%p)", + 3, (server, addr, creds)); + + /* create security context */ + if (creds == NULL) goto error; + status = grpc_server_credentials_create_security_connector(creds, &sc); + if (status != GRPC_SECURITY_OK) { + gpr_log(GPR_ERROR, + "Unable to create secure server with credentials of type %s.", + creds->type); + goto error; + } + sc->channel_args = grpc_server_get_channel_args(server); + + /* resolve address */ + resolved = grpc_blocking_resolve_address(addr, "https"); + if (!resolved) { + goto error; + } + state = gpr_malloc(sizeof(*state)); + memset(state, 0, sizeof(*state)); + grpc_closure_init(&state->destroy_closure, destroy_done, state); + tcp = grpc_tcp_server_create(&state->destroy_closure); + if (!tcp) { + goto error; + } + + state->server = server; + state->tcp = tcp; + state->sc = sc; + state->creds = grpc_server_credentials_ref(creds); + state->is_shutdown = 0; + gpr_mu_init(&state->mu); + gpr_ref_init(&state->refcount, 1); + + for (i = 0; i < resolved->naddrs; i++) { + port_temp = grpc_tcp_server_add_port( + tcp, (struct sockaddr *)&resolved->addrs[i].addr, + resolved->addrs[i].len); + if (port_temp > 0) { + if (port_num == -1) { + port_num = port_temp; + } else { + GPR_ASSERT(port_num == port_temp); + } + count++; + } + } + if (count == 0) { + gpr_log(GPR_ERROR, "No address added out of total %d resolved", + resolved->naddrs); + goto error; + } + if (count != resolved->naddrs) { + gpr_log(GPR_ERROR, "Only %d addresses added out of total %d resolved", + count, resolved->naddrs); + /* if it's an error, don't we want to goto error; here ? */ + } + grpc_resolved_addresses_destroy(resolved); + + /* Register with the server only upon success */ + grpc_server_add_listener(&exec_ctx, server, state, start, destroy); + + grpc_exec_ctx_finish(&exec_ctx); + return port_num; + +/* Error path: cleanup and return */ +error: + if (resolved) { + grpc_resolved_addresses_destroy(resolved); + } + if (tcp) { + grpc_tcp_server_unref(&exec_ctx, tcp); + } else { + if (sc) { + GRPC_SECURITY_CONNECTOR_UNREF(&sc->base, "server"); + } + if (state) { + gpr_free(state); + } + } + grpc_exec_ctx_finish(&exec_ctx); + return 0; +} diff --git a/src/core/lib/statistics/census_init.c b/src/core/lib/statistics/census_init.c new file mode 100644 index 0000000000..bbecd62764 --- /dev/null +++ b/src/core/lib/statistics/census_init.c @@ -0,0 +1,48 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/statistics/census_interface.h" + +#include <grpc/support/log.h> +#include "src/core/lib/statistics/census_rpc_stats.h" +#include "src/core/lib/statistics/census_tracing.h" + +void census_init(void) { + census_tracing_init(); + census_stats_store_init(); +} + +void census_shutdown(void) { + census_stats_store_shutdown(); + census_tracing_shutdown(); +} diff --git a/src/core/lib/statistics/census_interface.h b/src/core/lib/statistics/census_interface.h new file mode 100644 index 0000000000..b3b3439072 --- /dev/null +++ b/src/core/lib/statistics/census_interface.h @@ -0,0 +1,76 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_STATISTICS_CENSUS_INTERFACE_H +#define GRPC_CORE_LIB_STATISTICS_CENSUS_INTERFACE_H + +#include <grpc/support/port_platform.h> + +/* Maximum length of an individual census trace annotation. */ +#define CENSUS_MAX_ANNOTATION_LENGTH 200 + +/* Structure of a census op id. Define as structure because 64bit integer is not + available on every platform for C89. */ +typedef struct census_op_id { + uint32_t upper; + uint32_t lower; +} census_op_id; + +typedef struct census_rpc_stats census_rpc_stats; + +/* Initializes Census library. No-op if Census is already initialized. */ +void census_init(void); + +/* Shutdown Census Library. */ +void census_shutdown(void); + +/* Annotates grpc method name on a census_op_id. The method name has the format + of <full quantified rpc service name>/<rpc function name>. Returns 0 iff + op_id and method_name are all valid. op_id is valid after its creation and + before calling census_tracing_end_op(). + + TODO(hongyu): Figure out valid characters set for service name and command + name and document requirements here.*/ +int census_add_method_tag(census_op_id op_id, const char *method_name); + +/* Annotates tracing information to a specific op_id. + Up to CENSUS_MAX_ANNOTATION_LENGTH bytes are recorded. */ +void census_tracing_print(census_op_id op_id, const char *annotation); + +/* Starts tracing for an RPC. Returns a locally unique census_op_id */ +census_op_id census_tracing_start_op(void); + +/* Ends tracing. Calling this function will invalidate the input op_id. */ +void census_tracing_end_op(census_op_id op_id); + +#endif /* GRPC_CORE_LIB_STATISTICS_CENSUS_INTERFACE_H */ diff --git a/src/core/lib/statistics/census_log.c b/src/core/lib/statistics/census_log.c new file mode 100644 index 0000000000..1fb942a78a --- /dev/null +++ b/src/core/lib/statistics/census_log.c @@ -0,0 +1,603 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* Available log space is divided up in blocks of + CENSUS_LOG_2_MAX_RECORD_SIZE bytes. A block can be in one of the + following three data structures: + - Free blocks (free_block_list) + - Blocks with unread data (dirty_block_list) + - Blocks currently attached to cores (core_local_blocks[]) + + census_log_start_write() moves a block from core_local_blocks[] to the + end of dirty_block_list when block: + - is out-of-space OR + - has an incomplete record (an incomplete record occurs when a thread calls + census_log_start_write() and is context-switched before calling + census_log_end_write() + So, blocks in dirty_block_list are ordered, from oldest to newest, by time + when block is detached from the core. + + census_log_read_next() first iterates over dirty_block_list and then + core_local_blocks[]. It moves completely read blocks from dirty_block_list + to free_block_list. Blocks in core_local_blocks[] are not freed, even when + completely read. + + If log is configured to discard old records and free_block_list is empty, + census_log_start_write() iterates over dirty_block_list to allocate a + new block. It moves the oldest available block (no pending read/write) to + core_local_blocks[]. + + core_local_block_struct is used to implement a map from core id to the block + associated with that core. This mapping is advisory. It is possible that the + block returned by this mapping is no longer associated with that core. This + mapping is updated, lazily, by census_log_start_write(). + + Locking in block struct: + + Exclusive g_log.lock must be held before calling any functions operatong on + block structs except census_log_start_write() and + census_log_end_write(). + + Writes to a block are serialized via writer_lock. + census_log_start_write() acquires this lock and + census_log_end_write() releases it. On failure to acquire the lock, + writer allocates a new block for the current core and updates + core_local_block accordingly. + + Simultaneous read and write access is allowed. Reader can safely read up to + committed bytes (bytes_committed). + + reader_lock protects the block, currently being read, from getting recycled. + start_read() acquires reader_lock and end_read() releases the lock. + + Read/write access to a block is disabled via try_disable_access(). It returns + with both writer_lock and reader_lock held. These locks are subsequently + released by enable_access() to enable access to the block. + + A note on naming: Most function/struct names are prepended by cl_ + (shorthand for census_log). Further, functions that manipulate structures + include the name of the structure, which will be passed as the first + argument. E.g. cl_block_initialize() will initialize a cl_block. +*/ +#include "src/core/lib/statistics/census_log.h" +#include <grpc/support/alloc.h> +#include <grpc/support/atm.h> +#include <grpc/support/cpu.h> +#include <grpc/support/log.h> +#include <grpc/support/port_platform.h> +#include <grpc/support/sync.h> +#include <grpc/support/useful.h> +#include <string.h> + +/* End of platform specific code */ + +typedef struct census_log_block_list_struct { + struct census_log_block_list_struct *next; + struct census_log_block_list_struct *prev; + struct census_log_block *block; +} cl_block_list_struct; + +typedef struct census_log_block { + /* Pointer to underlying buffer */ + char *buffer; + gpr_atm writer_lock; + gpr_atm reader_lock; + /* Keeps completely written bytes. Declared atomic because accessed + simultaneously by reader and writer. */ + gpr_atm bytes_committed; + /* Bytes already read */ + int32_t bytes_read; + /* Links for list */ + cl_block_list_struct link; +/* We want this structure to be cacheline aligned. We assume the following + sizes for the various parts on 32/64bit systems: + type 32b size 64b size + char* 4 8 + 3x gpr_atm 12 24 + int32_t 4 8 (assumes padding) + cl_block_list_struct 12 24 + TOTAL 32 64 + + Depending on the size of our cacheline and the architecture, we + selectively add char buffering to this structure. The size is checked + via assert in census_log_initialize(). */ +#if defined(GPR_ARCH_64) +#define CL_BLOCK_PAD_SIZE (GPR_CACHELINE_SIZE - 64) +#else +#if defined(GPR_ARCH_32) +#define CL_BLOCK_PAD_SIZE (GPR_CACHELINE_SIZE - 32) +#else +#error "Unknown architecture" +#endif +#endif +#if CL_BLOCK_PAD_SIZE > 0 + char padding[CL_BLOCK_PAD_SIZE]; +#endif +} cl_block; + +/* A list of cl_blocks, doubly-linked through cl_block::link. */ +typedef struct census_log_block_list { + int32_t count; /* Number of items in list. */ + cl_block_list_struct ht; /* head/tail of linked list. */ +} cl_block_list; + +/* Cacheline aligned block pointers to avoid false sharing. Block pointer must + be initialized via set_block(), before calling other functions */ +typedef struct census_log_core_local_block { + gpr_atm block; +/* Ensure cachline alignment: we assume sizeof(gpr_atm) == 4 or 8 */ +#if defined(GPR_ARCH_64) +#define CL_CORE_LOCAL_BLOCK_PAD_SIZE (GPR_CACHELINE_SIZE - 8) +#else +#if defined(GPR_ARCH_32) +#define CL_CORE_LOCAL_BLOCK_PAD_SIZE (GPR_CACHELINE_SIZE - 4) +#else +#error "Unknown architecture" +#endif +#endif +#if CL_CORE_LOCAL_BLOCK_PAD_SIZE > 0 + char padding[CL_CORE_LOCAL_BLOCK_PAD_SIZE]; +#endif +} cl_core_local_block; + +struct census_log { + int discard_old_records; + /* Number of cores (aka hardware-contexts) */ + unsigned num_cores; + /* number of CENSUS_LOG_2_MAX_RECORD_SIZE blocks in log */ + int32_t num_blocks; + cl_block *blocks; /* Block metadata. */ + cl_core_local_block *core_local_blocks; /* Keeps core to block mappings. */ + gpr_mu lock; + int initialized; /* has log been initialized? */ + /* Keeps the state of the reader iterator. A value of 0 indicates that + iterator has reached the end. census_log_init_reader() resets the + value to num_core to restart iteration. */ + uint32_t read_iterator_state; + /* Points to the block being read. If non-NULL, the block is locked for + reading (block_being_read_->reader_lock is held). */ + cl_block *block_being_read; + /* A non-zero value indicates that log is full. */ + gpr_atm is_full; + char *buffer; + cl_block_list free_block_list; + cl_block_list dirty_block_list; + gpr_atm out_of_space_count; +}; + +/* Single internal log */ +static struct census_log g_log; + +/* Functions that operate on an atomic memory location used as a lock */ + +/* Returns non-zero if lock is acquired */ +static int cl_try_lock(gpr_atm *lock) { return gpr_atm_acq_cas(lock, 0, 1); } + +static void cl_unlock(gpr_atm *lock) { gpr_atm_rel_store(lock, 0); } + +/* Functions that operate on cl_core_local_block's */ + +static void cl_core_local_block_set_block(cl_core_local_block *clb, + cl_block *block) { + gpr_atm_rel_store(&clb->block, (gpr_atm)block); +} + +static cl_block *cl_core_local_block_get_block(cl_core_local_block *clb) { + return (cl_block *)gpr_atm_acq_load(&clb->block); +} + +/* Functions that operate on cl_block_list_struct's */ + +static void cl_block_list_struct_initialize(cl_block_list_struct *bls, + cl_block *block) { + bls->next = bls->prev = bls; + bls->block = block; +} + +/* Functions that operate on cl_block_list's */ + +static void cl_block_list_initialize(cl_block_list *list) { + list->count = 0; + cl_block_list_struct_initialize(&list->ht, NULL); +} + +/* Returns head of *this, or NULL if empty. */ +static cl_block *cl_block_list_head(cl_block_list *list) { + return list->ht.next->block; +} + +/* Insert element *e after *pos. */ +static void cl_block_list_insert(cl_block_list *list, cl_block_list_struct *pos, + cl_block_list_struct *e) { + list->count++; + e->next = pos->next; + e->prev = pos; + e->next->prev = e; + e->prev->next = e; +} + +/* Insert block at the head of the list */ +static void cl_block_list_insert_at_head(cl_block_list *list, cl_block *block) { + cl_block_list_insert(list, &list->ht, &block->link); +} + +/* Insert block at the tail of the list */ +static void cl_block_list_insert_at_tail(cl_block_list *list, cl_block *block) { + cl_block_list_insert(list, list->ht.prev, &block->link); +} + +/* Removes block *b. Requires *b be in the list. */ +static void cl_block_list_remove(cl_block_list *list, cl_block *b) { + list->count--; + b->link.next->prev = b->link.prev; + b->link.prev->next = b->link.next; +} + +/* Functions that operate on cl_block's */ + +static void cl_block_initialize(cl_block *block, char *buffer) { + block->buffer = buffer; + gpr_atm_rel_store(&block->writer_lock, 0); + gpr_atm_rel_store(&block->reader_lock, 0); + gpr_atm_rel_store(&block->bytes_committed, 0); + block->bytes_read = 0; + cl_block_list_struct_initialize(&block->link, block); +} + +/* Guards against exposing partially written buffer to the reader. */ +static void cl_block_set_bytes_committed(cl_block *block, + int32_t bytes_committed) { + gpr_atm_rel_store(&block->bytes_committed, bytes_committed); +} + +static int32_t cl_block_get_bytes_committed(cl_block *block) { + return gpr_atm_acq_load(&block->bytes_committed); +} + +/* Tries to disable future read/write access to this block. Succeeds if: + - no in-progress write AND + - no in-progress read AND + - 'discard_data' set to true OR no unread data + On success, clears the block state and returns with writer_lock_ and + reader_lock_ held. These locks are released by a subsequent + cl_block_access_enable() call. */ +static int cl_block_try_disable_access(cl_block *block, int discard_data) { + if (!cl_try_lock(&block->writer_lock)) { + return 0; + } + if (!cl_try_lock(&block->reader_lock)) { + cl_unlock(&block->writer_lock); + return 0; + } + if (!discard_data && + (block->bytes_read != cl_block_get_bytes_committed(block))) { + cl_unlock(&block->reader_lock); + cl_unlock(&block->writer_lock); + return 0; + } + cl_block_set_bytes_committed(block, 0); + block->bytes_read = 0; + return 1; +} + +static void cl_block_enable_access(cl_block *block) { + cl_unlock(&block->reader_lock); + cl_unlock(&block->writer_lock); +} + +/* Returns with writer_lock held. */ +static void *cl_block_start_write(cl_block *block, size_t size) { + int32_t bytes_committed; + if (!cl_try_lock(&block->writer_lock)) { + return NULL; + } + bytes_committed = cl_block_get_bytes_committed(block); + if (bytes_committed + size > CENSUS_LOG_MAX_RECORD_SIZE) { + cl_unlock(&block->writer_lock); + return NULL; + } + return block->buffer + bytes_committed; +} + +/* Releases writer_lock and increments committed bytes by 'bytes_written'. + 'bytes_written' must be <= 'size' specified in the corresponding + StartWrite() call. This function is thread-safe. */ +static void cl_block_end_write(cl_block *block, size_t bytes_written) { + cl_block_set_bytes_committed( + block, cl_block_get_bytes_committed(block) + bytes_written); + cl_unlock(&block->writer_lock); +} + +/* Returns a pointer to the first unread byte in buffer. The number of bytes + available are returned in 'bytes_available'. Acquires reader lock that is + released by a subsequent cl_block_end_read() call. Returns NULL if: + - read in progress + - no data available */ +static void *cl_block_start_read(cl_block *block, size_t *bytes_available) { + void *record; + if (!cl_try_lock(&block->reader_lock)) { + return NULL; + } + /* bytes_committed may change from under us. Use bytes_available to update + bytes_read below. */ + *bytes_available = cl_block_get_bytes_committed(block) - block->bytes_read; + if (*bytes_available == 0) { + cl_unlock(&block->reader_lock); + return NULL; + } + record = block->buffer + block->bytes_read; + block->bytes_read += *bytes_available; + return record; +} + +static void cl_block_end_read(cl_block *block) { + cl_unlock(&block->reader_lock); +} + +/* Internal functions operating on g_log */ + +/* Allocates a new free block (or recycles an available dirty block if log is + configured to discard old records). Returns NULL if out-of-space. */ +static cl_block *cl_allocate_block(void) { + cl_block *block = cl_block_list_head(&g_log.free_block_list); + if (block != NULL) { + cl_block_list_remove(&g_log.free_block_list, block); + return block; + } + if (!g_log.discard_old_records) { + /* No free block and log is configured to keep old records. */ + return NULL; + } + /* Recycle dirty block. Start from the oldest. */ + for (block = cl_block_list_head(&g_log.dirty_block_list); block != NULL; + block = block->link.next->block) { + if (cl_block_try_disable_access(block, 1 /* discard data */)) { + cl_block_list_remove(&g_log.dirty_block_list, block); + return block; + } + } + return NULL; +} + +/* Allocates a new block and updates core id => block mapping. 'old_block' + points to the block that the caller thinks is attached to + 'core_id'. 'old_block' may be NULL. Returns non-zero if: + - allocated a new block OR + - 'core_id' => 'old_block' mapping changed (another thread allocated a + block before lock was acquired). */ +static int cl_allocate_core_local_block(int32_t core_id, cl_block *old_block) { + /* Now that we have the lock, check if core-local mapping has changed. */ + cl_core_local_block *core_local_block = &g_log.core_local_blocks[core_id]; + cl_block *block = cl_core_local_block_get_block(core_local_block); + if ((block != NULL) && (block != old_block)) { + return 1; + } + if (block != NULL) { + cl_core_local_block_set_block(core_local_block, NULL); + cl_block_list_insert_at_tail(&g_log.dirty_block_list, block); + } + block = cl_allocate_block(); + if (block == NULL) { + gpr_atm_rel_store(&g_log.is_full, 1); + return 0; + } + cl_core_local_block_set_block(core_local_block, block); + cl_block_enable_access(block); + return 1; +} + +static cl_block *cl_get_block(void *record) { + uintptr_t p = (uintptr_t)((char *)record - g_log.buffer); + uintptr_t index = p >> CENSUS_LOG_2_MAX_RECORD_SIZE; + return &g_log.blocks[index]; +} + +/* Gets the next block to read and tries to free 'prev' block (if not NULL). + Returns NULL if reached the end. */ +static cl_block *cl_next_block_to_read(cl_block *prev) { + cl_block *block = NULL; + if (g_log.read_iterator_state == g_log.num_cores) { + /* We are traversing dirty list; find the next dirty block. */ + if (prev != NULL) { + /* Try to free the previous block if there is no unread data. This block + may have unread data if previously incomplete record completed between + read_next() calls. */ + block = prev->link.next->block; + if (cl_block_try_disable_access(prev, 0 /* do not discard data */)) { + cl_block_list_remove(&g_log.dirty_block_list, prev); + cl_block_list_insert_at_head(&g_log.free_block_list, prev); + gpr_atm_rel_store(&g_log.is_full, 0); + } + } else { + block = cl_block_list_head(&g_log.dirty_block_list); + } + if (block != NULL) { + return block; + } + /* We are done with the dirty list; moving on to core-local blocks. */ + } + while (g_log.read_iterator_state > 0) { + g_log.read_iterator_state--; + block = cl_core_local_block_get_block( + &g_log.core_local_blocks[g_log.read_iterator_state]); + if (block != NULL) { + return block; + } + } + return NULL; +} + +/* External functions: primary stats_log interface */ +void census_log_initialize(size_t size_in_mb, int discard_old_records) { + int32_t ix; + /* Check cacheline alignment. */ + GPR_ASSERT(sizeof(cl_block) % GPR_CACHELINE_SIZE == 0); + GPR_ASSERT(sizeof(cl_core_local_block) % GPR_CACHELINE_SIZE == 0); + GPR_ASSERT(!g_log.initialized); + g_log.discard_old_records = discard_old_records; + g_log.num_cores = gpr_cpu_num_cores(); + /* Ensure at least as many blocks as there are cores. */ + g_log.num_blocks = GPR_MAX( + g_log.num_cores, (size_in_mb << 20) >> CENSUS_LOG_2_MAX_RECORD_SIZE); + gpr_mu_init(&g_log.lock); + g_log.read_iterator_state = 0; + g_log.block_being_read = NULL; + gpr_atm_rel_store(&g_log.is_full, 0); + g_log.core_local_blocks = (cl_core_local_block *)gpr_malloc_aligned( + g_log.num_cores * sizeof(cl_core_local_block), GPR_CACHELINE_SIZE_LOG); + memset(g_log.core_local_blocks, 0, + g_log.num_cores * sizeof(cl_core_local_block)); + g_log.blocks = (cl_block *)gpr_malloc_aligned( + g_log.num_blocks * sizeof(cl_block), GPR_CACHELINE_SIZE_LOG); + memset(g_log.blocks, 0, g_log.num_blocks * sizeof(cl_block)); + g_log.buffer = gpr_malloc(g_log.num_blocks * CENSUS_LOG_MAX_RECORD_SIZE); + memset(g_log.buffer, 0, g_log.num_blocks * CENSUS_LOG_MAX_RECORD_SIZE); + cl_block_list_initialize(&g_log.free_block_list); + cl_block_list_initialize(&g_log.dirty_block_list); + for (ix = 0; ix < g_log.num_blocks; ++ix) { + cl_block *block = g_log.blocks + ix; + cl_block_initialize(block, + g_log.buffer + (CENSUS_LOG_MAX_RECORD_SIZE * ix)); + cl_block_try_disable_access(block, 1 /* discard data */); + cl_block_list_insert_at_tail(&g_log.free_block_list, block); + } + gpr_atm_rel_store(&g_log.out_of_space_count, 0); + g_log.initialized = 1; +} + +void census_log_shutdown(void) { + GPR_ASSERT(g_log.initialized); + gpr_mu_destroy(&g_log.lock); + gpr_free_aligned(g_log.core_local_blocks); + g_log.core_local_blocks = NULL; + gpr_free_aligned(g_log.blocks); + g_log.blocks = NULL; + gpr_free(g_log.buffer); + g_log.buffer = NULL; + g_log.initialized = 0; +} + +void *census_log_start_write(size_t size) { + /* Used to bound number of times block allocation is attempted. */ + int32_t attempts_remaining = g_log.num_blocks; + /* TODO(aveitch): move this inside the do loop when current_cpu is fixed */ + int32_t core_id = gpr_cpu_current_cpu(); + GPR_ASSERT(g_log.initialized); + if (size > CENSUS_LOG_MAX_RECORD_SIZE) { + return NULL; + } + do { + int allocated; + void *record = NULL; + cl_block *block = + cl_core_local_block_get_block(&g_log.core_local_blocks[core_id]); + if (block && (record = cl_block_start_write(block, size))) { + return record; + } + /* Need to allocate a new block. We are here if: + - No block associated with the core OR + - Write in-progress on the block OR + - block is out of space */ + if (gpr_atm_acq_load(&g_log.is_full)) { + gpr_atm_no_barrier_fetch_add(&g_log.out_of_space_count, 1); + return NULL; + } + gpr_mu_lock(&g_log.lock); + allocated = cl_allocate_core_local_block(core_id, block); + gpr_mu_unlock(&g_log.lock); + if (!allocated) { + gpr_atm_no_barrier_fetch_add(&g_log.out_of_space_count, 1); + return NULL; + } + } while (attempts_remaining--); + /* Give up. */ + gpr_atm_no_barrier_fetch_add(&g_log.out_of_space_count, 1); + return NULL; +} + +void census_log_end_write(void *record, size_t bytes_written) { + GPR_ASSERT(g_log.initialized); + cl_block_end_write(cl_get_block(record), bytes_written); +} + +void census_log_init_reader(void) { + GPR_ASSERT(g_log.initialized); + gpr_mu_lock(&g_log.lock); + /* If a block is locked for reading unlock it. */ + if (g_log.block_being_read != NULL) { + cl_block_end_read(g_log.block_being_read); + g_log.block_being_read = NULL; + } + g_log.read_iterator_state = g_log.num_cores; + gpr_mu_unlock(&g_log.lock); +} + +const void *census_log_read_next(size_t *bytes_available) { + GPR_ASSERT(g_log.initialized); + gpr_mu_lock(&g_log.lock); + if (g_log.block_being_read != NULL) { + cl_block_end_read(g_log.block_being_read); + } + do { + g_log.block_being_read = cl_next_block_to_read(g_log.block_being_read); + if (g_log.block_being_read != NULL) { + void *record = + cl_block_start_read(g_log.block_being_read, bytes_available); + if (record != NULL) { + gpr_mu_unlock(&g_log.lock); + return record; + } + } + } while (g_log.block_being_read != NULL); + gpr_mu_unlock(&g_log.lock); + return NULL; +} + +size_t census_log_remaining_space(void) { + size_t space; + GPR_ASSERT(g_log.initialized); + gpr_mu_lock(&g_log.lock); + if (g_log.discard_old_records) { + /* Remaining space is not meaningful; just return the entire log space. */ + space = g_log.num_blocks << CENSUS_LOG_2_MAX_RECORD_SIZE; + } else { + space = g_log.free_block_list.count * CENSUS_LOG_MAX_RECORD_SIZE; + } + gpr_mu_unlock(&g_log.lock); + return space; +} + +int census_log_out_of_space_count(void) { + GPR_ASSERT(g_log.initialized); + return gpr_atm_acq_load(&g_log.out_of_space_count); +} diff --git a/src/core/lib/statistics/census_log.h b/src/core/lib/statistics/census_log.h new file mode 100644 index 0000000000..c3fbd555ba --- /dev/null +++ b/src/core/lib/statistics/census_log.h @@ -0,0 +1,91 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_STATISTICS_CENSUS_LOG_H +#define GRPC_CORE_LIB_STATISTICS_CENSUS_LOG_H + +#include <stddef.h> + +/* Maximum record size, in bytes. */ +#define CENSUS_LOG_2_MAX_RECORD_SIZE 14 /* 2^14 = 16KB */ +#define CENSUS_LOG_MAX_RECORD_SIZE (1 << CENSUS_LOG_2_MAX_RECORD_SIZE) + +/* Initialize the statistics logging subsystem with the given log size. A log + size of 0 will result in the smallest possible log for the platform + (approximately CENSUS_LOG_MAX_RECORD_SIZE * gpr_cpu_num_cores()). If + discard_old_records is non-zero, then new records will displace older ones + when the log is full. This function must be called before any other + census_log functions. +*/ +void census_log_initialize(size_t size_in_mb, int discard_old_records); + +/* Shutdown the logging subsystem. Caller must ensure that: + - no in progress or future call to any census_log functions + - no incomplete records +*/ +void census_log_shutdown(void); + +/* Allocates and returns a 'size' bytes record and marks it in use. A + subsequent census_log_end_write() marks the record complete. The + 'bytes_written' census_log_end_write() argument must be <= + 'size'. Returns NULL if out-of-space AND: + - log is configured to keep old records OR + - all blocks are pinned by incomplete records. +*/ +void *census_log_start_write(size_t size); + +void census_log_end_write(void *record, size_t bytes_written); + +/* census_log_read_next() iterates over blocks with data and for each block + returns a pointer to the first unread byte. The number of bytes that can be + read are returned in 'bytes_available'. Reader is expected to read all + available data. Reading the data consumes it i.e. it cannot be read again. + census_log_read_next() returns NULL if the end is reached i.e last block + is read. census_log_init_reader() starts the iteration or aborts the + current iteration. +*/ +void census_log_init_reader(void); +const void *census_log_read_next(size_t *bytes_available); + +/* Returns estimated remaining space across all blocks, in bytes. If log is + configured to discard old records, returns total log space. Otherwise, + returns space available in empty blocks (partially filled blocks are + treated as full). +*/ +size_t census_log_remaining_space(void); + +/* Returns the number of times gprc_stats_log_start_write() failed due to + out-of-space. */ +int census_log_out_of_space_count(void); + +#endif /* GRPC_CORE_LIB_STATISTICS_CENSUS_LOG_H */ diff --git a/src/core/lib/statistics/census_rpc_stats.c b/src/core/lib/statistics/census_rpc_stats.c new file mode 100644 index 0000000000..2182561668 --- /dev/null +++ b/src/core/lib/statistics/census_rpc_stats.c @@ -0,0 +1,253 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/sync.h> +#include "src/core/lib/statistics/census_interface.h" +#include "src/core/lib/statistics/census_rpc_stats.h" +#include "src/core/lib/statistics/census_tracing.h" +#include "src/core/lib/statistics/hash_table.h" +#include "src/core/lib/statistics/window_stats.h" +#include "src/core/lib/support/murmur_hash.h" +#include "src/core/lib/support/string.h" + +#define NUM_INTERVALS 3 +#define MINUTE_INTERVAL 0 +#define HOUR_INTERVAL 1 +#define TOTAL_INTERVAL 2 + +/* for easier typing */ +typedef census_per_method_rpc_stats per_method_stats; + +/* Ensure mu is only initialized once. */ +static gpr_once g_stats_store_mu_init = GPR_ONCE_INIT; +/* Guards two stats stores. */ +static gpr_mu g_mu; +static census_ht *g_client_stats_store = NULL; +static census_ht *g_server_stats_store = NULL; + +static void init_mutex(void) { gpr_mu_init(&g_mu); } + +static void init_mutex_once(void) { + gpr_once_init(&g_stats_store_mu_init, init_mutex); +} + +static int cmp_str_keys(const void *k1, const void *k2) { + return strcmp((const char *)k1, (const char *)k2); +} + +/* TODO(hongyu): replace it with cityhash64 */ +static uint64_t simple_hash(const void *k) { + size_t len = strlen(k); + uint64_t higher = gpr_murmur_hash3((const char *)k, len / 2, 0); + return higher << 32 | + gpr_murmur_hash3((const char *)k + len / 2, len - len / 2, 0); +} + +static void delete_stats(void *stats) { + census_window_stats_destroy((struct census_window_stats *)stats); +} + +static void delete_key(void *key) { gpr_free(key); } + +static const census_ht_option ht_opt = { + CENSUS_HT_POINTER /* key type */, 1999 /* n_of_buckets */, + simple_hash /* hash function */, cmp_str_keys /* key comparator */, + delete_stats /* data deleter */, delete_key /* key deleter */ +}; + +static void init_rpc_stats(void *stats) { + memset(stats, 0, sizeof(census_rpc_stats)); +} + +static void stat_add_proportion(double p, void *base, const void *addme) { + census_rpc_stats *b = (census_rpc_stats *)base; + census_rpc_stats *a = (census_rpc_stats *)addme; + b->cnt += p * a->cnt; + b->rpc_error_cnt += p * a->rpc_error_cnt; + b->app_error_cnt += p * a->app_error_cnt; + b->elapsed_time_ms += p * a->elapsed_time_ms; + b->api_request_bytes += p * a->api_request_bytes; + b->wire_request_bytes += p * a->wire_request_bytes; + b->api_response_bytes += p * a->api_response_bytes; + b->wire_response_bytes += p * a->wire_response_bytes; +} + +static void stat_add(void *base, const void *addme) { + stat_add_proportion(1.0, base, addme); +} + +static gpr_timespec min_hour_total_intervals[3] = { + {60, 0}, {3600, 0}, {36000000, 0}}; + +static const census_window_stats_stat_info window_stats_settings = { + sizeof(census_rpc_stats), init_rpc_stats, stat_add, stat_add_proportion}; + +census_rpc_stats *census_rpc_stats_create_empty(void) { + census_rpc_stats *ret = + (census_rpc_stats *)gpr_malloc(sizeof(census_rpc_stats)); + memset(ret, 0, sizeof(census_rpc_stats)); + return ret; +} + +void census_aggregated_rpc_stats_set_empty(census_aggregated_rpc_stats *data) { + int i = 0; + for (i = 0; i < data->num_entries; i++) { + if (data->stats[i].method != NULL) { + gpr_free((void *)data->stats[i].method); + } + } + if (data->stats != NULL) { + gpr_free(data->stats); + } + data->num_entries = 0; + data->stats = NULL; +} + +static void record_stats(census_ht *store, census_op_id op_id, + const census_rpc_stats *stats) { + gpr_mu_lock(&g_mu); + if (store != NULL) { + census_trace_obj *trace = NULL; + census_internal_lock_trace_store(); + trace = census_get_trace_obj_locked(op_id); + if (trace != NULL) { + const char *method_name = census_get_trace_method_name(trace); + struct census_window_stats *window_stats = NULL; + census_ht_key key; + key.ptr = (void *)method_name; + window_stats = census_ht_find(store, key); + census_internal_unlock_trace_store(); + if (window_stats == NULL) { + window_stats = census_window_stats_create(3, min_hour_total_intervals, + 30, &window_stats_settings); + key.ptr = gpr_strdup(key.ptr); + census_ht_insert(store, key, (void *)window_stats); + } + census_window_stats_add(window_stats, gpr_now(GPR_CLOCK_REALTIME), stats); + } else { + census_internal_unlock_trace_store(); + } + } + gpr_mu_unlock(&g_mu); +} + +void census_record_rpc_client_stats(census_op_id op_id, + const census_rpc_stats *stats) { + record_stats(g_client_stats_store, op_id, stats); +} + +void census_record_rpc_server_stats(census_op_id op_id, + const census_rpc_stats *stats) { + record_stats(g_server_stats_store, op_id, stats); +} + +/* Get stats from input stats store */ +static void get_stats(census_ht *store, census_aggregated_rpc_stats *data) { + GPR_ASSERT(data != NULL); + if (data->num_entries != 0) { + census_aggregated_rpc_stats_set_empty(data); + } + gpr_mu_lock(&g_mu); + if (store != NULL) { + size_t n; + unsigned i, j; + gpr_timespec now = gpr_now(GPR_CLOCK_REALTIME); + census_ht_kv *kv = census_ht_get_all_elements(store, &n); + if (kv != NULL) { + data->num_entries = n; + data->stats = + (per_method_stats *)gpr_malloc(sizeof(per_method_stats) * n); + for (i = 0; i < n; i++) { + census_window_stats_sums sums[NUM_INTERVALS]; + for (j = 0; j < NUM_INTERVALS; j++) { + sums[j].statistic = (void *)census_rpc_stats_create_empty(); + } + data->stats[i].method = gpr_strdup(kv[i].k.ptr); + census_window_stats_get_sums(kv[i].v, now, sums); + data->stats[i].minute_stats = + *(census_rpc_stats *)sums[MINUTE_INTERVAL].statistic; + data->stats[i].hour_stats = + *(census_rpc_stats *)sums[HOUR_INTERVAL].statistic; + data->stats[i].total_stats = + *(census_rpc_stats *)sums[TOTAL_INTERVAL].statistic; + for (j = 0; j < NUM_INTERVALS; j++) { + gpr_free(sums[j].statistic); + } + } + gpr_free(kv); + } + } + gpr_mu_unlock(&g_mu); +} + +void census_get_client_stats(census_aggregated_rpc_stats *data) { + get_stats(g_client_stats_store, data); +} + +void census_get_server_stats(census_aggregated_rpc_stats *data) { + get_stats(g_server_stats_store, data); +} + +void census_stats_store_init(void) { + init_mutex_once(); + gpr_mu_lock(&g_mu); + if (g_client_stats_store == NULL && g_server_stats_store == NULL) { + g_client_stats_store = census_ht_create(&ht_opt); + g_server_stats_store = census_ht_create(&ht_opt); + } else { + gpr_log(GPR_ERROR, "Census stats store already initialized."); + } + gpr_mu_unlock(&g_mu); +} + +void census_stats_store_shutdown(void) { + init_mutex_once(); + gpr_mu_lock(&g_mu); + if (g_client_stats_store != NULL) { + census_ht_destroy(g_client_stats_store); + g_client_stats_store = NULL; + } else { + gpr_log(GPR_ERROR, "Census server stats store not initialized."); + } + if (g_server_stats_store != NULL) { + census_ht_destroy(g_server_stats_store); + g_server_stats_store = NULL; + } else { + gpr_log(GPR_ERROR, "Census client stats store not initialized."); + } + gpr_mu_unlock(&g_mu); +} diff --git a/src/core/lib/statistics/census_rpc_stats.h b/src/core/lib/statistics/census_rpc_stats.h new file mode 100644 index 0000000000..00bb48205e --- /dev/null +++ b/src/core/lib/statistics/census_rpc_stats.h @@ -0,0 +1,101 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_STATISTICS_CENSUS_RPC_STATS_H +#define GRPC_CORE_LIB_STATISTICS_CENSUS_RPC_STATS_H + +#include <grpc/support/port_platform.h> +#include "src/core/lib/statistics/census_interface.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct census_rpc_stats { + uint64_t cnt; + uint64_t rpc_error_cnt; + uint64_t app_error_cnt; + double elapsed_time_ms; + double api_request_bytes; + double wire_request_bytes; + double api_response_bytes; + double wire_response_bytes; +}; + +/* Creates an empty rpc stats object on heap. */ +census_rpc_stats *census_rpc_stats_create_empty(void); + +typedef struct census_per_method_rpc_stats { + const char *method; + census_rpc_stats minute_stats; /* cumulative stats in the past minute */ + census_rpc_stats hour_stats; /* cumulative stats in the past hour */ + census_rpc_stats total_stats; /* cumulative stats from last gc */ +} census_per_method_rpc_stats; + +typedef struct census_aggregated_rpc_stats { + int num_entries; + census_per_method_rpc_stats *stats; +} census_aggregated_rpc_stats; + +/* Initializes an aggregated rpc stats object to an empty state. */ +void census_aggregated_rpc_stats_set_empty(census_aggregated_rpc_stats *data); + +/* Records client side stats of a rpc. */ +void census_record_rpc_client_stats(census_op_id op_id, + const census_rpc_stats *stats); + +/* Records server side stats of a rpc. */ +void census_record_rpc_server_stats(census_op_id op_id, + const census_rpc_stats *stats); + +/* The following two functions are intended for inprocess query of + per-service per-method stats from grpc implementations. */ + +/* Populates *data_map with server side aggregated per-service per-method + stats. + DO NOT CALL from outside of grpc code. */ +void census_get_server_stats(census_aggregated_rpc_stats *data_map); + +/* Populates *data_map with client side aggregated per-service per-method + stats. + DO NOT CALL from outside of grpc code. */ +void census_get_client_stats(census_aggregated_rpc_stats *data_map); + +void census_stats_store_init(void); +void census_stats_store_shutdown(void); + +#ifdef __cplusplus +} +#endif + +#endif /* GRPC_CORE_LIB_STATISTICS_CENSUS_RPC_STATS_H */ diff --git a/src/core/lib/statistics/census_tracing.c b/src/core/lib/statistics/census_tracing.c new file mode 100644 index 0000000000..b58ae733fc --- /dev/null +++ b/src/core/lib/statistics/census_tracing.c @@ -0,0 +1,241 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/statistics/census_tracing.h" +#include "src/core/lib/statistics/census_interface.h" + +#include <stdio.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/port_platform.h> +#include <grpc/support/sync.h> +#include "src/core/lib/statistics/hash_table.h" +#include "src/core/lib/support/string.h" + +void census_trace_obj_destroy(census_trace_obj *obj) { + census_trace_annotation *p = obj->annotations; + while (p != NULL) { + census_trace_annotation *next = p->next; + gpr_free(p); + p = next; + } + gpr_free(obj->method); + gpr_free(obj); +} + +static void delete_trace_obj(void *obj) { + census_trace_obj_destroy((census_trace_obj *)obj); +} + +static const census_ht_option ht_opt = { + CENSUS_HT_UINT64 /* key type */, + 571 /* n_of_buckets */, + NULL /* hash */, + NULL /* compare_keys */, + delete_trace_obj /* delete data */, + NULL /* delete key */ +}; + +static gpr_once g_init_mutex_once = GPR_ONCE_INIT; +static gpr_mu g_mu; /* Guards following two static variables. */ +static census_ht *g_trace_store = NULL; +static uint64_t g_id = 0; + +static census_ht_key op_id_as_key(census_op_id *id) { + return *(census_ht_key *)id; +} + +static uint64_t op_id_2_uint64(census_op_id *id) { + uint64_t ret; + memcpy(&ret, id, sizeof(census_op_id)); + return ret; +} + +static void init_mutex(void) { gpr_mu_init(&g_mu); } + +static void init_mutex_once(void) { + gpr_once_init(&g_init_mutex_once, init_mutex); +} + +census_op_id census_tracing_start_op(void) { + gpr_mu_lock(&g_mu); + { + census_trace_obj *ret = gpr_malloc(sizeof(census_trace_obj)); + memset(ret, 0, sizeof(census_trace_obj)); + g_id++; + memcpy(&ret->id, &g_id, sizeof(census_op_id)); + ret->rpc_stats.cnt = 1; + ret->ts = gpr_now(GPR_CLOCK_REALTIME); + census_ht_insert(g_trace_store, op_id_as_key(&ret->id), (void *)ret); + gpr_log(GPR_DEBUG, "Start tracing for id %lu", g_id); + gpr_mu_unlock(&g_mu); + return ret->id; + } +} + +int census_add_method_tag(census_op_id op_id, const char *method) { + int ret = 0; + census_trace_obj *trace = NULL; + gpr_mu_lock(&g_mu); + trace = census_ht_find(g_trace_store, op_id_as_key(&op_id)); + if (trace == NULL) { + ret = 1; + } else { + trace->method = gpr_strdup(method); + } + gpr_mu_unlock(&g_mu); + return ret; +} + +void census_tracing_print(census_op_id op_id, const char *anno_txt) { + census_trace_obj *trace = NULL; + gpr_mu_lock(&g_mu); + trace = census_ht_find(g_trace_store, op_id_as_key(&op_id)); + if (trace != NULL) { + census_trace_annotation *anno = gpr_malloc(sizeof(census_trace_annotation)); + anno->ts = gpr_now(GPR_CLOCK_REALTIME); + { + char *d = anno->txt; + const char *s = anno_txt; + int n = 0; + for (; n < CENSUS_MAX_ANNOTATION_LENGTH && *s != '\0'; ++n) { + *d++ = *s++; + } + *d = '\0'; + } + anno->next = trace->annotations; + trace->annotations = anno; + } + gpr_mu_unlock(&g_mu); +} + +void census_tracing_end_op(census_op_id op_id) { + census_trace_obj *trace = NULL; + gpr_mu_lock(&g_mu); + trace = census_ht_find(g_trace_store, op_id_as_key(&op_id)); + if (trace != NULL) { + trace->rpc_stats.elapsed_time_ms = gpr_timespec_to_micros( + gpr_time_sub(gpr_now(GPR_CLOCK_REALTIME), trace->ts)); + gpr_log(GPR_DEBUG, "End tracing for id %lu, method %s, latency %f us", + op_id_2_uint64(&op_id), trace->method, + trace->rpc_stats.elapsed_time_ms); + census_ht_erase(g_trace_store, op_id_as_key(&op_id)); + } + gpr_mu_unlock(&g_mu); +} + +void census_tracing_init(void) { + init_mutex_once(); + gpr_mu_lock(&g_mu); + if (g_trace_store == NULL) { + g_id = 1; + g_trace_store = census_ht_create(&ht_opt); + } else { + gpr_log(GPR_ERROR, "Census trace store already initialized."); + } + gpr_mu_unlock(&g_mu); +} + +void census_tracing_shutdown(void) { + gpr_mu_lock(&g_mu); + if (g_trace_store != NULL) { + census_ht_destroy(g_trace_store); + g_trace_store = NULL; + } else { + gpr_log(GPR_ERROR, "Census trace store is not initialized."); + } + gpr_mu_unlock(&g_mu); +} + +void census_internal_lock_trace_store(void) { gpr_mu_lock(&g_mu); } + +void census_internal_unlock_trace_store(void) { gpr_mu_unlock(&g_mu); } + +census_trace_obj *census_get_trace_obj_locked(census_op_id op_id) { + if (g_trace_store == NULL) { + gpr_log(GPR_ERROR, "Census trace store is not initialized."); + return NULL; + } + return (census_trace_obj *)census_ht_find(g_trace_store, + op_id_as_key(&op_id)); +} + +const char *census_get_trace_method_name(const census_trace_obj *trace) { + return trace->method; +} + +static census_trace_annotation *dup_annotation_chain( + census_trace_annotation *from) { + census_trace_annotation *ret = NULL; + census_trace_annotation **to = &ret; + for (; from != NULL; from = from->next) { + *to = gpr_malloc(sizeof(census_trace_annotation)); + memcpy(*to, from, sizeof(census_trace_annotation)); + to = &(*to)->next; + } + return ret; +} + +static census_trace_obj *trace_obj_dup(census_trace_obj *from) { + census_trace_obj *to = NULL; + GPR_ASSERT(from != NULL); + to = gpr_malloc(sizeof(census_trace_obj)); + to->id = from->id; + to->ts = from->ts; + to->rpc_stats = from->rpc_stats; + to->method = gpr_strdup(from->method); + to->annotations = dup_annotation_chain(from->annotations); + return to; +} + +census_trace_obj **census_get_active_ops(int *num_active_ops) { + census_trace_obj **ret = NULL; + gpr_mu_lock(&g_mu); + if (g_trace_store != NULL) { + size_t n = 0; + census_ht_kv *all_kvs = census_ht_get_all_elements(g_trace_store, &n); + *num_active_ops = (int)n; + if (n != 0) { + size_t i = 0; + ret = gpr_malloc(sizeof(census_trace_obj *) * n); + for (i = 0; i < n; i++) { + ret[i] = trace_obj_dup((census_trace_obj *)all_kvs[i].v); + } + } + gpr_free(all_kvs); + } + gpr_mu_unlock(&g_mu); + return ret; +} diff --git a/src/core/lib/statistics/census_tracing.h b/src/core/lib/statistics/census_tracing.h new file mode 100644 index 0000000000..a101abf3cb --- /dev/null +++ b/src/core/lib/statistics/census_tracing.h @@ -0,0 +1,96 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_STATISTICS_CENSUS_TRACING_H +#define GRPC_CORE_LIB_STATISTICS_CENSUS_TRACING_H + +#include <grpc/support/time.h> +#include "src/core/lib/statistics/census_rpc_stats.h" + +/* WARNING: The data structures and APIs provided by this file are for GRPC + library's internal use ONLY. They might be changed in backward-incompatible + ways and are not subject to any deprecation policy. + They are not recommended for external use. + */ +#ifdef __cplusplus +extern "C" { +#endif + +/* Struct for a trace annotation. */ +typedef struct census_trace_annotation { + gpr_timespec ts; /* timestamp of the annotation */ + char txt[CENSUS_MAX_ANNOTATION_LENGTH + 1]; /* actual txt annotation */ + struct census_trace_annotation *next; +} census_trace_annotation; + +typedef struct census_trace_obj { + census_op_id id; + gpr_timespec ts; + census_rpc_stats rpc_stats; + char *method; + census_trace_annotation *annotations; +} census_trace_obj; + +/* Deletes trace object. */ +void census_trace_obj_destroy(census_trace_obj *obj); + +/* Initializes trace store. This function is thread safe. */ +void census_tracing_init(void); + +/* Shutsdown trace store. This function is thread safe. */ +void census_tracing_shutdown(void); + +/* Gets trace obj corresponding to the input op_id. Returns NULL if trace store + is not initialized or trace obj is not found. Requires trace store being + locked before calling this function. */ +census_trace_obj *census_get_trace_obj_locked(census_op_id op_id); + +/* The following two functions acquire and release the trace store global lock. + They are for census internal use only. */ +void census_internal_lock_trace_store(void); +void census_internal_unlock_trace_store(void); + +/* Gets method name associated with the input trace object. */ +const char *census_get_trace_method_name(const census_trace_obj *trace); + +/* Returns an array of pointers to trace objects of currently active operations + and fills in number of active operations. Returns NULL if there are no active + operations. + Caller owns the returned objects. */ +census_trace_obj **census_get_active_ops(int *num_active_ops); + +#ifdef __cplusplus +} +#endif + +#endif /* GRPC_CORE_LIB_STATISTICS_CENSUS_TRACING_H */ diff --git a/src/core/lib/statistics/hash_table.c b/src/core/lib/statistics/hash_table.c new file mode 100644 index 0000000000..18b7442a0c --- /dev/null +++ b/src/core/lib/statistics/hash_table.c @@ -0,0 +1,303 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/statistics/hash_table.h" + +#include <stddef.h> +#include <stdio.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/port_platform.h> + +#define CENSUS_HT_NUM_BUCKETS 1999 + +/* A single hash table data entry */ +typedef struct ht_entry { + census_ht_key key; + void *data; + struct ht_entry *next; +} ht_entry; + +/* hash table bucket */ +typedef struct bucket { + /* NULL if bucket is empty */ + ht_entry *next; + /* -1 if all buckets are empty. */ + int32_t prev_non_empty_bucket; + /* -1 if all buckets are empty. */ + int32_t next_non_empty_bucket; +} bucket; + +struct unresizable_hash_table { + /* Number of entries in the table */ + size_t size; + /* Number of buckets */ + uint32_t num_buckets; + /* Array of buckets initialized at creation time. Memory consumption is + 16 bytes per bucket on a 64-bit platform. */ + bucket *buckets; + /* Index of the first non-empty bucket. -1 iff size == 0. */ + int32_t first_non_empty_bucket; + /* Index of the last non_empty bucket. -1 iff size == 0. */ + int32_t last_non_empty_bucket; + /* Immutable options of this hash table, initialized at creation time. */ + census_ht_option options; +}; + +typedef struct entry_locator { + int32_t bucket_idx; + int is_first_in_chain; + int found; + ht_entry *prev_entry; +} entry_locator; + +/* Asserts if option is not valid. */ +void check_options(const census_ht_option *option) { + GPR_ASSERT(option != NULL); + GPR_ASSERT(option->num_buckets > 0); + GPR_ASSERT(option->key_type == CENSUS_HT_UINT64 || + option->key_type == CENSUS_HT_POINTER); + if (option->key_type == CENSUS_HT_UINT64) { + GPR_ASSERT(option->hash == NULL); + } else if (option->key_type == CENSUS_HT_POINTER) { + GPR_ASSERT(option->hash != NULL); + GPR_ASSERT(option->compare_keys != NULL); + } +} + +#define REMOVE_NEXT(options, ptr) \ + do { \ + ht_entry *tmp = (ptr)->next; \ + (ptr)->next = tmp->next; \ + delete_entry(options, tmp); \ + } while (0) + +static void delete_entry(const census_ht_option *opt, ht_entry *p) { + if (opt->delete_data != NULL) { + opt->delete_data(p->data); + } + if (opt->delete_key != NULL) { + opt->delete_key(p->key.ptr); + } + gpr_free(p); +} + +static uint64_t hash(const census_ht_option *opt, census_ht_key key) { + return opt->key_type == CENSUS_HT_UINT64 ? key.val : opt->hash(key.ptr); +} + +census_ht *census_ht_create(const census_ht_option *option) { + int i; + census_ht *ret = NULL; + check_options(option); + ret = (census_ht *)gpr_malloc(sizeof(census_ht)); + ret->size = 0; + ret->num_buckets = option->num_buckets; + ret->buckets = (bucket *)gpr_malloc(sizeof(bucket) * ret->num_buckets); + ret->options = *option; + /* initialize each bucket */ + for (i = 0; i < ret->options.num_buckets; i++) { + ret->buckets[i].prev_non_empty_bucket = -1; + ret->buckets[i].next_non_empty_bucket = -1; + ret->buckets[i].next = NULL; + } + return ret; +} + +static int32_t find_bucket_idx(const census_ht *ht, census_ht_key key) { + return hash(&ht->options, key) % ht->num_buckets; +} + +static int keys_match(const census_ht_option *opt, const ht_entry *p, + const census_ht_key key) { + GPR_ASSERT(opt->key_type == CENSUS_HT_UINT64 || + opt->key_type == CENSUS_HT_POINTER); + if (opt->key_type == CENSUS_HT_UINT64) return p->key.val == key.val; + return !opt->compare_keys((p->key).ptr, key.ptr); +} + +static entry_locator ht_find(const census_ht *ht, census_ht_key key) { + entry_locator loc = {0, 0, 0, NULL}; + int32_t idx = 0; + ht_entry *ptr = NULL; + GPR_ASSERT(ht != NULL); + idx = find_bucket_idx(ht, key); + ptr = ht->buckets[idx].next; + if (ptr == NULL) { + /* bucket is empty */ + return loc; + } + if (keys_match(&ht->options, ptr, key)) { + loc.bucket_idx = idx; + loc.is_first_in_chain = 1; + loc.found = 1; + return loc; + } else { + for (; ptr->next != NULL; ptr = ptr->next) { + if (keys_match(&ht->options, ptr->next, key)) { + loc.bucket_idx = idx; + loc.is_first_in_chain = 0; + loc.found = 1; + loc.prev_entry = ptr; + return loc; + } + } + } + /* Could not find the key */ + return loc; +} + +void *census_ht_find(const census_ht *ht, census_ht_key key) { + entry_locator loc = ht_find(ht, key); + if (loc.found == 0) { + return NULL; + } + return loc.is_first_in_chain ? ht->buckets[loc.bucket_idx].next->data + : loc.prev_entry->next->data; +} + +void census_ht_insert(census_ht *ht, census_ht_key key, void *data) { + int32_t idx = find_bucket_idx(ht, key); + ht_entry *ptr = NULL; + entry_locator loc = ht_find(ht, key); + if (loc.found) { + /* Replace old value with new value. */ + ptr = loc.is_first_in_chain ? ht->buckets[loc.bucket_idx].next + : loc.prev_entry->next; + if (ht->options.delete_data != NULL) { + ht->options.delete_data(ptr->data); + } + ptr->data = data; + return; + } + + /* first entry in the table. */ + if (ht->size == 0) { + ht->buckets[idx].next_non_empty_bucket = -1; + ht->buckets[idx].prev_non_empty_bucket = -1; + ht->first_non_empty_bucket = idx; + ht->last_non_empty_bucket = idx; + } else if (ht->buckets[idx].next == NULL) { + /* first entry in the bucket. */ + ht->buckets[ht->last_non_empty_bucket].next_non_empty_bucket = idx; + ht->buckets[idx].prev_non_empty_bucket = ht->last_non_empty_bucket; + ht->buckets[idx].next_non_empty_bucket = -1; + ht->last_non_empty_bucket = idx; + } + ptr = (ht_entry *)gpr_malloc(sizeof(ht_entry)); + ptr->key = key; + ptr->data = data; + ptr->next = ht->buckets[idx].next; + ht->buckets[idx].next = ptr; + ht->size++; +} + +void census_ht_erase(census_ht *ht, census_ht_key key) { + entry_locator loc = ht_find(ht, key); + if (loc.found == 0) { + /* noop if not found */ + return; + } + ht->size--; + if (loc.is_first_in_chain) { + bucket *b = &ht->buckets[loc.bucket_idx]; + GPR_ASSERT(b->next != NULL); + /* The only entry in the bucket */ + if (b->next->next == NULL) { + int prev = b->prev_non_empty_bucket; + int next = b->next_non_empty_bucket; + if (prev != -1) { + ht->buckets[prev].next_non_empty_bucket = next; + } else { + ht->first_non_empty_bucket = next; + } + if (next != -1) { + ht->buckets[next].prev_non_empty_bucket = prev; + } else { + ht->last_non_empty_bucket = prev; + } + } + REMOVE_NEXT(&ht->options, b); + } else { + GPR_ASSERT(loc.prev_entry->next != NULL); + REMOVE_NEXT(&ht->options, loc.prev_entry); + } +} + +/* Returns NULL if input table is empty. */ +census_ht_kv *census_ht_get_all_elements(const census_ht *ht, size_t *num) { + census_ht_kv *ret = NULL; + int i = 0; + int32_t idx = -1; + GPR_ASSERT(ht != NULL && num != NULL); + *num = ht->size; + if (*num == 0) { + return NULL; + } + + ret = (census_ht_kv *)gpr_malloc(sizeof(census_ht_kv) * ht->size); + idx = ht->first_non_empty_bucket; + while (idx >= 0) { + ht_entry *ptr = ht->buckets[idx].next; + for (; ptr != NULL; ptr = ptr->next) { + ret[i].k = ptr->key; + ret[i].v = ptr->data; + i++; + } + idx = ht->buckets[idx].next_non_empty_bucket; + } + return ret; +} + +static void ht_delete_entry_chain(const census_ht_option *options, + ht_entry *first) { + if (first == NULL) { + return; + } + if (first->next != NULL) { + ht_delete_entry_chain(options, first->next); + } + delete_entry(options, first); +} + +void census_ht_destroy(census_ht *ht) { + unsigned i; + for (i = 0; i < ht->num_buckets; ++i) { + ht_delete_entry_chain(&ht->options, ht->buckets[i].next); + } + gpr_free(ht->buckets); + gpr_free(ht); +} + +size_t census_ht_get_size(const census_ht *ht) { return ht->size; } diff --git a/src/core/lib/statistics/hash_table.h b/src/core/lib/statistics/hash_table.h new file mode 100644 index 0000000000..8f74ec82aa --- /dev/null +++ b/src/core/lib/statistics/hash_table.h @@ -0,0 +1,131 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_STATISTICS_HASH_TABLE_H +#define GRPC_CORE_LIB_STATISTICS_HASH_TABLE_H + +#include <stddef.h> + +#include <grpc/support/port_platform.h> + +/* A chain based hash table with fixed number of buckets. + Your probably shouldn't use this code directly. It is implemented for the + use case in census trace store and stats store, where number of entries in + the table is in the scale of upto several thousands, entries are added and + removed from the table very frequently (~100k/s), the frequency of find() + operations is roughly several times of the frequency of insert() and erase() + Comparing to find(), the insert(), erase() and get_all_entries() operations + are much less freqent (<1/s). + + Per bucket memory overhead is about (8 + sizeof(intptr_t) bytes. + Per entry memory overhead is about (8 + 2 * sizeof(intptr_t) bytes. + + All functions are not thread-safe. Synchronization will be provided in the + upper layer (in trace store and stats store). +*/ + +/* Opaque hash table struct */ +typedef struct unresizable_hash_table census_ht; + +/* Currently, the hash_table can take two types of keys. (uint64 for trace + store and const char* for stats store). */ +typedef union { + uint64_t val; + void *ptr; +} census_ht_key; + +typedef enum census_ht_key_type { + CENSUS_HT_UINT64 = 0, + CENSUS_HT_POINTER = 1 +} census_ht_key_type; + +typedef struct census_ht_option { + /* Type of hash key */ + census_ht_key_type key_type; + /* Desired number of buckets, preferably a prime number */ + int32_t num_buckets; + /* Fucntion to calculate uint64 hash value of the key. Only takes effect if + key_type is POINTER. */ + uint64_t (*hash)(const void *); + /* Function to compare two keys, returns 0 iff equal. Only takes effect if + key_type is POINTER */ + int (*compare_keys)(const void *k1, const void *k2); + /* Value deleter. NULL if no specialized delete function is needed. */ + void (*delete_data)(void *); + /* Key deleter. NULL if table does not own the key. (e.g. key is part of the + value or key is not owned by the table.) */ + void (*delete_key)(void *); +} census_ht_option; + +/* Creates a hashtable with fixed number of buckets according to the settings + specified in 'options' arg. Function pointers "hash" and "compare_keys" must + be provided if key_type is POINTER. Asserts if fail to create. */ +census_ht *census_ht_create(const census_ht_option *options); + +/* Deletes hash table instance. Frees all dynamic memory owned by ht.*/ +void census_ht_destroy(census_ht *ht); + +/* Inserts the input key-val pair into hash_table. If an entry with the same key + exists in the table, the corresponding value will be overwritten by the input + val. */ +void census_ht_insert(census_ht *ht, census_ht_key key, void *val); + +/* Returns pointer to data, returns NULL if not found. */ +void *census_ht_find(const census_ht *ht, census_ht_key key); + +/* Erase hash table entry with input key. Noop if key is not found. */ +void census_ht_erase(census_ht *ht, census_ht_key key); + +typedef struct census_ht_kv { + census_ht_key k; + void *v; +} census_ht_kv; + +/* Returns an array of pointers to all values in the hash table. Order of the + elements can be arbitrary. Sets 'num' to the size of returned array. Caller + owns returned array. */ +census_ht_kv *census_ht_get_all_elements(const census_ht *ht, size_t *num); + +/* Returns number of elements kept. */ +size_t census_ht_get_size(const census_ht *ht); + +/* Functor applied on each key-value pair while iterating through entries in the + table. The functor should not mutate data. */ +typedef void (*census_ht_itr_cb)(census_ht_key key, const void *val_ptr, + void *state); + +/* Iterates through all key-value pairs in the hash_table. The callback function + should not invalidate data entries. */ +uint64_t census_ht_for_all(const census_ht *ht, census_ht_itr_cb); + +#endif /* GRPC_CORE_LIB_STATISTICS_HASH_TABLE_H */ diff --git a/src/core/lib/statistics/window_stats.c b/src/core/lib/statistics/window_stats.c new file mode 100644 index 0000000000..53427a24bc --- /dev/null +++ b/src/core/lib/statistics/window_stats.c @@ -0,0 +1,316 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/statistics/window_stats.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/time.h> +#include <grpc/support/useful.h> +#include <math.h> +#include <stddef.h> +#include <string.h> + +/* typedefs make typing long names easier. Use cws (for census_window_stats) */ +typedef census_window_stats_stat_info cws_stat_info; +typedef struct census_window_stats_sum cws_sum; + +/* Each interval is composed of a number of buckets, which hold a count of + entries and a single statistic */ +typedef struct census_window_stats_bucket { + int64_t count; + void *statistic; +} cws_bucket; + +/* Each interval has a set of buckets, and the variables needed to keep + track of their current state */ +typedef struct census_window_stats_interval_stats { + /* The buckets. There will be 'granularity' + 1 of these. */ + cws_bucket *buckets; + /* Index of the bucket containing the smallest time interval. */ + int bottom_bucket; + /* The smallest time storable in the current window. */ + int64_t bottom; + /* The largest time storable in the current window + 1ns */ + int64_t top; + /* The width of each bucket in ns. */ + int64_t width; +} cws_interval_stats; + +typedef struct census_window_stats { + /* Number of intervals. */ + int nintervals; + /* Number of buckets in each interval. 'granularity' + 1. */ + int nbuckets; + /* Record of stat_info. */ + cws_stat_info stat_info; + /* Stats for each interval. */ + cws_interval_stats *interval_stats; + /* The time the newset stat was recorded. */ + int64_t newest_time; +} window_stats; + +/* Calculate an actual bucket index from a logical index 'IDX'. Other + parameters supply information on the interval struct and overall stats. */ +#define BUCKET_IDX(IS, IDX, WSTATS) \ + ((IS->bottom_bucket + (IDX)) % WSTATS->nbuckets) + +/* The maximum seconds value we can have in a valid timespec. More than this + will result in overflow in timespec_to_ns(). This works out to ~292 years. + TODO: consider using doubles instead of int64. */ +static int64_t max_seconds = (GPR_INT64_MAX - GPR_NS_PER_SEC) / GPR_NS_PER_SEC; + +static int64_t timespec_to_ns(const gpr_timespec ts) { + if (ts.tv_sec > max_seconds) { + return GPR_INT64_MAX - 1; + } + return ts.tv_sec * GPR_NS_PER_SEC + ts.tv_nsec; +} + +static void cws_initialize_statistic(void *statistic, + const cws_stat_info *stat_info) { + if (stat_info->stat_initialize == NULL) { + memset(statistic, 0, stat_info->stat_size); + } else { + stat_info->stat_initialize(statistic); + } +} + +/* Create and initialize a statistic */ +static void *cws_create_statistic(const cws_stat_info *stat_info) { + void *stat = gpr_malloc(stat_info->stat_size); + cws_initialize_statistic(stat, stat_info); + return stat; +} + +window_stats *census_window_stats_create(int nintervals, + const gpr_timespec intervals[], + int granularity, + const cws_stat_info *stat_info) { + window_stats *ret; + int i; + /* validate inputs */ + GPR_ASSERT(nintervals > 0 && granularity > 2 && intervals != NULL && + stat_info != NULL); + for (i = 0; i < nintervals; i++) { + int64_t ns = timespec_to_ns(intervals[i]); + GPR_ASSERT(intervals[i].tv_sec >= 0 && intervals[i].tv_nsec >= 0 && + intervals[i].tv_nsec < GPR_NS_PER_SEC && ns >= 100 && + granularity * 10 <= ns); + } + /* Allocate and initialize relevant data structures */ + ret = (window_stats *)gpr_malloc(sizeof(window_stats)); + ret->nintervals = nintervals; + ret->nbuckets = granularity + 1; + ret->stat_info = *stat_info; + ret->interval_stats = + (cws_interval_stats *)gpr_malloc(nintervals * sizeof(cws_interval_stats)); + for (i = 0; i < nintervals; i++) { + int64_t size_ns = timespec_to_ns(intervals[i]); + cws_interval_stats *is = ret->interval_stats + i; + cws_bucket *buckets = is->buckets = + (cws_bucket *)gpr_malloc(ret->nbuckets * sizeof(cws_bucket)); + int b; + for (b = 0; b < ret->nbuckets; b++) { + buckets[b].statistic = cws_create_statistic(stat_info); + buckets[b].count = 0; + } + is->bottom_bucket = 0; + is->bottom = 0; + is->width = size_ns / granularity; + /* Check for possible overflow issues, and maximize interval size if the + user requested something large enough. */ + if ((GPR_INT64_MAX - is->width) > size_ns) { + is->top = size_ns + is->width; + } else { + is->top = GPR_INT64_MAX; + is->width = GPR_INT64_MAX / (granularity + 1); + } + /* If size doesn't divide evenly, we can have a width slightly too small; + better to have it slightly large. */ + if ((size_ns - (granularity + 1) * is->width) > 0) { + is->width += 1; + } + } + ret->newest_time = 0; + return ret; +} + +/* When we try adding a measurement above the current interval range, we + need to "shift" the buckets sufficiently to cover the new range. */ +static void cws_shift_buckets(const window_stats *wstats, + cws_interval_stats *is, int64_t when_ns) { + int i; + /* number of bucket time widths to "shift" */ + int shift; + /* number of buckets to clear */ + int nclear; + GPR_ASSERT(when_ns >= is->top); + /* number of bucket time widths to "shift" */ + shift = ((when_ns - is->top) / is->width) + 1; + /* number of buckets to clear - limited by actual number of buckets */ + nclear = GPR_MIN(shift, wstats->nbuckets); + for (i = 0; i < nclear; i++) { + int b = BUCKET_IDX(is, i, wstats); + is->buckets[b].count = 0; + cws_initialize_statistic(is->buckets[b].statistic, &wstats->stat_info); + } + /* adjust top/bottom times and current bottom bucket */ + is->bottom_bucket = BUCKET_IDX(is, shift, wstats); + is->top += shift * is->width; + is->bottom += shift * is->width; +} + +void census_window_stats_add(window_stats *wstats, const gpr_timespec when, + const void *stat_value) { + int i; + int64_t when_ns = timespec_to_ns(when); + GPR_ASSERT(wstats->interval_stats != NULL); + for (i = 0; i < wstats->nintervals; i++) { + cws_interval_stats *is = wstats->interval_stats + i; + cws_bucket *bucket; + if (when_ns < is->bottom) { /* Below smallest time in interval: drop */ + continue; + } + if (when_ns >= is->top) { /* above limit: shift buckets */ + cws_shift_buckets(wstats, is, when_ns); + } + /* Add the stat. */ + GPR_ASSERT(is->bottom <= when_ns && when_ns < is->top); + bucket = is->buckets + + BUCKET_IDX(is, (when_ns - is->bottom) / is->width, wstats); + bucket->count++; + wstats->stat_info.stat_add(bucket->statistic, stat_value); + } + if (when_ns > wstats->newest_time) { + wstats->newest_time = when_ns; + } +} + +/* Add a specific bucket contents to an accumulating total. */ +static void cws_add_bucket_to_sum(cws_sum *sum, const cws_bucket *bucket, + const cws_stat_info *stat_info) { + sum->count += bucket->count; + stat_info->stat_add(sum->statistic, bucket->statistic); +} + +/* Add a proportion to an accumulating sum. */ +static void cws_add_proportion_to_sum(double p, cws_sum *sum, + const cws_bucket *bucket, + const cws_stat_info *stat_info) { + sum->count += p * bucket->count; + stat_info->stat_add_proportion(p, sum->statistic, bucket->statistic); +} + +void census_window_stats_get_sums(const window_stats *wstats, + const gpr_timespec when, cws_sum sums[]) { + int i; + int64_t when_ns = timespec_to_ns(when); + GPR_ASSERT(wstats->interval_stats != NULL); + for (i = 0; i < wstats->nintervals; i++) { + int when_bucket; + int new_bucket; + double last_proportion = 1.0; + double bottom_proportion; + cws_interval_stats *is = wstats->interval_stats + i; + cws_sum *sum = sums + i; + sum->count = 0; + cws_initialize_statistic(sum->statistic, &wstats->stat_info); + if (when_ns < is->bottom) { + continue; + } + if (when_ns >= is->top) { + cws_shift_buckets(wstats, is, when_ns); + } + /* Calculating the appropriate amount of which buckets to use can get + complicated. Essentially there are two cases: + 1) if the "top" bucket (new_bucket, where the newest additions to the + stats recorded are entered) corresponds to 'when', then we need + to take a proportion of it - (if when < newest_time) or the full + thing. We also (possibly) need to take a corresponding + proportion of the bottom bucket. + 2) Other cases, we just take a straight proportion. + */ + when_bucket = (when_ns - is->bottom) / is->width; + new_bucket = (wstats->newest_time - is->bottom) / is->width; + if (new_bucket == when_bucket) { + int64_t bottom_bucket_time = is->bottom + when_bucket * is->width; + if (when_ns < wstats->newest_time) { + last_proportion = (double)(when_ns - bottom_bucket_time) / + (double)(wstats->newest_time - bottom_bucket_time); + bottom_proportion = + (double)(is->width - (when_ns - bottom_bucket_time)) / is->width; + } else { + bottom_proportion = + (double)(is->width - (wstats->newest_time - bottom_bucket_time)) / + is->width; + } + } else { + last_proportion = + (double)(when_ns + 1 - is->bottom - when_bucket * is->width) / + is->width; + bottom_proportion = 1.0 - last_proportion; + } + cws_add_proportion_to_sum(last_proportion, sum, + is->buckets + BUCKET_IDX(is, when_bucket, wstats), + &wstats->stat_info); + if (when_bucket != 0) { /* last bucket isn't also bottom bucket */ + int b; + /* Add all of "bottom" bucket if we are looking at a subset of the + full interval, or a proportion if we are adding full interval. */ + cws_add_proportion_to_sum( + (when_bucket == wstats->nbuckets - 1 ? bottom_proportion : 1.0), sum, + is->buckets + is->bottom_bucket, &wstats->stat_info); + /* Add all the remaining buckets (everything but top and bottom). */ + for (b = 1; b < when_bucket; b++) { + cws_add_bucket_to_sum(sum, is->buckets + BUCKET_IDX(is, b, wstats), + &wstats->stat_info); + } + } + } +} + +void census_window_stats_destroy(window_stats *wstats) { + int i; + GPR_ASSERT(wstats->interval_stats != NULL); + for (i = 0; i < wstats->nintervals; i++) { + int b; + for (b = 0; b < wstats->nbuckets; b++) { + gpr_free(wstats->interval_stats[i].buckets[b].statistic); + } + gpr_free(wstats->interval_stats[i].buckets); + } + gpr_free(wstats->interval_stats); + /* Ensure any use-after free triggers assert. */ + wstats->interval_stats = NULL; + gpr_free(wstats); +} diff --git a/src/core/lib/statistics/window_stats.h b/src/core/lib/statistics/window_stats.h new file mode 100644 index 0000000000..8dec50d620 --- /dev/null +++ b/src/core/lib/statistics/window_stats.h @@ -0,0 +1,173 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_STATISTICS_WINDOW_STATS_H +#define GRPC_CORE_LIB_STATISTICS_WINDOW_STATS_H + +#include <grpc/support/time.h> + +/* Keep rolling sums of a user-defined statistic (containing a number of + measurements) over a a number of time intervals ("windows"). For example, + you can use a window_stats object to answer questions such as + "Approximately how many RPCs/s did I receive over the past minute, and + approximately how many bytes did I send out over that period?". + + The type of data to record, and the time intervals to keep are specified + when creating the object via a call to census_window_stats_create(). + + A window's interval is divided into one or more "buckets"; the interval + must be divisible by the number of buckets. Internally, these buckets + control the granularity of window_stats' measurements. Increasing the + number of buckets lets the object respond more quickly to changes in the + overall rate of data added into the object, at the cost of additional + memory usage. + + Here's some code which keeps one minute/hour measurements for two values + (latency in seconds and bytes transferred), with each interval divided into + 4 buckets. + + typedef struct my_stat { + double latency; + int bytes; + } my_stat; + + void add_my_stat(void* base, const void* addme) { + my_stat* b = (my_stat*)base; + const my_stat* a = (const my_stat*)addme; + b->latency += a->latency; + b->bytes += a->bytes; + } + + void add_proportion_my_stat(double p, void* base, const void* addme) { + (my_stat*)result->latency += p * (const my_stat*)base->latency; + (my_stat*)result->bytes += p * (const my_stat*)base->bytes; + } + + #define kNumIntervals 2 + #define kMinInterval 0 + #define kHourInterval 1 + #define kNumBuckets 4 + + const struct census_window_stats_stat_info kMyStatInfo + = { sizeof(my_stat), NULL, add_my_stat, add_proportion_my_stat }; + gpr_timespec intervals[kNumIntervals] = {{60, 0}, {3600, 0}}; + my_stat stat; + my_stat sums[kNumIntervals]; + census_window_stats_sums result[kNumIntervals]; + struct census_window_stats* stats + = census_window_stats_create(kNumIntervals, intervals, kNumBuckets, + &kMyStatInfo); + // Record a new event, taking 15.3ms, transferring 1784 bytes. + stat.latency = 0.153; + stat.bytes = 1784; + census_window_stats_add(stats, gpr_now(GPR_CLOCK_REALTIME), &stat); + // Get sums and print them out + result[kMinInterval].statistic = &sums[kMinInterval]; + result[kHourInterval].statistic = &sums[kHourInterval]; + census_window_stats_get_sums(stats, gpr_now(GPR_CLOCK_REALTIME), result); + printf("%d events/min, average time %gs, average bytes %g\n", + result[kMinInterval].count, + (my_stat*)result[kMinInterval].statistic->latency / + result[kMinInterval].count, + (my_stat*)result[kMinInterval].statistic->bytes / + result[kMinInterval].count + ); + printf("%d events/hr, average time %gs, average bytes %g\n", + result[kHourInterval].count, + (my_stat*)result[kHourInterval].statistic->latency / + result[kHourInterval].count, + (my_stat*)result[kHourInterval].statistic->bytes / + result[kHourInterval].count + ); +*/ + +/* Opaque structure for representing window_stats object */ +struct census_window_stats; + +/* Information provided by API user on the information they want to record */ +typedef struct census_window_stats_stat_info { + /* Number of bytes in user-defined object. */ + size_t stat_size; + /* Function to initialize a user-defined statistics object. If this is set + * to NULL, then the object will be zero-initialized. */ + void (*stat_initialize)(void *stat); + /* Function to add one user-defined statistics object ('addme') to 'base' */ + void (*stat_add)(void *base, const void *addme); + /* As for previous function, but only add a proportion 'p'. This API will + currently only use 'p' values in the range [0,1], but other values are + possible in the future, and should be supported. */ + void (*stat_add_proportion)(double p, void *base, const void *addme); +} census_window_stats_stat_info; + +/* Create a new window_stats object. 'nintervals' is the number of + 'intervals', and must be >=1. 'granularity' is the number of buckets, with + a larger number using more memory, but providing greater accuracy of + results. 'granularity should be > 2. We also require that each interval be + at least 10 * 'granularity' nanoseconds in size. 'stat_info' contains + information about the statistic to be gathered. Intervals greater than ~192 + years will be treated as essentially infinite in size. This function will + GPR_ASSERT() if the object cannot be created or any of the parameters have + invalid values. This function is thread-safe. */ +struct census_window_stats *census_window_stats_create( + int nintervals, const gpr_timespec intervals[], int granularity, + const census_window_stats_stat_info *stat_info); + +/* Add a new measurement (in 'stat_value'), as of a given time ('when'). + This function is thread-compatible. */ +void census_window_stats_add(struct census_window_stats *wstats, + const gpr_timespec when, const void *stat_value); + +/* Structure used to record a single intervals sum for a given statistic */ +typedef struct census_window_stats_sum { + /* Total count of samples. Note that because some internal interpolation + is performed, the count of samples returned for each interval may not be an + integral value. */ + double count; + /* Sum for statistic */ + void *statistic; +} census_window_stats_sums; + +/* Retrieve a set of all values stored in a window_stats object 'wstats'. The + number of 'sums' MUST be the same as the number 'nintervals' used in + census_window_stats_create(). This function is thread-compatible. */ +void census_window_stats_get_sums(const struct census_window_stats *wstats, + const gpr_timespec when, + struct census_window_stats_sum sums[]); + +/* Destroy a window_stats object. Once this function has been called, the + object will no longer be usable from any of the above functions (and + calling them will most likely result in a NULL-pointer dereference or + assertion failure). This function is thread-compatible. */ +void census_window_stats_destroy(struct census_window_stats *wstats); + +#endif /* GRPC_CORE_LIB_STATISTICS_WINDOW_STATS_H */ diff --git a/src/core/lib/support/alloc.c b/src/core/lib/support/alloc.c new file mode 100644 index 0000000000..27fa6a95ed --- /dev/null +++ b/src/core/lib/support/alloc.c @@ -0,0 +1,90 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/alloc.h> + +#include <grpc/support/log.h> +#include <grpc/support/port_platform.h> +#include <stdlib.h> +#include "src/core/lib/profiling/timers.h" + +static gpr_allocation_functions g_alloc_functions = {malloc, realloc, free}; + +gpr_allocation_functions gpr_get_allocation_functions() { + return g_alloc_functions; +} + +void gpr_set_allocation_functions(gpr_allocation_functions functions) { + GPR_ASSERT(functions.malloc_fn != NULL); + GPR_ASSERT(functions.realloc_fn != NULL); + GPR_ASSERT(functions.free_fn != NULL); + g_alloc_functions = functions; +} + +void *gpr_malloc(size_t size) { + void *p; + GPR_TIMER_BEGIN("gpr_malloc", 0); + p = g_alloc_functions.malloc_fn(size); + if (!p) { + abort(); + } + GPR_TIMER_END("gpr_malloc", 0); + return p; +} + +void gpr_free(void *p) { + GPR_TIMER_BEGIN("gpr_free", 0); + g_alloc_functions.free_fn(p); + GPR_TIMER_END("gpr_free", 0); +} + +void *gpr_realloc(void *p, size_t size) { + GPR_TIMER_BEGIN("gpr_realloc", 0); + p = g_alloc_functions.realloc_fn(p, size); + if (!p) { + abort(); + } + GPR_TIMER_END("gpr_realloc", 0); + return p; +} + +void *gpr_malloc_aligned(size_t size, size_t alignment_log) { + size_t alignment = ((size_t)1) << alignment_log; + size_t extra = alignment - 1 + sizeof(void *); + void *p = gpr_malloc(size + extra); + void **ret = (void **)(((uintptr_t)p + extra) & ~(alignment - 1)); + ret[-1] = p; + return (void *)ret; +} + +void gpr_free_aligned(void *ptr) { gpr_free(((void **)ptr)[-1]); } diff --git a/src/core/lib/support/avl.c b/src/core/lib/support/avl.c new file mode 100644 index 0000000000..f378b3ee17 --- /dev/null +++ b/src/core/lib/support/avl.c @@ -0,0 +1,288 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/avl.h> + +#include <assert.h> +#include <stdlib.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/string_util.h> +#include <grpc/support/useful.h> + +gpr_avl gpr_avl_create(const gpr_avl_vtable *vtable) { + gpr_avl out; + out.vtable = vtable; + out.root = NULL; + return out; +} + +static gpr_avl_node *ref_node(gpr_avl_node *node) { + if (node) { + gpr_ref(&node->refs); + } + return node; +} + +static void unref_node(const gpr_avl_vtable *vtable, gpr_avl_node *node) { + if (node == NULL) { + return; + } + if (gpr_unref(&node->refs)) { + vtable->destroy_key(node->key); + vtable->destroy_value(node->value); + unref_node(vtable, node->left); + unref_node(vtable, node->right); + gpr_free(node); + } +} + +static long node_height(gpr_avl_node *node) { + return node == NULL ? 0 : node->height; +} + +#ifndef NDEBUG +static long calculate_height(gpr_avl_node *node) { + return node == NULL ? 0 : 1 + GPR_MAX(calculate_height(node->left), + calculate_height(node->right)); +} + +static gpr_avl_node *assert_invariants(gpr_avl_node *n) { + if (n == NULL) return NULL; + assert_invariants(n->left); + assert_invariants(n->right); + assert(calculate_height(n) == n->height); + assert(labs(node_height(n->left) - node_height(n->right)) <= 1); + return n; +} +#else +static gpr_avl_node *assert_invariants(gpr_avl_node *n) { return n; } +#endif + +gpr_avl_node *new_node(void *key, void *value, gpr_avl_node *left, + gpr_avl_node *right) { + gpr_avl_node *node = gpr_malloc(sizeof(*node)); + gpr_ref_init(&node->refs, 1); + node->key = key; + node->value = value; + node->left = assert_invariants(left); + node->right = assert_invariants(right); + node->height = 1 + GPR_MAX(node_height(left), node_height(right)); + return node; +} + +static gpr_avl_node *get(const gpr_avl_vtable *vtable, gpr_avl_node *node, + void *key) { + long cmp; + + if (node == NULL) { + return NULL; + } + + cmp = vtable->compare_keys(node->key, key); + if (cmp == 0) { + return node; + } else if (cmp > 0) { + return get(vtable, node->left, key); + } else { + return get(vtable, node->right, key); + } +} + +void *gpr_avl_get(gpr_avl avl, void *key) { + gpr_avl_node *node = get(avl.vtable, avl.root, key); + return node ? node->value : NULL; +} + +static gpr_avl_node *rotate_left(const gpr_avl_vtable *vtable, void *key, + void *value, gpr_avl_node *left, + gpr_avl_node *right) { + gpr_avl_node *n = + new_node(vtable->copy_key(right->key), vtable->copy_value(right->value), + new_node(key, value, left, ref_node(right->left)), + ref_node(right->right)); + unref_node(vtable, right); + return n; +} + +static gpr_avl_node *rotate_right(const gpr_avl_vtable *vtable, void *key, + void *value, gpr_avl_node *left, + gpr_avl_node *right) { + gpr_avl_node *n = new_node( + vtable->copy_key(left->key), vtable->copy_value(left->value), + ref_node(left->left), new_node(key, value, ref_node(left->right), right)); + unref_node(vtable, left); + return n; +} + +static gpr_avl_node *rotate_left_right(const gpr_avl_vtable *vtable, void *key, + void *value, gpr_avl_node *left, + gpr_avl_node *right) { + /* rotate_right(..., rotate_left(left), right) */ + gpr_avl_node *n = new_node( + vtable->copy_key(left->right->key), + vtable->copy_value(left->right->value), + new_node(vtable->copy_key(left->key), vtable->copy_value(left->value), + ref_node(left->left), ref_node(left->right->left)), + new_node(key, value, ref_node(left->right->right), right)); + unref_node(vtable, left); + return n; +} + +static gpr_avl_node *rotate_right_left(const gpr_avl_vtable *vtable, void *key, + void *value, gpr_avl_node *left, + gpr_avl_node *right) { + /* rotate_left(..., left, rotate_right(right)) */ + gpr_avl_node *n = new_node( + vtable->copy_key(right->left->key), + vtable->copy_value(right->left->value), + new_node(key, value, left, ref_node(right->left->left)), + new_node(vtable->copy_key(right->key), vtable->copy_value(right->value), + ref_node(right->left->right), ref_node(right->right))); + unref_node(vtable, right); + return n; +} + +static gpr_avl_node *rebalance(const gpr_avl_vtable *vtable, void *key, + void *value, gpr_avl_node *left, + gpr_avl_node *right) { + switch (node_height(left) - node_height(right)) { + case 2: + if (node_height(left->left) - node_height(left->right) == -1) { + return assert_invariants( + rotate_left_right(vtable, key, value, left, right)); + } else { + return assert_invariants(rotate_right(vtable, key, value, left, right)); + } + case -2: + if (node_height(right->left) - node_height(right->right) == 1) { + return assert_invariants( + rotate_right_left(vtable, key, value, left, right)); + } else { + return assert_invariants(rotate_left(vtable, key, value, left, right)); + } + default: + return assert_invariants(new_node(key, value, left, right)); + } +} + +static gpr_avl_node *add(const gpr_avl_vtable *vtable, gpr_avl_node *node, + void *key, void *value) { + long cmp; + if (node == NULL) { + return new_node(key, value, NULL, NULL); + } + cmp = vtable->compare_keys(node->key, key); + if (cmp == 0) { + return new_node(key, value, ref_node(node->left), ref_node(node->right)); + } else if (cmp > 0) { + return rebalance( + vtable, vtable->copy_key(node->key), vtable->copy_value(node->value), + add(vtable, node->left, key, value), ref_node(node->right)); + } else { + return rebalance(vtable, vtable->copy_key(node->key), + vtable->copy_value(node->value), ref_node(node->left), + add(vtable, node->right, key, value)); + } +} + +gpr_avl gpr_avl_add(gpr_avl avl, void *key, void *value) { + gpr_avl_node *old_root = avl.root; + avl.root = add(avl.vtable, avl.root, key, value); + assert_invariants(avl.root); + unref_node(avl.vtable, old_root); + return avl; +} + +static gpr_avl_node *in_order_head(gpr_avl_node *node) { + while (node->left != NULL) { + node = node->left; + } + return node; +} + +static gpr_avl_node *in_order_tail(gpr_avl_node *node) { + while (node->right != NULL) { + node = node->right; + } + return node; +} + +static gpr_avl_node *remove(const gpr_avl_vtable *vtable, gpr_avl_node *node, + void *key) { + long cmp; + if (node == NULL) { + return NULL; + } + cmp = vtable->compare_keys(node->key, key); + if (cmp == 0) { + if (node->left == NULL) { + return ref_node(node->right); + } else if (node->right == NULL) { + return ref_node(node->left); + } else if (node->left->height < node->right->height) { + gpr_avl_node *h = in_order_head(node->right); + return rebalance(vtable, vtable->copy_key(h->key), + vtable->copy_value(h->value), ref_node(node->left), + remove(vtable, node->right, h->key)); + } else { + gpr_avl_node *h = in_order_tail(node->left); + return rebalance( + vtable, vtable->copy_key(h->key), vtable->copy_value(h->value), + remove(vtable, node->left, h->key), ref_node(node->right)); + } + } else if (cmp > 0) { + return rebalance(vtable, vtable->copy_key(node->key), + vtable->copy_value(node->value), + remove(vtable, node->left, key), ref_node(node->right)); + } else { + return rebalance(vtable, vtable->copy_key(node->key), + vtable->copy_value(node->value), ref_node(node->left), + remove(vtable, node->right, key)); + } +} + +gpr_avl gpr_avl_remove(gpr_avl avl, void *key) { + gpr_avl_node *old_root = avl.root; + avl.root = remove(avl.vtable, avl.root, key); + assert_invariants(avl.root); + unref_node(avl.vtable, old_root); + return avl; +} + +gpr_avl gpr_avl_ref(gpr_avl avl) { + ref_node(avl.root); + return avl; +} + +void gpr_avl_unref(gpr_avl avl) { unref_node(avl.vtable, avl.root); } diff --git a/src/core/lib/support/backoff.c b/src/core/lib/support/backoff.c new file mode 100644 index 0000000000..e89ef47220 --- /dev/null +++ b/src/core/lib/support/backoff.c @@ -0,0 +1,76 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/support/backoff.h" + +#include <grpc/support/useful.h> + +void gpr_backoff_init(gpr_backoff *backoff, double multiplier, double jitter, + int64_t min_timeout_millis, int64_t max_timeout_millis) { + backoff->multiplier = multiplier; + backoff->jitter = jitter; + backoff->min_timeout_millis = min_timeout_millis; + backoff->max_timeout_millis = max_timeout_millis; + backoff->rng_state = (uint32_t)gpr_now(GPR_CLOCK_REALTIME).tv_nsec; +} + +gpr_timespec gpr_backoff_begin(gpr_backoff *backoff, gpr_timespec now) { + backoff->current_timeout_millis = backoff->min_timeout_millis; + return gpr_time_add( + now, gpr_time_from_millis(backoff->current_timeout_millis, GPR_TIMESPAN)); +} + +/* Generate a random number between 0 and 1. */ +static double generate_uniform_random_number(uint32_t *rng_state) { + *rng_state = (1103515245 * *rng_state + 12345) % ((uint32_t)1 << 31); + return *rng_state / (double)((uint32_t)1 << 31); +} + +gpr_timespec gpr_backoff_step(gpr_backoff *backoff, gpr_timespec now) { + double new_timeout_millis = + backoff->multiplier * (double)backoff->current_timeout_millis; + double jitter_range = backoff->jitter * new_timeout_millis; + double jitter = + (2 * generate_uniform_random_number(&backoff->rng_state) - 1) * + jitter_range; + backoff->current_timeout_millis = + GPR_CLAMP((int64_t)(new_timeout_millis + jitter), + backoff->min_timeout_millis, backoff->max_timeout_millis); + return gpr_time_add( + now, gpr_time_from_millis(backoff->current_timeout_millis, GPR_TIMESPAN)); +} + +void gpr_backoff_reset(gpr_backoff *backoff) { + // forces step() to return a timeout of min_timeout_millis + backoff->current_timeout_millis = 0; +} diff --git a/src/core/lib/support/backoff.h b/src/core/lib/support/backoff.h new file mode 100644 index 0000000000..6d40c15546 --- /dev/null +++ b/src/core/lib/support/backoff.h @@ -0,0 +1,68 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SUPPORT_BACKOFF_H +#define GRPC_CORE_LIB_SUPPORT_BACKOFF_H + +#include <grpc/support/time.h> + +typedef struct { + /// const: multiplier between retry attempts + double multiplier; + /// const: amount to randomize backoffs + double jitter; + /// const: minimum time between retries in milliseconds + int64_t min_timeout_millis; + /// const: maximum time between retries in milliseconds + int64_t max_timeout_millis; + + /// random number generator + uint32_t rng_state; + + /// current retry timeout in milliseconds + int64_t current_timeout_millis; +} gpr_backoff; + +/// Initialize backoff machinery - does not need to be destroyed +void gpr_backoff_init(gpr_backoff *backoff, double multiplier, double jitter, + int64_t min_timeout_millis, int64_t max_timeout_millis); + +/// Begin retry loop: returns a timespec for the NEXT retry +gpr_timespec gpr_backoff_begin(gpr_backoff *backoff, gpr_timespec now); +/// Step a retry loop: returns a timespec for the NEXT retry +gpr_timespec gpr_backoff_step(gpr_backoff *backoff, gpr_timespec now); +/// Reset the backoff, so the next gpr_backoff_step will be a gpr_backoff_begin +/// instead +void gpr_backoff_reset(gpr_backoff *backoff); + +#endif /* GRPC_CORE_LIB_SUPPORT_BACKOFF_H */ diff --git a/src/core/lib/support/block_annotate.h b/src/core/lib/support/block_annotate.h new file mode 100644 index 0000000000..bd3071655e --- /dev/null +++ b/src/core/lib/support/block_annotate.h @@ -0,0 +1,48 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SUPPORT_BLOCK_ANNOTATE_H +#define GRPC_CORE_LIB_SUPPORT_BLOCK_ANNOTATE_H + +/* These annotations identify the beginning and end of regions where + the code may block for reasons other than synchronization functions. + These include poll, epoll, and getaddrinfo. */ + +#define GRPC_SCHEDULING_START_BLOCKING_REGION \ + do { \ + } while (0) +#define GRPC_SCHEDULING_END_BLOCKING_REGION \ + do { \ + } while (0) + +#endif /* GRPC_CORE_LIB_SUPPORT_BLOCK_ANNOTATE_H */ diff --git a/src/core/lib/support/cmdline.c b/src/core/lib/support/cmdline.c new file mode 100644 index 0000000000..35c4990b22 --- /dev/null +++ b/src/core/lib/support/cmdline.c @@ -0,0 +1,347 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/cmdline.h> + +#include <limits.h> +#include <stdio.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> +#include "src/core/lib/support/string.h" + +typedef enum { ARGTYPE_INT, ARGTYPE_BOOL, ARGTYPE_STRING } argtype; + +typedef struct arg { + const char *name; + const char *help; + argtype type; + void *value; + struct arg *next; +} arg; + +struct gpr_cmdline { + const char *description; + arg *args; + const char *argv0; + + const char *extra_arg_name; + const char *extra_arg_help; + void (*extra_arg)(void *user_data, const char *arg); + void *extra_arg_user_data; + + int (*state)(gpr_cmdline *cl, char *arg); + arg *cur_arg; + + int survive_failure; +}; + +static int normal_state(gpr_cmdline *cl, char *arg); + +gpr_cmdline *gpr_cmdline_create(const char *description) { + gpr_cmdline *cl = gpr_malloc(sizeof(gpr_cmdline)); + memset(cl, 0, sizeof(gpr_cmdline)); + + cl->description = description; + cl->state = normal_state; + + return cl; +} + +void gpr_cmdline_set_survive_failure(gpr_cmdline *cl) { + cl->survive_failure = 1; +} + +void gpr_cmdline_destroy(gpr_cmdline *cl) { + while (cl->args) { + arg *a = cl->args; + cl->args = a->next; + gpr_free(a); + } + gpr_free(cl); +} + +static void add_arg(gpr_cmdline *cl, const char *name, const char *help, + argtype type, void *value) { + arg *a; + + for (a = cl->args; a; a = a->next) { + GPR_ASSERT(0 != strcmp(a->name, name)); + } + + a = gpr_malloc(sizeof(arg)); + memset(a, 0, sizeof(arg)); + a->name = name; + a->help = help; + a->type = type; + a->value = value; + a->next = cl->args; + cl->args = a; +} + +void gpr_cmdline_add_int(gpr_cmdline *cl, const char *name, const char *help, + int *value) { + add_arg(cl, name, help, ARGTYPE_INT, value); +} + +void gpr_cmdline_add_flag(gpr_cmdline *cl, const char *name, const char *help, + int *value) { + add_arg(cl, name, help, ARGTYPE_BOOL, value); +} + +void gpr_cmdline_add_string(gpr_cmdline *cl, const char *name, const char *help, + char **value) { + add_arg(cl, name, help, ARGTYPE_STRING, value); +} + +void gpr_cmdline_on_extra_arg( + gpr_cmdline *cl, const char *name, const char *help, + void (*on_extra_arg)(void *user_data, const char *arg), void *user_data) { + GPR_ASSERT(!cl->extra_arg); + GPR_ASSERT(on_extra_arg); + + cl->extra_arg = on_extra_arg; + cl->extra_arg_user_data = user_data; + cl->extra_arg_name = name; + cl->extra_arg_help = help; +} + +/* recursively descend argument list, adding the last element + to s first - so that arguments are added in the order they were + added to the list by api calls */ +static void add_args_to_usage(gpr_strvec *s, arg *a) { + char *tmp; + + if (!a) return; + add_args_to_usage(s, a->next); + + switch (a->type) { + case ARGTYPE_BOOL: + gpr_asprintf(&tmp, " [--%s|--no-%s]", a->name, a->name); + gpr_strvec_add(s, tmp); + break; + case ARGTYPE_STRING: + gpr_asprintf(&tmp, " [--%s=string]", a->name); + gpr_strvec_add(s, tmp); + break; + case ARGTYPE_INT: + gpr_asprintf(&tmp, " [--%s=int]", a->name); + gpr_strvec_add(s, tmp); + break; + } +} + +char *gpr_cmdline_usage_string(gpr_cmdline *cl, const char *argv0) { + /* TODO(ctiller): make this prettier */ + gpr_strvec s; + char *tmp; + const char *name = strrchr(argv0, '/'); + + if (name) { + name++; + } else { + name = argv0; + } + + gpr_strvec_init(&s); + + gpr_asprintf(&tmp, "Usage: %s", name); + gpr_strvec_add(&s, tmp); + add_args_to_usage(&s, cl->args); + if (cl->extra_arg) { + gpr_asprintf(&tmp, " [%s...]", cl->extra_arg_name); + gpr_strvec_add(&s, tmp); + } + gpr_strvec_add(&s, gpr_strdup("\n")); + + tmp = gpr_strvec_flatten(&s, NULL); + gpr_strvec_destroy(&s); + return tmp; +} + +static int print_usage_and_die(gpr_cmdline *cl) { + char *usage = gpr_cmdline_usage_string(cl, cl->argv0); + fprintf(stderr, "%s", usage); + gpr_free(usage); + if (!cl->survive_failure) { + exit(1); + } + return 0; +} + +static int extra_state(gpr_cmdline *cl, char *str) { + if (!cl->extra_arg) { + return print_usage_and_die(cl); + } + cl->extra_arg(cl->extra_arg_user_data, str); + return 1; +} + +static arg *find_arg(gpr_cmdline *cl, char *name) { + arg *a; + + for (a = cl->args; a; a = a->next) { + if (0 == strcmp(a->name, name)) { + break; + } + } + + if (!a) { + fprintf(stderr, "Unknown argument: %s\n", name); + return NULL; + } + + return a; +} + +static int value_state(gpr_cmdline *cl, char *str) { + long intval; + char *end; + + GPR_ASSERT(cl->cur_arg); + + switch (cl->cur_arg->type) { + case ARGTYPE_INT: + intval = strtol(str, &end, 0); + if (*end || intval < INT_MIN || intval > INT_MAX) { + fprintf(stderr, "expected integer, got '%s' for %s\n", str, + cl->cur_arg->name); + return print_usage_and_die(cl); + } + *(int *)cl->cur_arg->value = (int)intval; + break; + case ARGTYPE_BOOL: + if (0 == strcmp(str, "1") || 0 == strcmp(str, "true")) { + *(int *)cl->cur_arg->value = 1; + } else if (0 == strcmp(str, "0") || 0 == strcmp(str, "false")) { + *(int *)cl->cur_arg->value = 0; + } else { + fprintf(stderr, "expected boolean, got '%s' for %s\n", str, + cl->cur_arg->name); + return print_usage_and_die(cl); + } + break; + case ARGTYPE_STRING: + *(char **)cl->cur_arg->value = str; + break; + } + + cl->state = normal_state; + return 1; +} + +static int normal_state(gpr_cmdline *cl, char *str) { + char *eq = NULL; + char *tmp = NULL; + char *arg_name = NULL; + int r = 1; + + if (0 == strcmp(str, "-help") || 0 == strcmp(str, "--help") || + 0 == strcmp(str, "-h")) { + return print_usage_and_die(cl); + } + + cl->cur_arg = NULL; + + if (str[0] == '-') { + if (str[1] == '-') { + if (str[2] == 0) { + /* handle '--' to move to just extra args */ + cl->state = extra_state; + return 1; + } + str += 2; + } else { + str += 1; + } + /* first byte of str is now past the leading '-' or '--' */ + if (str[0] == 'n' && str[1] == 'o' && str[2] == '-') { + /* str is of the form '--no-foo' - it's a flag disable */ + str += 3; + cl->cur_arg = find_arg(cl, str); + if (cl->cur_arg == NULL) { + return print_usage_and_die(cl); + } + if (cl->cur_arg->type != ARGTYPE_BOOL) { + fprintf(stderr, "%s is not a flag argument\n", str); + return print_usage_and_die(cl); + } + *(int *)cl->cur_arg->value = 0; + return 1; /* early out */ + } + eq = strchr(str, '='); + if (eq != NULL) { + /* copy the string into a temp buffer and extract the name */ + tmp = arg_name = gpr_malloc((size_t)(eq - str + 1)); + memcpy(arg_name, str, (size_t)(eq - str)); + arg_name[eq - str] = 0; + } else { + arg_name = str; + } + cl->cur_arg = find_arg(cl, arg_name); + if (cl->cur_arg == NULL) { + return print_usage_and_die(cl); + } + if (eq != NULL) { + /* str was of the type --foo=value, parse the value */ + r = value_state(cl, eq + 1); + } else if (cl->cur_arg->type != ARGTYPE_BOOL) { + /* flag types don't have a '--foo value' variant, other types do */ + cl->state = value_state; + } else { + /* flag parameter: just set the value */ + *(int *)cl->cur_arg->value = 1; + } + } else { + r = extra_state(cl, str); + } + + gpr_free(tmp); + return r; +} + +int gpr_cmdline_parse(gpr_cmdline *cl, int argc, char **argv) { + int i; + + GPR_ASSERT(argc >= 1); + cl->argv0 = argv[0]; + + for (i = 1; i < argc; i++) { + if (!cl->state(cl, argv[i])) { + return 0; + } + } + return 1; +} diff --git a/src/core/lib/support/cpu_iphone.c b/src/core/lib/support/cpu_iphone.c new file mode 100644 index 0000000000..e83191bada --- /dev/null +++ b/src/core/lib/support/cpu_iphone.c @@ -0,0 +1,49 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_CPU_IPHONE + +/* Probably 2 instead of 1, but see comment on gpr_cpu_current_cpu. */ +unsigned gpr_cpu_num_cores(void) { return 1; } + +/* Most code that's using this is using it to shard across work queues. So + unless profiling shows it's a problem or there appears a way to detect the + currently running CPU core, let's have it shard the default way. + Note that the interface in cpu.h lets gpr_cpu_num_cores return 0, but doing + it makes it impossible for gpr_cpu_current_cpu to satisfy its stated range, + and some code might be relying on it. */ +unsigned gpr_cpu_current_cpu(void) { return 0; } + +#endif /* GPR_CPU_IPHONE */ diff --git a/src/core/lib/support/cpu_linux.c b/src/core/lib/support/cpu_linux.c new file mode 100644 index 0000000000..5597df2d03 --- /dev/null +++ b/src/core/lib/support/cpu_linux.c @@ -0,0 +1,78 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif /* _GNU_SOURCE */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_CPU_LINUX + +#include <errno.h> +#include <sched.h> +#include <string.h> +#include <unistd.h> + +#include <grpc/support/cpu.h> +#include <grpc/support/log.h> +#include <grpc/support/sync.h> + +static int ncpus = 0; + +static void init_num_cpus() { + /* This must be signed. sysconf returns -1 when the number cannot be + determined */ + ncpus = (int)sysconf(_SC_NPROCESSORS_ONLN); + if (ncpus < 1) { + gpr_log(GPR_ERROR, "Cannot determine number of CPUs: assuming 1"); + ncpus = 1; + } +} + +unsigned gpr_cpu_num_cores(void) { + static gpr_once once = GPR_ONCE_INIT; + gpr_once_init(&once, init_num_cpus); + return (unsigned)ncpus; +} + +unsigned gpr_cpu_current_cpu(void) { + int cpu = sched_getcpu(); + if (cpu < 0) { + gpr_log(GPR_ERROR, "Error determining current CPU: %s\n", strerror(errno)); + return 0; + } + return (unsigned)cpu; +} + +#endif /* GPR_CPU_LINUX */ diff --git a/src/core/lib/support/cpu_posix.c b/src/core/lib/support/cpu_posix.c new file mode 100644 index 0000000000..e508ddd8ca --- /dev/null +++ b/src/core/lib/support/cpu_posix.c @@ -0,0 +1,77 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_CPU_POSIX + +#include <errno.h> +#include <string.h> +#include <unistd.h> + +#include <grpc/support/log.h> +#include <grpc/support/sync.h> + +static __thread char magic_thread_local; + +static long ncpus = 0; + +static void init_ncpus() { + ncpus = sysconf(_SC_NPROCESSORS_ONLN); + if (ncpus < 1 || ncpus > INT32_MAX) { + gpr_log(GPR_ERROR, "Cannot determine number of CPUs: assuming 1"); + ncpus = 1; + } +} + +unsigned gpr_cpu_num_cores(void) { + static gpr_once once = GPR_ONCE_INIT; + gpr_once_init(&once, init_ncpus); + return (unsigned)ncpus; +} + +/* This is a cheap, but good enough, pointer hash for sharding things: */ +static size_t shard_ptr(const void *info) { + size_t x = (size_t)info; + return ((x >> 4) ^ (x >> 9) ^ (x >> 14)) % gpr_cpu_num_cores(); +} + +unsigned gpr_cpu_current_cpu(void) { + /* NOTE: there's no way I know to return the actual cpu index portably... + most code that's using this is using it to shard across work queues though, + so here we use thread identity instead to achieve a similar though not + identical effect */ + return (unsigned)shard_ptr(&magic_thread_local); +} + +#endif /* GPR_CPU_POSIX */ diff --git a/src/core/lib/support/cpu_windows.c b/src/core/lib/support/cpu_windows.c new file mode 100644 index 0000000000..0f84a9e5ea --- /dev/null +++ b/src/core/lib/support/cpu_windows.c @@ -0,0 +1,47 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WIN32 +#include <grpc/support/log.h> + +unsigned gpr_cpu_num_cores(void) { + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwNumberOfProcessors; +} + +unsigned gpr_cpu_current_cpu(void) { return GetCurrentProcessorNumber(); } + +#endif /* GPR_WIN32 */ diff --git a/src/core/lib/support/env.h b/src/core/lib/support/env.h new file mode 100644 index 0000000000..ddc4ee3c6d --- /dev/null +++ b/src/core/lib/support/env.h @@ -0,0 +1,60 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SUPPORT_ENV_H +#define GRPC_CORE_LIB_SUPPORT_ENV_H + +#include <stdio.h> + +#include <grpc/support/slice.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Env utility functions */ + +/* Gets the environment variable value with the specified name. + Returns a newly allocated string. It is the responsability of the caller to + gpr_free the return value if not NULL (which means that the environment + variable exists). */ +char *gpr_getenv(const char *name); + +/* Sets the the environment with the specified name to the specified value. */ +void gpr_setenv(const char *name, const char *value); + +#ifdef __cplusplus +} +#endif + +#endif /* GRPC_CORE_LIB_SUPPORT_ENV_H */ diff --git a/src/core/lib/support/env_linux.c b/src/core/lib/support/env_linux.c new file mode 100644 index 0000000000..a86133e6c3 --- /dev/null +++ b/src/core/lib/support/env_linux.c @@ -0,0 +1,89 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* for secure_getenv. */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <grpc/support/port_platform.h> + +#ifdef GPR_LINUX_ENV + +#include "src/core/lib/support/env.h" + +#include <dlfcn.h> +#include <features.h> +#include <stdlib.h> +#include <string.h> + +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> +#include <grpc/support/useful.h> + +#include "src/core/lib/support/string.h" + +char *gpr_getenv(const char *name) { +#if defined(GPR_BACKWARDS_COMPATIBILITY_MODE) + typedef char *(*getenv_type)(const char *); + static getenv_type getenv_func = NULL; + /* Check to see which getenv variant is supported (go from most + * to least secure) */ + const char *names[] = {"secure_getenv", "__secure_getenv", "getenv"}; + for (size_t i = 0; getenv_func == NULL && i < GPR_ARRAY_SIZE(names); i++) { + getenv_func = (getenv_type)dlsym(RTLD_DEFAULT, names[i]); + if (getenv_func != NULL && strstr(names[i], "secure") == NULL) { + gpr_log(GPR_DEBUG, + "Warning: insecure environment read function '%s' used", + names[i]); + } + } + char *result = getenv_func(name); + return result == NULL ? result : gpr_strdup(result); +#elif __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 17) + char *result = secure_getenv(name); + return result == NULL ? result : gpr_strdup(result); +#else + gpr_log(GPR_DEBUG, "Warning: insecure environment read function '%s' used", + "getenv"); + char *result = getenv(name); + return result == NULL ? result : gpr_strdup(result); +#endif +} + +void gpr_setenv(const char *name, const char *value) { + int res = setenv(name, value, 1); + GPR_ASSERT(res == 0); +} + +#endif /* GPR_LINUX_ENV */ diff --git a/src/core/lib/support/env_posix.c b/src/core/lib/support/env_posix.c new file mode 100644 index 0000000000..1b57b094a9 --- /dev/null +++ b/src/core/lib/support/env_posix.c @@ -0,0 +1,57 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_ENV + +#include "src/core/lib/support/env.h" + +#include <stdlib.h> + +#include <grpc/support/log.h> + +#include <grpc/support/string_util.h> +#include "src/core/lib/support/string.h" + +char *gpr_getenv(const char *name) { + char *result = getenv(name); + return result == NULL ? result : gpr_strdup(result); +} + +void gpr_setenv(const char *name, const char *value) { + int res = setenv(name, value, 1); + GPR_ASSERT(res == 0); +} + +#endif /* GPR_POSIX_ENV */ diff --git a/src/core/lib/support/env_win32.c b/src/core/lib/support/env_win32.c new file mode 100644 index 0000000000..566feee49e --- /dev/null +++ b/src/core/lib/support/env_win32.c @@ -0,0 +1,73 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WIN32 + +#include "src/core/lib/support/env.h" +#include "src/core/lib/support/string.h" + +#ifdef __MINGW32__ +errno_t getenv_s(size_t *size_needed, char *buffer, size_t size, + const char *varname); +#else +#include <stdlib.h> +#endif + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> + +char *gpr_getenv(const char *name) { + size_t size; + char *result = NULL; + errno_t err; + + err = getenv_s(&size, NULL, 0, name); + if (err || (size == 0)) return NULL; + result = gpr_malloc(size); + err = getenv_s(&size, result, size, name); + if (err) { + gpr_free(result); + return NULL; + } + return result; +} + +void gpr_setenv(const char *name, const char *value) { + errno_t res = _putenv_s(name, value); + GPR_ASSERT(res == 0); +} + +#endif /* GPR_WIN32 */ diff --git a/src/core/lib/support/histogram.c b/src/core/lib/support/histogram.c new file mode 100644 index 0000000000..62227be1a6 --- /dev/null +++ b/src/core/lib/support/histogram.c @@ -0,0 +1,244 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/histogram.h> + +#include <math.h> +#include <stddef.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/port_platform.h> +#include <grpc/support/useful.h> + +/* Histograms are stored with exponentially increasing bucket sizes. + The first bucket is [0, m) where m = 1 + resolution + Bucket n (n>=1) contains [m**n, m**(n+1)) + There are sufficient buckets to reach max_bucket_start */ + +struct gpr_histogram { + /* Sum of all values seen so far */ + double sum; + /* Sum of squares of all values seen so far */ + double sum_of_squares; + /* number of values seen so far */ + double count; + /* m in the description */ + double multiplier; + double one_on_log_multiplier; + /* minimum value seen */ + double min_seen; + /* maximum value seen */ + double max_seen; + /* maximum representable value */ + double max_possible; + /* number of buckets */ + size_t num_buckets; + /* the buckets themselves */ + uint32_t *buckets; +}; + +/* determine a bucket index given a value - does no bounds checking */ +static size_t bucket_for_unchecked(gpr_histogram *h, double x) { + return (size_t)(log(x) * h->one_on_log_multiplier); +} + +/* bounds checked version of the above */ +static size_t bucket_for(gpr_histogram *h, double x) { + size_t bucket = bucket_for_unchecked(h, GPR_CLAMP(x, 1.0, h->max_possible)); + GPR_ASSERT(bucket < h->num_buckets); + return bucket; +} + +/* at what value does a bucket start? */ +static double bucket_start(gpr_histogram *h, double x) { + return pow(h->multiplier, x); +} + +gpr_histogram *gpr_histogram_create(double resolution, + double max_bucket_start) { + gpr_histogram *h = gpr_malloc(sizeof(gpr_histogram)); + GPR_ASSERT(resolution > 0.0); + GPR_ASSERT(max_bucket_start > resolution); + h->sum = 0.0; + h->sum_of_squares = 0.0; + h->multiplier = 1.0 + resolution; + h->one_on_log_multiplier = 1.0 / log(1.0 + resolution); + h->max_possible = max_bucket_start; + h->count = 0.0; + h->min_seen = max_bucket_start; + h->max_seen = 0.0; + h->num_buckets = bucket_for_unchecked(h, max_bucket_start) + 1; + GPR_ASSERT(h->num_buckets > 1); + GPR_ASSERT(h->num_buckets < 100000000); + h->buckets = gpr_malloc(sizeof(uint32_t) * h->num_buckets); + memset(h->buckets, 0, sizeof(uint32_t) * h->num_buckets); + return h; +} + +void gpr_histogram_destroy(gpr_histogram *h) { + gpr_free(h->buckets); + gpr_free(h); +} + +void gpr_histogram_add(gpr_histogram *h, double x) { + h->sum += x; + h->sum_of_squares += x * x; + h->count++; + if (x < h->min_seen) { + h->min_seen = x; + } + if (x > h->max_seen) { + h->max_seen = x; + } + h->buckets[bucket_for(h, x)]++; +} + +int gpr_histogram_merge(gpr_histogram *dst, const gpr_histogram *src) { + if ((dst->num_buckets != src->num_buckets) || + (dst->multiplier != src->multiplier)) { + /* Fail because these histograms don't match */ + return 0; + } + gpr_histogram_merge_contents(dst, src->buckets, src->num_buckets, + src->min_seen, src->max_seen, src->sum, + src->sum_of_squares, src->count); + return 1; +} + +void gpr_histogram_merge_contents(gpr_histogram *dst, const uint32_t *data, + size_t data_count, double min_seen, + double max_seen, double sum, + double sum_of_squares, double count) { + size_t i; + GPR_ASSERT(dst->num_buckets == data_count); + dst->sum += sum; + dst->sum_of_squares += sum_of_squares; + dst->count += count; + if (min_seen < dst->min_seen) { + dst->min_seen = min_seen; + } + if (max_seen > dst->max_seen) { + dst->max_seen = max_seen; + } + for (i = 0; i < dst->num_buckets; i++) { + dst->buckets[i] += data[i]; + } +} + +static double threshold_for_count_below(gpr_histogram *h, double count_below) { + double count_so_far; + double lower_bound; + double upper_bound; + size_t lower_idx; + size_t upper_idx; + + if (h->count == 0) { + return 0.0; + } + + if (count_below <= 0) { + return h->min_seen; + } + if (count_below >= h->count) { + return h->max_seen; + } + + /* find the lowest bucket that gets us above count_below */ + count_so_far = 0.0; + for (lower_idx = 0; lower_idx < h->num_buckets; lower_idx++) { + count_so_far += h->buckets[lower_idx]; + if (count_so_far >= count_below) { + break; + } + } + if (count_so_far == count_below) { + /* this bucket hits the threshold exactly... we should be midway through + any run of zero values following the bucket */ + for (upper_idx = lower_idx + 1; upper_idx < h->num_buckets; upper_idx++) { + if (h->buckets[upper_idx]) { + break; + } + } + return (bucket_start(h, (double)lower_idx) + + bucket_start(h, (double)upper_idx)) / + 2.0; + } else { + /* treat values as uniform throughout the bucket, and find where this value + should lie */ + lower_bound = bucket_start(h, (double)lower_idx); + upper_bound = bucket_start(h, (double)(lower_idx + 1)); + return GPR_CLAMP(upper_bound - + (upper_bound - lower_bound) * + (count_so_far - count_below) / + h->buckets[lower_idx], + h->min_seen, h->max_seen); + } +} + +double gpr_histogram_percentile(gpr_histogram *h, double percentile) { + return threshold_for_count_below(h, h->count * percentile / 100.0); +} + +double gpr_histogram_mean(gpr_histogram *h) { + GPR_ASSERT(h->count != 0); + return h->sum / h->count; +} + +double gpr_histogram_stddev(gpr_histogram *h) { + return sqrt(gpr_histogram_variance(h)); +} + +double gpr_histogram_variance(gpr_histogram *h) { + if (h->count == 0) return 0.0; + return (h->sum_of_squares * h->count - h->sum * h->sum) / + (h->count * h->count); +} + +double gpr_histogram_maximum(gpr_histogram *h) { return h->max_seen; } + +double gpr_histogram_minimum(gpr_histogram *h) { return h->min_seen; } + +double gpr_histogram_count(gpr_histogram *h) { return h->count; } + +double gpr_histogram_sum(gpr_histogram *h) { return h->sum; } + +double gpr_histogram_sum_of_squares(gpr_histogram *h) { + return h->sum_of_squares; +} + +const uint32_t *gpr_histogram_get_contents(gpr_histogram *h, size_t *size) { + *size = h->num_buckets; + return h->buckets; +} diff --git a/src/core/lib/support/host_port.c b/src/core/lib/support/host_port.c new file mode 100644 index 0000000000..e03f6241ff --- /dev/null +++ b/src/core/lib/support/host_port.c @@ -0,0 +1,110 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/host_port.h> + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> +#include "src/core/lib/support/string.h" + +int gpr_join_host_port(char **out, const char *host, int port) { + if (host[0] != '[' && strchr(host, ':') != NULL) { + /* IPv6 literals must be enclosed in brackets. */ + return gpr_asprintf(out, "[%s]:%d", host, port); + } else { + /* Ordinary non-bracketed host:port. */ + return gpr_asprintf(out, "%s:%d", host, port); + } +} + +int gpr_split_host_port(const char *name, char **host, char **port) { + const char *host_start; + size_t host_len; + const char *port_start; + + *host = NULL; + *port = NULL; + + if (name[0] == '[') { + /* Parse a bracketed host, typically an IPv6 literal. */ + const char *rbracket = strchr(name, ']'); + if (rbracket == NULL) { + /* Unmatched [ */ + return 0; + } + if (rbracket[1] == '\0') { + /* ]<end> */ + port_start = NULL; + } else if (rbracket[1] == ':') { + /* ]:<port?> */ + port_start = rbracket + 2; + } else { + /* ]<invalid> */ + return 0; + } + host_start = name + 1; + host_len = (size_t)(rbracket - host_start); + if (memchr(host_start, ':', host_len) == NULL) { + /* Require all bracketed hosts to contain a colon, because a hostname or + IPv4 address should never use brackets. */ + return 0; + } + } else { + const char *colon = strchr(name, ':'); + if (colon != NULL && strchr(colon + 1, ':') == NULL) { + /* Exactly 1 colon. Split into host:port. */ + host_start = name; + host_len = (size_t)(colon - name); + port_start = colon + 1; + } else { + /* 0 or 2+ colons. Bare hostname or IPv6 litearal. */ + host_start = name; + host_len = strlen(name); + port_start = NULL; + } + } + + /* Allocate return values. */ + *host = gpr_malloc(host_len + 1); + memcpy(*host, host_start, host_len); + (*host)[host_len] = '\0'; + + if (port_start != NULL) { + *port = gpr_strdup(port_start); + } + + return 1; +} diff --git a/src/core/lib/support/load_file.c b/src/core/lib/support/load_file.c new file mode 100644 index 0000000000..0cecd5edd5 --- /dev/null +++ b/src/core/lib/support/load_file.c @@ -0,0 +1,91 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/support/load_file.h" + +#include <errno.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> + +#include "src/core/lib/support/block_annotate.h" +#include "src/core/lib/support/string.h" + +gpr_slice gpr_load_file(const char *filename, int add_null_terminator, + int *success) { + unsigned char *contents = NULL; + size_t contents_size = 0; + char *error_msg = NULL; + gpr_slice result = gpr_empty_slice(); + FILE *file; + size_t bytes_read = 0; + + GRPC_SCHEDULING_START_BLOCKING_REGION; + file = fopen(filename, "rb"); + if (file == NULL) { + gpr_asprintf(&error_msg, "Could not open file %s (error = %s).", filename, + strerror(errno)); + GPR_ASSERT(error_msg != NULL); + goto end; + } + fseek(file, 0, SEEK_END); + /* Converting to size_t on the assumption that it will not fail */ + contents_size = (size_t)ftell(file); + fseek(file, 0, SEEK_SET); + contents = gpr_malloc(contents_size + (add_null_terminator ? 1 : 0)); + bytes_read = fread(contents, 1, contents_size, file); + if (bytes_read < contents_size) { + GPR_ASSERT(ferror(file)); + gpr_asprintf(&error_msg, "Error %s occured while reading file %s.", + strerror(errno), filename); + GPR_ASSERT(error_msg != NULL); + goto end; + } + if (success != NULL) *success = 1; + if (add_null_terminator) { + contents[contents_size++] = 0; + } + result = gpr_slice_new(contents, contents_size, gpr_free); + +end: + if (error_msg != NULL) { + gpr_log(GPR_ERROR, "%s", error_msg); + gpr_free(error_msg); + if (success != NULL) *success = 0; + } + if (file != NULL) fclose(file); + GRPC_SCHEDULING_END_BLOCKING_REGION; + return result; +} diff --git a/src/core/lib/support/load_file.h b/src/core/lib/support/load_file.h new file mode 100644 index 0000000000..fe030c967e --- /dev/null +++ b/src/core/lib/support/load_file.h @@ -0,0 +1,55 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SUPPORT_LOAD_FILE_H +#define GRPC_CORE_LIB_SUPPORT_LOAD_FILE_H + +#include <stdio.h> + +#include <grpc/support/slice.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Loads the content of a file into a slice. add_null_terminator will add + a NULL terminator if non-zero. The success parameter, if not NULL, + will be set to 1 in case of success and 0 in case of failure. */ +gpr_slice gpr_load_file(const char *filename, int add_null_terminator, + int *success); + +#ifdef __cplusplus +} +#endif + +#endif /* GRPC_CORE_LIB_SUPPORT_LOAD_FILE_H */ diff --git a/src/core/lib/support/log.c b/src/core/lib/support/log.c new file mode 100644 index 0000000000..cd6a0726cf --- /dev/null +++ b/src/core/lib/support/log.c @@ -0,0 +1,66 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/log.h> +#include <grpc/support/port_platform.h> + +#include <stdio.h> +#include <string.h> + +extern void gpr_default_log(gpr_log_func_args *args); +static gpr_log_func g_log_func = gpr_default_log; + +const char *gpr_log_severity_string(gpr_log_severity severity) { + switch (severity) { + case GPR_LOG_SEVERITY_DEBUG: + return "D"; + case GPR_LOG_SEVERITY_INFO: + return "I"; + case GPR_LOG_SEVERITY_ERROR: + return "E"; + } + GPR_UNREACHABLE_CODE(return "UNKNOWN"); +} + +void gpr_log_message(const char *file, int line, gpr_log_severity severity, + const char *message) { + gpr_log_func_args lfargs; + memset(&lfargs, 0, sizeof(lfargs)); + lfargs.file = file; + lfargs.line = line; + lfargs.severity = severity; + lfargs.message = message; + g_log_func(&lfargs); +} + +void gpr_set_log_function(gpr_log_func f) { g_log_func = f; } diff --git a/src/core/lib/support/log_android.c b/src/core/lib/support/log_android.c new file mode 100644 index 0000000000..640c9d7099 --- /dev/null +++ b/src/core/lib/support/log_android.c @@ -0,0 +1,87 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_ANDROID + +#include <android/log.h> +#include <grpc/support/log.h> +#include <grpc/support/time.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +static android_LogPriority severity_to_log_priority(gpr_log_severity severity) { + switch (severity) { + case GPR_LOG_SEVERITY_DEBUG: + return ANDROID_LOG_DEBUG; + case GPR_LOG_SEVERITY_INFO: + return ANDROID_LOG_INFO; + case GPR_LOG_SEVERITY_ERROR: + return ANDROID_LOG_ERROR; + } + return ANDROID_LOG_DEFAULT; +} + +void gpr_log(const char *file, int line, gpr_log_severity severity, + const char *format, ...) { + char *message = NULL; + va_list args; + va_start(args, format); + vasprintf(&message, format, args); + va_end(args); + gpr_log_message(file, line, severity, message); + free(message); +} + +void gpr_default_log(gpr_log_func_args *args) { + char *final_slash; + const char *display_file; + char *output = NULL; + + final_slash = strrchr(args->file, '/'); + if (final_slash == NULL) + display_file = args->file; + else + display_file = final_slash + 1; + + asprintf(&output, "%s:%d] %s", display_file, args->line, args->message); + + __android_log_write(severity_to_log_priority(args->severity), "GRPC", output); + + /* allocated by asprintf => use free, not gpr_free */ + free(output); +} + +#endif /* GPR_ANDROID */ diff --git a/src/core/lib/support/log_linux.c b/src/core/lib/support/log_linux.c new file mode 100644 index 0000000000..e60512c526 --- /dev/null +++ b/src/core/lib/support/log_linux.c @@ -0,0 +1,105 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _POSIX_SOURCE +#define _POSIX_SOURCE +#endif + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <grpc/support/port_platform.h> + +#ifdef GPR_LINUX + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> +#include <grpc/support/time.h> +#include <linux/unistd.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <sys/syscall.h> +#include <time.h> +#include <unistd.h> + +static long gettid(void) { return syscall(__NR_gettid); } + +void gpr_log(const char *file, int line, gpr_log_severity severity, + const char *format, ...) { + char *message = NULL; + va_list args; + va_start(args, format); + if (vasprintf(&message, format, args) == -1) { + va_end(args); + return; + } + va_end(args); + gpr_log_message(file, line, severity, message); + free(message); +} + +void gpr_default_log(gpr_log_func_args *args) { + char *final_slash; + char *prefix; + const char *display_file; + char time_buffer[64]; + time_t timer; + gpr_timespec now = gpr_now(GPR_CLOCK_REALTIME); + struct tm tm; + + timer = (time_t)now.tv_sec; + final_slash = strrchr(args->file, '/'); + if (final_slash == NULL) + display_file = args->file; + else + display_file = final_slash + 1; + + if (!localtime_r(&timer, &tm)) { + strcpy(time_buffer, "error:localtime"); + } else if (0 == + strftime(time_buffer, sizeof(time_buffer), "%m%d %H:%M:%S", &tm)) { + strcpy(time_buffer, "error:strftime"); + } + + gpr_asprintf(&prefix, "%s%s.%09d %7tu %s:%d]", + gpr_log_severity_string(args->severity), time_buffer, + (int)(now.tv_nsec), gettid(), display_file, args->line); + + fprintf(stderr, "%-60s %s\n", prefix, args->message); + gpr_free(prefix); +} + +#endif diff --git a/src/core/lib/support/log_posix.c b/src/core/lib/support/log_posix.c new file mode 100644 index 0000000000..7429dd0a2c --- /dev/null +++ b/src/core/lib/support/log_posix.c @@ -0,0 +1,102 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#if defined(GPR_POSIX_LOG) + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/time.h> +#include <pthread.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdio.h> +#include <string.h> +#include <time.h> + +static intptr_t gettid(void) { return (intptr_t)pthread_self(); } + +void gpr_log(const char *file, int line, gpr_log_severity severity, + const char *format, ...) { + char buf[64]; + char *allocated = NULL; + char *message = NULL; + int ret; + va_list args; + va_start(args, format); + ret = vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + if (ret < 0) { + message = NULL; + } else if ((size_t)ret <= sizeof(buf) - 1) { + message = buf; + } else { + message = allocated = gpr_malloc((size_t)ret + 1); + va_start(args, format); + vsnprintf(message, (size_t)(ret + 1), format, args); + va_end(args); + } + gpr_log_message(file, line, severity, message); + gpr_free(allocated); +} + +void gpr_default_log(gpr_log_func_args *args) { + char *final_slash; + const char *display_file; + char time_buffer[64]; + time_t timer; + gpr_timespec now = gpr_now(GPR_CLOCK_REALTIME); + struct tm tm; + + timer = (time_t)now.tv_sec; + final_slash = strrchr(args->file, '/'); + if (final_slash == NULL) + display_file = args->file; + else + display_file = final_slash + 1; + + if (!localtime_r(&timer, &tm)) { + strcpy(time_buffer, "error:localtime"); + } else if (0 == + strftime(time_buffer, sizeof(time_buffer), "%m%d %H:%M:%S", &tm)) { + strcpy(time_buffer, "error:strftime"); + } + + fprintf(stderr, "%s%s.%09d %7tu %s:%d] %s\n", + gpr_log_severity_string(args->severity), time_buffer, + (int)(now.tv_nsec), gettid(), display_file, args->line, + args->message); +} + +#endif /* defined(GPR_POSIX_LOG) */ diff --git a/src/core/lib/support/log_win32.c b/src/core/lib/support/log_win32.c new file mode 100644 index 0000000000..cec99440a5 --- /dev/null +++ b/src/core/lib/support/log_win32.c @@ -0,0 +1,126 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WIN32 + +#include <stdarg.h> +#include <stdio.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/log_win32.h> +#include <grpc/support/string_util.h> +#include <grpc/support/time.h> + +#include "src/core/lib/support/string.h" +#include "src/core/lib/support/string_win32.h" + +void gpr_log(const char *file, int line, gpr_log_severity severity, + const char *format, ...) { + char *message = NULL; + va_list args; + int ret; + + /* Determine the length. */ + va_start(args, format); + ret = _vscprintf(format, args); + va_end(args); + if (ret < 0) { + message = NULL; + } else { + /* Allocate a new buffer, with space for the NUL terminator. */ + size_t strp_buflen = (size_t)ret + 1; + message = gpr_malloc(strp_buflen); + + /* Print to the buffer. */ + va_start(args, format); + ret = vsnprintf_s(message, strp_buflen, _TRUNCATE, format, args); + va_end(args); + if ((size_t)ret != strp_buflen - 1) { + /* This should never happen. */ + gpr_free(message); + message = NULL; + } + } + + gpr_log_message(file, line, severity, message); + gpr_free(message); +} + +/* Simple starter implementation */ +void gpr_default_log(gpr_log_func_args *args) { + char *final_slash; + const char *display_file; + char time_buffer[64]; + time_t timer; + gpr_timespec now = gpr_now(GPR_CLOCK_REALTIME); + struct tm tm; + + timer = (time_t)now.tv_sec; + final_slash = strrchr(args->file, '\\'); + if (final_slash == NULL) + display_file = args->file; + else + display_file = final_slash + 1; + + if (localtime_s(&tm, &timer)) { + strcpy(time_buffer, "error:localtime"); + } else if (0 == + strftime(time_buffer, sizeof(time_buffer), "%m%d %H:%M:%S", &tm)) { + strcpy(time_buffer, "error:strftime"); + } + + fprintf(stderr, "%s%s.%09u %5lu %s:%d] %s\n", + gpr_log_severity_string(args->severity), time_buffer, + (int)(now.tv_nsec), GetCurrentThreadId(), display_file, args->line, + args->message); + fflush(stderr); +} + +char *gpr_format_message(int messageid) { + LPTSTR tmessage; + char *message; + DWORD status = FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, (DWORD)messageid, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)(&tmessage), 0, NULL); + if (status == 0) return gpr_strdup("Unable to retrieve error string"); + message = gpr_tchar_to_char(tmessage); + LocalFree(tmessage); + return message; +} + +#endif /* GPR_WIN32 */ diff --git a/src/core/lib/support/murmur_hash.c b/src/core/lib/support/murmur_hash.c new file mode 100644 index 0000000000..97832f1510 --- /dev/null +++ b/src/core/lib/support/murmur_hash.c @@ -0,0 +1,96 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/support/murmur_hash.h" + +#define ROTL32(x, r) ((x) << (r)) | ((x) >> (32 - (r))) + +#define FMIX32(h) \ + (h) ^= (h) >> 16; \ + (h) *= 0x85ebca6b; \ + (h) ^= (h) >> 13; \ + (h) *= 0xc2b2ae35; \ + (h) ^= (h) >> 16; + +/* Block read - if your platform needs to do endian-swapping or can only + handle aligned reads, do the conversion here */ +#define GETBLOCK32(p, i) (p)[(i)] + +uint32_t gpr_murmur_hash3(const void *key, size_t len, uint32_t seed) { + const uint8_t *data = (const uint8_t *)key; + const size_t nblocks = len / 4; + int i; + + uint32_t h1 = seed; + uint32_t k1; + + const uint32_t c1 = 0xcc9e2d51; + const uint32_t c2 = 0x1b873593; + + const uint32_t *blocks = ((const uint32_t *)key) + nblocks; + const uint8_t *tail = (const uint8_t *)(data + nblocks * 4); + + /* body */ + for (i = -(int)nblocks; i; i++) { + k1 = GETBLOCK32(blocks, i); + + k1 *= c1; + k1 = ROTL32(k1, 15); + k1 *= c2; + + h1 ^= k1; + h1 = ROTL32(h1, 13); + h1 = h1 * 5 + 0xe6546b64; + } + + k1 = 0; + + /* tail */ + switch (len & 3) { + case 3: + k1 ^= ((uint32_t)tail[2]) << 16; + case 2: + k1 ^= ((uint32_t)tail[1]) << 8; + case 1: + k1 ^= tail[0]; + k1 *= c1; + k1 = ROTL32(k1, 15); + k1 *= c2; + h1 ^= k1; + }; + + /* finalization */ + h1 ^= (uint32_t)len; + FMIX32(h1); + return h1; +} diff --git a/src/core/lib/support/murmur_hash.h b/src/core/lib/support/murmur_hash.h new file mode 100644 index 0000000000..e54cdf2592 --- /dev/null +++ b/src/core/lib/support/murmur_hash.h @@ -0,0 +1,44 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SUPPORT_MURMUR_HASH_H +#define GRPC_CORE_LIB_SUPPORT_MURMUR_HASH_H + +#include <grpc/support/port_platform.h> + +#include <stddef.h> + +/* compute the hash of key (length len) */ +uint32_t gpr_murmur_hash3(const void *key, size_t len, uint32_t seed); + +#endif /* GRPC_CORE_LIB_SUPPORT_MURMUR_HASH_H */ diff --git a/src/core/lib/support/slice.c b/src/core/lib/support/slice.c new file mode 100644 index 0000000000..cf3953ce4e --- /dev/null +++ b/src/core/lib/support/slice.c @@ -0,0 +1,343 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/slice.h> + +#include <string.h> + +gpr_slice gpr_empty_slice(void) { + gpr_slice out; + out.refcount = 0; + out.data.inlined.length = 0; + return out; +} + +gpr_slice gpr_slice_ref(gpr_slice slice) { + if (slice.refcount) { + slice.refcount->ref(slice.refcount); + } + return slice; +} + +void gpr_slice_unref(gpr_slice slice) { + if (slice.refcount) { + slice.refcount->unref(slice.refcount); + } +} + +/* gpr_slice_from_static_string support structure - a refcount that does + nothing */ +static void noop_ref_or_unref(void *unused) {} + +static gpr_slice_refcount noop_refcount = {noop_ref_or_unref, + noop_ref_or_unref}; + +gpr_slice gpr_slice_from_static_string(const char *s) { + gpr_slice slice; + slice.refcount = &noop_refcount; + slice.data.refcounted.bytes = (uint8_t *)s; + slice.data.refcounted.length = strlen(s); + return slice; +} + +/* gpr_slice_new support structures - we create a refcount object extended + with the user provided data pointer & destroy function */ +typedef struct new_slice_refcount { + gpr_slice_refcount rc; + gpr_refcount refs; + void (*user_destroy)(void *); + void *user_data; +} new_slice_refcount; + +static void new_slice_ref(void *p) { + new_slice_refcount *r = p; + gpr_ref(&r->refs); +} + +static void new_slice_unref(void *p) { + new_slice_refcount *r = p; + if (gpr_unref(&r->refs)) { + r->user_destroy(r->user_data); + gpr_free(r); + } +} + +gpr_slice gpr_slice_new(void *p, size_t len, void (*destroy)(void *)) { + gpr_slice slice; + new_slice_refcount *rc = gpr_malloc(sizeof(new_slice_refcount)); + gpr_ref_init(&rc->refs, 1); + rc->rc.ref = new_slice_ref; + rc->rc.unref = new_slice_unref; + rc->user_destroy = destroy; + rc->user_data = p; + + slice.refcount = &rc->rc; + slice.data.refcounted.bytes = p; + slice.data.refcounted.length = len; + return slice; +} + +/* gpr_slice_new_with_len support structures - we create a refcount object + extended with the user provided data pointer & destroy function */ +typedef struct new_with_len_slice_refcount { + gpr_slice_refcount rc; + gpr_refcount refs; + void *user_data; + size_t user_length; + void (*user_destroy)(void *, size_t); +} new_with_len_slice_refcount; + +static void new_with_len_ref(void *p) { + new_with_len_slice_refcount *r = p; + gpr_ref(&r->refs); +} + +static void new_with_len_unref(void *p) { + new_with_len_slice_refcount *r = p; + if (gpr_unref(&r->refs)) { + r->user_destroy(r->user_data, r->user_length); + gpr_free(r); + } +} + +gpr_slice gpr_slice_new_with_len(void *p, size_t len, + void (*destroy)(void *, size_t)) { + gpr_slice slice; + new_with_len_slice_refcount *rc = + gpr_malloc(sizeof(new_with_len_slice_refcount)); + gpr_ref_init(&rc->refs, 1); + rc->rc.ref = new_with_len_ref; + rc->rc.unref = new_with_len_unref; + rc->user_destroy = destroy; + rc->user_data = p; + rc->user_length = len; + + slice.refcount = &rc->rc; + slice.data.refcounted.bytes = p; + slice.data.refcounted.length = len; + return slice; +} + +gpr_slice gpr_slice_from_copied_buffer(const char *source, size_t length) { + gpr_slice slice = gpr_slice_malloc(length); + memcpy(GPR_SLICE_START_PTR(slice), source, length); + return slice; +} + +gpr_slice gpr_slice_from_copied_string(const char *source) { + return gpr_slice_from_copied_buffer(source, strlen(source)); +} + +typedef struct { + gpr_slice_refcount base; + gpr_refcount refs; +} malloc_refcount; + +static void malloc_ref(void *p) { + malloc_refcount *r = p; + gpr_ref(&r->refs); +} + +static void malloc_unref(void *p) { + malloc_refcount *r = p; + if (gpr_unref(&r->refs)) { + gpr_free(r); + } +} + +gpr_slice gpr_slice_malloc(size_t length) { + gpr_slice slice; + + if (length > sizeof(slice.data.inlined.bytes)) { + /* Memory layout used by the slice created here: + + +-----------+----------------------------------------------------------+ + | refcount | bytes | + +-----------+----------------------------------------------------------+ + + refcount is a malloc_refcount + bytes is an array of bytes of the requested length + Both parts are placed in the same allocation returned from gpr_malloc */ + malloc_refcount *rc = gpr_malloc(sizeof(malloc_refcount) + length); + + /* Initial refcount on rc is 1 - and it's up to the caller to release + this reference. */ + gpr_ref_init(&rc->refs, 1); + + rc->base.ref = malloc_ref; + rc->base.unref = malloc_unref; + + /* Build up the slice to be returned. */ + /* The slices refcount points back to the allocated block. */ + slice.refcount = &rc->base; + /* The data bytes are placed immediately after the refcount struct */ + slice.data.refcounted.bytes = (uint8_t *)(rc + 1); + /* And the length of the block is set to the requested length */ + slice.data.refcounted.length = length; + } else { + /* small slice: just inline the data */ + slice.refcount = NULL; + slice.data.inlined.length = (uint8_t)length; + } + return slice; +} + +gpr_slice gpr_slice_sub_no_ref(gpr_slice source, size_t begin, size_t end) { + gpr_slice subset; + + GPR_ASSERT(end >= begin); + + if (source.refcount) { + /* Enforce preconditions */ + GPR_ASSERT(source.data.refcounted.length >= end); + + /* Build the result */ + subset.refcount = source.refcount; + /* Point into the source array */ + subset.data.refcounted.bytes = source.data.refcounted.bytes + begin; + subset.data.refcounted.length = end - begin; + } else { + /* Enforce preconditions */ + GPR_ASSERT(source.data.inlined.length >= end); + subset.refcount = NULL; + subset.data.inlined.length = (uint8_t)(end - begin); + memcpy(subset.data.inlined.bytes, source.data.inlined.bytes + begin, + end - begin); + } + return subset; +} + +gpr_slice gpr_slice_sub(gpr_slice source, size_t begin, size_t end) { + gpr_slice subset; + + if (end - begin <= sizeof(subset.data.inlined.bytes)) { + subset.refcount = NULL; + subset.data.inlined.length = (uint8_t)(end - begin); + memcpy(subset.data.inlined.bytes, GPR_SLICE_START_PTR(source) + begin, + end - begin); + } else { + subset = gpr_slice_sub_no_ref(source, begin, end); + /* Bump the refcount */ + subset.refcount->ref(subset.refcount); + } + return subset; +} + +gpr_slice gpr_slice_split_tail(gpr_slice *source, size_t split) { + gpr_slice tail; + + if (source->refcount == NULL) { + /* inlined data, copy it out */ + GPR_ASSERT(source->data.inlined.length >= split); + tail.refcount = NULL; + tail.data.inlined.length = (uint8_t)(source->data.inlined.length - split); + memcpy(tail.data.inlined.bytes, source->data.inlined.bytes + split, + tail.data.inlined.length); + source->data.inlined.length = (uint8_t)split; + } else { + size_t tail_length = source->data.refcounted.length - split; + GPR_ASSERT(source->data.refcounted.length >= split); + if (tail_length < sizeof(tail.data.inlined.bytes)) { + /* Copy out the bytes - it'll be cheaper than refcounting */ + tail.refcount = NULL; + tail.data.inlined.length = (uint8_t)tail_length; + memcpy(tail.data.inlined.bytes, source->data.refcounted.bytes + split, + tail_length); + } else { + /* Build the result */ + tail.refcount = source->refcount; + /* Bump the refcount */ + tail.refcount->ref(tail.refcount); + /* Point into the source array */ + tail.data.refcounted.bytes = source->data.refcounted.bytes + split; + tail.data.refcounted.length = tail_length; + } + source->data.refcounted.length = split; + } + + return tail; +} + +gpr_slice gpr_slice_split_head(gpr_slice *source, size_t split) { + gpr_slice head; + + if (source->refcount == NULL) { + GPR_ASSERT(source->data.inlined.length >= split); + + head.refcount = NULL; + head.data.inlined.length = (uint8_t)split; + memcpy(head.data.inlined.bytes, source->data.inlined.bytes, split); + source->data.inlined.length = + (uint8_t)(source->data.inlined.length - split); + memmove(source->data.inlined.bytes, source->data.inlined.bytes + split, + source->data.inlined.length); + } else if (split < sizeof(head.data.inlined.bytes)) { + GPR_ASSERT(source->data.refcounted.length >= split); + + head.refcount = NULL; + head.data.inlined.length = (uint8_t)split; + memcpy(head.data.inlined.bytes, source->data.refcounted.bytes, split); + source->data.refcounted.bytes += split; + source->data.refcounted.length -= split; + } else { + GPR_ASSERT(source->data.refcounted.length >= split); + + /* Build the result */ + head.refcount = source->refcount; + /* Bump the refcount */ + head.refcount->ref(head.refcount); + /* Point into the source array */ + head.data.refcounted.bytes = source->data.refcounted.bytes; + head.data.refcounted.length = split; + source->data.refcounted.bytes += split; + source->data.refcounted.length -= split; + } + + return head; +} + +int gpr_slice_cmp(gpr_slice a, gpr_slice b) { + int d = (int)(GPR_SLICE_LENGTH(a) - GPR_SLICE_LENGTH(b)); + if (d != 0) return d; + return memcmp(GPR_SLICE_START_PTR(a), GPR_SLICE_START_PTR(b), + GPR_SLICE_LENGTH(a)); +} + +int gpr_slice_str_cmp(gpr_slice a, const char *b) { + size_t b_length = strlen(b); + int d = (int)(GPR_SLICE_LENGTH(a) - b_length); + if (d != 0) return d; + return memcmp(GPR_SLICE_START_PTR(a), b, b_length); +} diff --git a/src/core/lib/support/slice_buffer.c b/src/core/lib/support/slice_buffer.c new file mode 100644 index 0000000000..563e659dd7 --- /dev/null +++ b/src/core/lib/support/slice_buffer.c @@ -0,0 +1,282 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> +#include <grpc/support/slice_buffer.h> + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/useful.h> + +/* grow a buffer; requires GRPC_SLICE_BUFFER_INLINE_ELEMENTS > 1 */ +#define GROW(x) (3 * (x) / 2) + +static void maybe_embiggen(gpr_slice_buffer *sb) { + if (sb->count == sb->capacity) { + sb->capacity = GROW(sb->capacity); + GPR_ASSERT(sb->capacity > sb->count); + if (sb->slices == sb->inlined) { + sb->slices = gpr_malloc(sb->capacity * sizeof(gpr_slice)); + memcpy(sb->slices, sb->inlined, sb->count * sizeof(gpr_slice)); + } else { + sb->slices = gpr_realloc(sb->slices, sb->capacity * sizeof(gpr_slice)); + } + } +} + +void gpr_slice_buffer_init(gpr_slice_buffer *sb) { + sb->count = 0; + sb->length = 0; + sb->capacity = GRPC_SLICE_BUFFER_INLINE_ELEMENTS; + sb->slices = sb->inlined; +} + +void gpr_slice_buffer_destroy(gpr_slice_buffer *sb) { + gpr_slice_buffer_reset_and_unref(sb); + if (sb->slices != sb->inlined) { + gpr_free(sb->slices); + } +} + +uint8_t *gpr_slice_buffer_tiny_add(gpr_slice_buffer *sb, size_t n) { + gpr_slice *back; + uint8_t *out; + + sb->length += n; + + if (sb->count == 0) goto add_new; + back = &sb->slices[sb->count - 1]; + if (back->refcount) goto add_new; + if ((back->data.inlined.length + n) > sizeof(back->data.inlined.bytes)) + goto add_new; + out = back->data.inlined.bytes + back->data.inlined.length; + back->data.inlined.length = (uint8_t)(back->data.inlined.length + n); + return out; + +add_new: + maybe_embiggen(sb); + back = &sb->slices[sb->count]; + sb->count++; + back->refcount = NULL; + back->data.inlined.length = (uint8_t)n; + return back->data.inlined.bytes; +} + +size_t gpr_slice_buffer_add_indexed(gpr_slice_buffer *sb, gpr_slice s) { + size_t out = sb->count; + maybe_embiggen(sb); + sb->slices[out] = s; + sb->length += GPR_SLICE_LENGTH(s); + sb->count = out + 1; + return out; +} + +void gpr_slice_buffer_add(gpr_slice_buffer *sb, gpr_slice s) { + size_t n = sb->count; + /* if both the last slice in the slice buffer and the slice being added + are inlined (that is, that they carry their data inside the slice data + structure), and the back slice is not full, then concatenate directly + into the back slice, preventing many small slices being passed into + writes */ + if (!s.refcount && n) { + gpr_slice *back = &sb->slices[n - 1]; + if (!back->refcount && back->data.inlined.length < GPR_SLICE_INLINED_SIZE) { + if (s.data.inlined.length + back->data.inlined.length <= + GPR_SLICE_INLINED_SIZE) { + memcpy(back->data.inlined.bytes + back->data.inlined.length, + s.data.inlined.bytes, s.data.inlined.length); + back->data.inlined.length = + (uint8_t)(back->data.inlined.length + s.data.inlined.length); + } else { + size_t cp1 = GPR_SLICE_INLINED_SIZE - back->data.inlined.length; + memcpy(back->data.inlined.bytes + back->data.inlined.length, + s.data.inlined.bytes, cp1); + back->data.inlined.length = GPR_SLICE_INLINED_SIZE; + maybe_embiggen(sb); + back = &sb->slices[n]; + sb->count = n + 1; + back->refcount = NULL; + back->data.inlined.length = (uint8_t)(s.data.inlined.length - cp1); + memcpy(back->data.inlined.bytes, s.data.inlined.bytes + cp1, + s.data.inlined.length - cp1); + } + sb->length += s.data.inlined.length; + return; /* early out */ + } + } + gpr_slice_buffer_add_indexed(sb, s); +} + +void gpr_slice_buffer_addn(gpr_slice_buffer *sb, gpr_slice *s, size_t n) { + size_t i; + for (i = 0; i < n; i++) { + gpr_slice_buffer_add(sb, s[i]); + } +} + +void gpr_slice_buffer_pop(gpr_slice_buffer *sb) { + if (sb->count != 0) { + size_t count = --sb->count; + sb->length -= GPR_SLICE_LENGTH(sb->slices[count]); + } +} + +void gpr_slice_buffer_reset_and_unref(gpr_slice_buffer *sb) { + size_t i; + + for (i = 0; i < sb->count; i++) { + gpr_slice_unref(sb->slices[i]); + } + + sb->count = 0; + sb->length = 0; +} + +void gpr_slice_buffer_swap(gpr_slice_buffer *a, gpr_slice_buffer *b) { + GPR_SWAP(size_t, a->count, b->count); + GPR_SWAP(size_t, a->capacity, b->capacity); + GPR_SWAP(size_t, a->length, b->length); + + if (a->slices == a->inlined) { + if (b->slices == b->inlined) { + /* swap contents of inlined buffer */ + gpr_slice temp[GRPC_SLICE_BUFFER_INLINE_ELEMENTS]; + memcpy(temp, a->slices, b->count * sizeof(gpr_slice)); + memcpy(a->slices, b->slices, a->count * sizeof(gpr_slice)); + memcpy(b->slices, temp, b->count * sizeof(gpr_slice)); + } else { + /* a is inlined, b is not - copy a inlined into b, fix pointers */ + a->slices = b->slices; + b->slices = b->inlined; + memcpy(b->slices, a->inlined, b->count * sizeof(gpr_slice)); + } + } else if (b->slices == b->inlined) { + /* b is inlined, a is not - copy b inlined int a, fix pointers */ + b->slices = a->slices; + a->slices = a->inlined; + memcpy(a->slices, b->inlined, a->count * sizeof(gpr_slice)); + } else { + /* no inlining: easy swap */ + GPR_SWAP(gpr_slice *, a->slices, b->slices); + } +} + +void gpr_slice_buffer_move_into(gpr_slice_buffer *src, gpr_slice_buffer *dst) { + /* anything to move? */ + if (src->count == 0) { + return; + } + /* anything in dst? */ + if (dst->count == 0) { + gpr_slice_buffer_swap(src, dst); + return; + } + /* both buffers have data - copy, and reset src */ + gpr_slice_buffer_addn(dst, src->slices, src->count); + src->count = 0; + src->length = 0; +} + +void gpr_slice_buffer_move_first(gpr_slice_buffer *src, size_t n, + gpr_slice_buffer *dst) { + size_t src_idx; + size_t output_len = dst->length + n; + size_t new_input_len = src->length - n; + GPR_ASSERT(src->length >= n); + if (src->length == n) { + gpr_slice_buffer_move_into(src, dst); + return; + } + src_idx = 0; + while (src_idx < src->capacity) { + gpr_slice slice = src->slices[src_idx]; + size_t slice_len = GPR_SLICE_LENGTH(slice); + if (n > slice_len) { + gpr_slice_buffer_add(dst, slice); + n -= slice_len; + src_idx++; + } else if (n == slice_len) { + gpr_slice_buffer_add(dst, slice); + src_idx++; + break; + } else { /* n < slice_len */ + src->slices[src_idx] = gpr_slice_split_tail(&slice, n); + GPR_ASSERT(GPR_SLICE_LENGTH(slice) == n); + GPR_ASSERT(GPR_SLICE_LENGTH(src->slices[src_idx]) == slice_len - n); + gpr_slice_buffer_add(dst, slice); + break; + } + } + GPR_ASSERT(dst->length == output_len); + memmove(src->slices, src->slices + src_idx, + sizeof(gpr_slice) * (src->count - src_idx)); + src->count -= src_idx; + src->length = new_input_len; + GPR_ASSERT(src->count > 0); +} + +void gpr_slice_buffer_trim_end(gpr_slice_buffer *sb, size_t n, + gpr_slice_buffer *garbage) { + GPR_ASSERT(n <= sb->length); + sb->length -= n; + for (;;) { + size_t idx = sb->count - 1; + gpr_slice slice = sb->slices[idx]; + size_t slice_len = GPR_SLICE_LENGTH(slice); + if (slice_len > n) { + sb->slices[idx] = gpr_slice_split_head(&slice, slice_len - n); + gpr_slice_buffer_add_indexed(garbage, slice); + return; + } else if (slice_len == n) { + gpr_slice_buffer_add_indexed(garbage, slice); + sb->count = idx; + return; + } else { + gpr_slice_buffer_add_indexed(garbage, slice); + n -= slice_len; + sb->count = idx; + } + } +} + +gpr_slice gpr_slice_buffer_take_first(gpr_slice_buffer *sb) { + gpr_slice slice; + GPR_ASSERT(sb->count > 0); + slice = sb->slices[0]; + memmove(&sb->slices[0], &sb->slices[1], (sb->count - 1) * sizeof(gpr_slice)); + sb->count--; + sb->length -= GPR_SLICE_LENGTH(slice); + return slice; +} diff --git a/src/core/lib/support/stack_lockfree.c b/src/core/lib/support/stack_lockfree.c new file mode 100644 index 0000000000..de80486132 --- /dev/null +++ b/src/core/lib/support/stack_lockfree.c @@ -0,0 +1,185 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/support/stack_lockfree.h" + +#include <stdlib.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/atm.h> +#include <grpc/support/log.h> +#include <grpc/support/port_platform.h> + +/* The lockfree node structure is a single architecture-level + word that allows for an atomic CAS to set it up. */ +struct lockfree_node_contents { + /* next thing to look at. Actual index for head, next index otherwise */ + uint16_t index; +#ifdef GPR_ARCH_64 + uint16_t pad; + uint32_t aba_ctr; +#else +#ifdef GPR_ARCH_32 + uint16_t aba_ctr; +#else +#error Unsupported bit width architecture +#endif +#endif +}; + +/* Use a union to make sure that these are in the same bits as an atm word */ +typedef union lockfree_node { + gpr_atm atm; + struct lockfree_node_contents contents; +} lockfree_node; + +/* make sure that entries aligned to 8-bytes */ +#define ENTRY_ALIGNMENT_BITS 3 +/* reserve this entry as invalid */ +#define INVALID_ENTRY_INDEX ((1 << 16) - 1) + +struct gpr_stack_lockfree { + lockfree_node *entries; + lockfree_node head; /* An atomic entry describing curr head */ + +#ifndef NDEBUG + /* Bitmap of pushed entries to check for double-push or pop */ + gpr_atm pushed[(INVALID_ENTRY_INDEX + 1) / (8 * sizeof(gpr_atm))]; +#endif +}; + +gpr_stack_lockfree *gpr_stack_lockfree_create(size_t entries) { + gpr_stack_lockfree *stack; + stack = gpr_malloc(sizeof(*stack)); + /* Since we only allocate 16 bits to represent an entry number, + * make sure that we are within the desired range */ + /* Reserve the highest entry number as a dummy */ + GPR_ASSERT(entries < INVALID_ENTRY_INDEX); + stack->entries = gpr_malloc_aligned(entries * sizeof(stack->entries[0]), + ENTRY_ALIGNMENT_BITS); + /* Clear out all entries */ + memset(stack->entries, 0, entries * sizeof(stack->entries[0])); + memset(&stack->head, 0, sizeof(stack->head)); +#ifndef NDEBUG + memset(&stack->pushed, 0, sizeof(stack->pushed)); +#endif + + GPR_ASSERT(sizeof(stack->entries->atm) == sizeof(stack->entries->contents)); + + /* Point the head at reserved dummy entry */ + stack->head.contents.index = INVALID_ENTRY_INDEX; +/* Fill in the pad and aba_ctr to avoid confusing memcheck tools */ +#ifdef GPR_ARCH_64 + stack->head.contents.pad = 0; +#endif + stack->head.contents.aba_ctr = 0; + return stack; +} + +void gpr_stack_lockfree_destroy(gpr_stack_lockfree *stack) { + gpr_free_aligned(stack->entries); + gpr_free(stack); +} + +int gpr_stack_lockfree_push(gpr_stack_lockfree *stack, int entry) { + lockfree_node head; + lockfree_node newhead; + lockfree_node curent; + lockfree_node newent; + + /* First fill in the entry's index and aba ctr for new head */ + newhead.contents.index = (uint16_t)entry; +#ifdef GPR_ARCH_64 + /* Fill in the pad to avoid confusing memcheck tools */ + newhead.contents.pad = 0; +#endif + + /* Also post-increment the aba_ctr */ + curent.atm = gpr_atm_no_barrier_load(&stack->entries[entry].atm); + newhead.contents.aba_ctr = ++curent.contents.aba_ctr; + gpr_atm_no_barrier_store(&stack->entries[entry].atm, curent.atm); + +#ifndef NDEBUG + /* Check for double push */ + { + int pushed_index = entry / (int)(8 * sizeof(gpr_atm)); + int pushed_bit = entry % (int)(8 * sizeof(gpr_atm)); + gpr_atm old_val; + + old_val = gpr_atm_no_barrier_fetch_add(&stack->pushed[pushed_index], + ((gpr_atm)1 << pushed_bit)); + GPR_ASSERT((old_val & (((gpr_atm)1) << pushed_bit)) == 0); + } +#endif + + do { + /* Atomically get the existing head value for use */ + head.atm = gpr_atm_no_barrier_load(&(stack->head.atm)); + /* Point to it */ + newent.atm = gpr_atm_no_barrier_load(&stack->entries[entry].atm); + newent.contents.index = head.contents.index; + gpr_atm_no_barrier_store(&stack->entries[entry].atm, newent.atm); + } while (!gpr_atm_rel_cas(&(stack->head.atm), head.atm, newhead.atm)); + /* Use rel_cas above to make sure that entry index is set properly */ + return head.contents.index == INVALID_ENTRY_INDEX; +} + +int gpr_stack_lockfree_pop(gpr_stack_lockfree *stack) { + lockfree_node head; + lockfree_node newhead; + + do { + head.atm = gpr_atm_acq_load(&(stack->head.atm)); + if (head.contents.index == INVALID_ENTRY_INDEX) { + return -1; + } + newhead.atm = + gpr_atm_no_barrier_load(&(stack->entries[head.contents.index].atm)); + + } while (!gpr_atm_no_barrier_cas(&(stack->head.atm), head.atm, newhead.atm)); +#ifndef NDEBUG + /* Check for valid pop */ + { + int pushed_index = head.contents.index / (8 * sizeof(gpr_atm)); + int pushed_bit = head.contents.index % (8 * sizeof(gpr_atm)); + gpr_atm old_val; + + old_val = gpr_atm_no_barrier_fetch_add(&stack->pushed[pushed_index], + -((gpr_atm)1 << pushed_bit)); + GPR_ASSERT((old_val & (((gpr_atm)1) << pushed_bit)) != 0); + } +#endif + + return head.contents.index; +} diff --git a/src/core/lib/support/stack_lockfree.h b/src/core/lib/support/stack_lockfree.h new file mode 100644 index 0000000000..a030a01d1f --- /dev/null +++ b/src/core/lib/support/stack_lockfree.h @@ -0,0 +1,53 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SUPPORT_STACK_LOCKFREE_H +#define GRPC_CORE_LIB_SUPPORT_STACK_LOCKFREE_H + +#include <stddef.h> + +typedef struct gpr_stack_lockfree gpr_stack_lockfree; + +/* This stack must specify the maximum number of entries to track. + The current implementation only allows up to 65534 entries */ +gpr_stack_lockfree *gpr_stack_lockfree_create(size_t entries); +void gpr_stack_lockfree_destroy(gpr_stack_lockfree *stack); + +/* Pass in a valid entry number for the next stack entry */ +/* Returns 1 if this is the first element on the stack, 0 otherwise */ +int gpr_stack_lockfree_push(gpr_stack_lockfree *, int entry); + +/* Returns -1 on empty or the actual entry number */ +int gpr_stack_lockfree_pop(gpr_stack_lockfree *stack); + +#endif /* GRPC_CORE_LIB_SUPPORT_STACK_LOCKFREE_H */ diff --git a/src/core/lib/support/string.c b/src/core/lib/support/string.c new file mode 100644 index 0000000000..365d861de3 --- /dev/null +++ b/src/core/lib/support/string.c @@ -0,0 +1,296 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/support/string.h" + +#include <ctype.h> +#include <stddef.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/port_platform.h> +#include <grpc/support/useful.h> + +char *gpr_strdup(const char *src) { + char *dst; + size_t len; + + if (!src) { + return NULL; + } + + len = strlen(src) + 1; + dst = gpr_malloc(len); + + memcpy(dst, src, len); + + return dst; +} + +typedef struct { + size_t capacity; + size_t length; + char *data; +} dump_out; + +static dump_out dump_out_create(void) { + dump_out r = {0, 0, NULL}; + return r; +} + +static void dump_out_append(dump_out *out, char c) { + if (out->length == out->capacity) { + out->capacity = GPR_MAX(8, 2 * out->capacity); + out->data = gpr_realloc(out->data, out->capacity); + } + out->data[out->length++] = c; +} + +static void hexdump(dump_out *out, const char *buf, size_t len) { + static const char hex[16] = "0123456789abcdef"; + + const uint8_t *const beg = (const uint8_t *)buf; + const uint8_t *const end = beg + len; + const uint8_t *cur; + + for (cur = beg; cur != end; ++cur) { + if (cur != beg) dump_out_append(out, ' '); + dump_out_append(out, hex[*cur >> 4]); + dump_out_append(out, hex[*cur & 0xf]); + } +} + +static void asciidump(dump_out *out, const char *buf, size_t len) { + const uint8_t *const beg = (const uint8_t *)buf; + const uint8_t *const end = beg + len; + const uint8_t *cur; + int out_was_empty = (out->length == 0); + if (!out_was_empty) { + dump_out_append(out, ' '); + dump_out_append(out, '\''); + } + for (cur = beg; cur != end; ++cur) { + dump_out_append(out, (char)(isprint(*cur) ? *(char *)cur : '.')); + } + if (!out_was_empty) { + dump_out_append(out, '\''); + } +} + +char *gpr_dump(const char *buf, size_t len, uint32_t flags) { + dump_out out = dump_out_create(); + if (flags & GPR_DUMP_HEX) { + hexdump(&out, buf, len); + } + if (flags & GPR_DUMP_ASCII) { + asciidump(&out, buf, len); + } + dump_out_append(&out, 0); + return out.data; +} + +char *gpr_dump_slice(gpr_slice s, uint32_t flags) { + return gpr_dump((const char *)GPR_SLICE_START_PTR(s), GPR_SLICE_LENGTH(s), + flags); +} + +int gpr_parse_bytes_to_uint32(const char *buf, size_t len, uint32_t *result) { + uint32_t out = 0; + uint32_t new; + size_t i; + + if (len == 0) return 0; /* must have some bytes */ + + for (i = 0; i < len; i++) { + if (buf[i] < '0' || buf[i] > '9') return 0; /* bad char */ + new = 10 * out + (uint32_t)(buf[i] - '0'); + if (new < out) return 0; /* overflow */ + out = new; + } + + *result = out; + return 1; +} + +void gpr_reverse_bytes(char *str, int len) { + char *p1, *p2; + for (p1 = str, p2 = str + len - 1; p2 > p1; ++p1, --p2) { + char temp = *p1; + *p1 = *p2; + *p2 = temp; + } +} + +int gpr_ltoa(long value, char *string) { + long sign; + int i = 0; + + if (value == 0) { + string[0] = '0'; + string[1] = 0; + return 1; + } + + sign = value < 0 ? -1 : 1; + while (value) { + string[i++] = (char)('0' + sign * (value % 10)); + value /= 10; + } + if (sign < 0) string[i++] = '-'; + gpr_reverse_bytes(string, i); + string[i] = 0; + return i; +} + +int int64_ttoa(int64_t value, char *string) { + int64_t sign; + int i = 0; + + if (value == 0) { + string[0] = '0'; + string[1] = 0; + return 1; + } + + sign = value < 0 ? -1 : 1; + while (value) { + string[i++] = (char)('0' + sign * (value % 10)); + value /= 10; + } + if (sign < 0) string[i++] = '-'; + gpr_reverse_bytes(string, i); + string[i] = 0; + return i; +} + +char *gpr_strjoin(const char **strs, size_t nstrs, size_t *final_length) { + return gpr_strjoin_sep(strs, nstrs, "", final_length); +} + +char *gpr_strjoin_sep(const char **strs, size_t nstrs, const char *sep, + size_t *final_length) { + const size_t sep_len = strlen(sep); + size_t out_length = 0; + size_t i; + char *out; + for (i = 0; i < nstrs; i++) { + out_length += strlen(strs[i]); + } + out_length += 1; /* null terminator */ + if (nstrs > 0) { + out_length += sep_len * (nstrs - 1); /* separators */ + } + out = gpr_malloc(out_length); + out_length = 0; + for (i = 0; i < nstrs; i++) { + const size_t slen = strlen(strs[i]); + if (i != 0) { + memcpy(out + out_length, sep, sep_len); + out_length += sep_len; + } + memcpy(out + out_length, strs[i], slen); + out_length += slen; + } + out[out_length] = 0; + if (final_length != NULL) { + *final_length = out_length; + } + return out; +} + +/** Finds the initial (\a begin) and final (\a end) offsets of the next + * substring from \a str + \a read_offset until the next \a sep or the end of \a + * str. + * + * Returns 1 and updates \a begin and \a end. Returns 0 otherwise. */ +static int slice_find_separator_offset(const gpr_slice str, const char *sep, + const size_t read_offset, size_t *begin, + size_t *end) { + size_t i; + const uint8_t *str_ptr = GPR_SLICE_START_PTR(str) + read_offset; + const size_t str_len = GPR_SLICE_LENGTH(str) - read_offset; + const size_t sep_len = strlen(sep); + if (str_len < sep_len) { + return 0; + } + + for (i = 0; i <= str_len - sep_len; i++) { + if (memcmp(str_ptr + i, sep, sep_len) == 0) { + *begin = read_offset; + *end = read_offset + i; + return 1; + } + } + return 0; +} + +void gpr_slice_split(gpr_slice str, const char *sep, gpr_slice_buffer *dst) { + const size_t sep_len = strlen(sep); + size_t begin, end; + + GPR_ASSERT(sep_len > 0); + + if (slice_find_separator_offset(str, sep, 0, &begin, &end) != 0) { + do { + gpr_slice_buffer_add_indexed(dst, gpr_slice_sub(str, begin, end)); + } while (slice_find_separator_offset(str, sep, end + sep_len, &begin, + &end) != 0); + gpr_slice_buffer_add_indexed( + dst, gpr_slice_sub(str, end + sep_len, GPR_SLICE_LENGTH(str))); + } else { /* no sep found, add whole input */ + gpr_slice_buffer_add_indexed(dst, gpr_slice_ref(str)); + } +} + +void gpr_strvec_init(gpr_strvec *sv) { memset(sv, 0, sizeof(*sv)); } + +void gpr_strvec_destroy(gpr_strvec *sv) { + size_t i; + for (i = 0; i < sv->count; i++) { + gpr_free(sv->strs[i]); + } + gpr_free(sv->strs); +} + +void gpr_strvec_add(gpr_strvec *sv, char *str) { + if (sv->count == sv->capacity) { + sv->capacity = GPR_MAX(sv->capacity + 8, sv->capacity * 2); + sv->strs = gpr_realloc(sv->strs, sizeof(char *) * sv->capacity); + } + sv->strs[sv->count++] = str; +} + +char *gpr_strvec_flatten(gpr_strvec *sv, size_t *final_length) { + return gpr_strjoin((const char **)sv->strs, sv->count, final_length); +} diff --git a/src/core/lib/support/string.h b/src/core/lib/support/string.h new file mode 100644 index 0000000000..68c02878e0 --- /dev/null +++ b/src/core/lib/support/string.h @@ -0,0 +1,121 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SUPPORT_STRING_H +#define GRPC_CORE_LIB_SUPPORT_STRING_H + +#include <stddef.h> + +#include <grpc/support/port_platform.h> +#include <grpc/support/slice.h> +#include <grpc/support/slice_buffer.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* String utility functions */ + +/* Flags for gpr_dump function. */ +#define GPR_DUMP_HEX 0x00000001 +#define GPR_DUMP_ASCII 0x00000002 + +/* Converts array buf, of length len, into a C string according to the flags. + Result should be freed with gpr_free() */ +char *gpr_dump(const char *buf, size_t len, uint32_t flags); + +/* Calls gpr_dump on a slice. */ +char *gpr_dump_slice(gpr_slice slice, uint32_t flags); + +/* Parses an array of bytes into an integer (base 10). Returns 1 on success, + 0 on failure. */ +int gpr_parse_bytes_to_uint32(const char *data, size_t length, + uint32_t *result); + +/* Minimum buffer size for calling ltoa */ +#define GPR_LTOA_MIN_BUFSIZE (3 * sizeof(long)) + +/* Convert a long to a string in base 10; returns the length of the + output string (or 0 on failure). + output must be at least GPR_LTOA_MIN_BUFSIZE bytes long. */ +int gpr_ltoa(long value, char *output); + +/* Minimum buffer size for calling int64toa */ +#define GPR_INT64TOA_MIN_BUFSIZE (3 * sizeof(int64_t)) + +/* Convert an int64 to a string in base 10; returns the length of the +output string (or 0 on failure). +output must be at least GPR_INT64TOA_MIN_BUFSIZE bytes long. +NOTE: This function ensures sufficient bit width even on Win x64, +where long is 32bit is size.*/ +int int64_ttoa(int64_t value, char *output); + +/* Reverse a run of bytes */ +void gpr_reverse_bytes(char *str, int len); + +/* Join a set of strings, returning the resulting string. + Total combined length (excluding null terminator) is returned in total_length + if it is non-null. */ +char *gpr_strjoin(const char **strs, size_t nstrs, size_t *total_length); + +/* Join a set of strings using a separator, returning the resulting string. + Total combined length (excluding null terminator) is returned in total_length + if it is non-null. */ +char *gpr_strjoin_sep(const char **strs, size_t nstrs, const char *sep, + size_t *total_length); + +/** Split \a str by the separator \a sep. Results are stored in \a dst, which + * should be a properly initialized instance. */ +void gpr_slice_split(gpr_slice str, const char *sep, gpr_slice_buffer *dst); + +/* A vector of strings... for building up a final string one piece at a time */ +typedef struct { + char **strs; + size_t count; + size_t capacity; +} gpr_strvec; + +/* Initialize/destroy */ +void gpr_strvec_init(gpr_strvec *strs); +void gpr_strvec_destroy(gpr_strvec *strs); +/* Add a string to a strvec, takes ownership of the string */ +void gpr_strvec_add(gpr_strvec *strs, char *add); +/* Return a joined string with all added substrings, optionally setting + total_length as per gpr_strjoin */ +char *gpr_strvec_flatten(gpr_strvec *strs, size_t *total_length); + +#ifdef __cplusplus +} +#endif + +#endif /* GRPC_CORE_LIB_SUPPORT_STRING_H */ diff --git a/src/core/lib/support/string_posix.c b/src/core/lib/support/string_posix.c new file mode 100644 index 0000000000..a73b3106a5 --- /dev/null +++ b/src/core/lib/support/string_posix.c @@ -0,0 +1,86 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_STRING + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +#include <grpc/support/alloc.h> + +int gpr_asprintf(char **strp, const char *format, ...) { + va_list args; + int ret; + char buf[64]; + size_t strp_buflen; + + /* Use a constant-sized buffer to determine the length. */ + va_start(args, format); + ret = vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + if (ret < 0) { + *strp = NULL; + return -1; + } + + /* Allocate a new buffer, with space for the NUL terminator. */ + strp_buflen = (size_t)ret + 1; + if ((*strp = gpr_malloc(strp_buflen)) == NULL) { + /* This shouldn't happen, because gpr_malloc() calls abort(). */ + return -1; + } + + /* Return early if we have all the bytes. */ + if (strp_buflen <= sizeof(buf)) { + memcpy(*strp, buf, strp_buflen); + return ret; + } + + /* Try again using the larger buffer. */ + va_start(args, format); + ret = vsnprintf(*strp, strp_buflen, format, args); + va_end(args); + if ((size_t)ret == strp_buflen - 1) { + return ret; + } + + /* This should never happen. */ + gpr_free(*strp); + *strp = NULL; + return -1; +} + +#endif /* GPR_POSIX_STRING */ diff --git a/src/core/lib/support/string_win32.c b/src/core/lib/support/string_win32.c new file mode 100644 index 0000000000..16b7e37f2a --- /dev/null +++ b/src/core/lib/support/string_win32.c @@ -0,0 +1,109 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* Posix code for gpr snprintf support. */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WIN32 + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +#include <grpc/support/alloc.h> + +#include "src/core/lib/support/string.h" + +int gpr_asprintf(char **strp, const char *format, ...) { + va_list args; + int ret; + size_t strp_buflen; + + /* Determine the length. */ + va_start(args, format); + ret = _vscprintf(format, args); + va_end(args); + if (ret < 0) { + *strp = NULL; + return -1; + } + + /* Allocate a new buffer, with space for the NUL terminator. */ + strp_buflen = (size_t)ret + 1; + if ((*strp = gpr_malloc(strp_buflen)) == NULL) { + /* This shouldn't happen, because gpr_malloc() calls abort(). */ + return -1; + } + + /* Print to the buffer. */ + va_start(args, format); + ret = vsnprintf_s(*strp, strp_buflen, _TRUNCATE, format, args); + va_end(args); + if ((size_t)ret == strp_buflen - 1) { + return ret; + } + + /* This should never happen. */ + gpr_free(*strp); + *strp = NULL; + return -1; +} + +#if defined UNICODE || defined _UNICODE +LPTSTR +gpr_char_to_tchar(LPCSTR input) { + LPTSTR ret; + int needed = MultiByteToWideChar(CP_UTF8, 0, input, -1, NULL, 0); + if (needed <= 0) return NULL; + ret = gpr_malloc((unsigned)needed * sizeof(TCHAR)); + MultiByteToWideChar(CP_UTF8, 0, input, -1, ret, needed); + return ret; +} + +LPSTR +gpr_tchar_to_char(LPCTSTR input) { + LPSTR ret; + int needed = WideCharToMultiByte(CP_UTF8, 0, input, -1, NULL, 0, NULL, NULL); + if (needed <= 0) return NULL; + ret = gpr_malloc((unsigned)needed); + WideCharToMultiByte(CP_UTF8, 0, input, -1, ret, needed, NULL, NULL); + return ret; +} +#else +char *gpr_tchar_to_char(LPTSTR input) { return gpr_strdup(input); } + +char *gpr_char_to_tchar(LPTSTR input) { return gpr_strdup(input); } +#endif + +#endif /* GPR_WIN32 */ diff --git a/src/core/lib/support/string_win32.h b/src/core/lib/support/string_win32.h new file mode 100644 index 0000000000..f47d567715 --- /dev/null +++ b/src/core/lib/support/string_win32.h @@ -0,0 +1,47 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SUPPORT_STRING_WIN32_H +#define GRPC_CORE_LIB_SUPPORT_STRING_WIN32_H + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WIN32 + +/* These allocate new strings using gpr_malloc to convert from and to utf-8. */ +LPTSTR gpr_char_to_tchar(LPCSTR input); +LPSTR gpr_tchar_to_char(LPCTSTR input); + +#endif /* GPR_WIN32 */ + +#endif /* GRPC_CORE_LIB_SUPPORT_STRING_WIN32_H */ diff --git a/src/core/lib/support/subprocess_posix.c b/src/core/lib/support/subprocess_posix.c new file mode 100644 index 0000000000..662e7dd999 --- /dev/null +++ b/src/core/lib/support/subprocess_posix.c @@ -0,0 +1,112 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_SUBPROCESS + +#include <grpc/support/subprocess.h> + +#include <assert.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +struct gpr_subprocess { + int pid; + int joined; +}; + +const char *gpr_subprocess_binary_extension() { return ""; } + +gpr_subprocess *gpr_subprocess_create(int argc, const char **argv) { + gpr_subprocess *r; + int pid; + char **exec_args; + + pid = fork(); + if (pid == -1) { + return NULL; + } else if (pid == 0) { + exec_args = gpr_malloc(((size_t)argc + 1) * sizeof(char *)); + memcpy(exec_args, argv, (size_t)argc * sizeof(char *)); + exec_args[argc] = NULL; + execv(exec_args[0], exec_args); + /* if we reach here, an error has occurred */ + gpr_log(GPR_ERROR, "execv '%s' failed: %s", exec_args[0], strerror(errno)); + _exit(1); + return NULL; + } else { + r = gpr_malloc(sizeof(gpr_subprocess)); + memset(r, 0, sizeof(*r)); + r->pid = pid; + return r; + } +} + +void gpr_subprocess_destroy(gpr_subprocess *p) { + if (!p->joined) { + kill(p->pid, SIGKILL); + gpr_subprocess_join(p); + } + gpr_free(p); +} + +int gpr_subprocess_join(gpr_subprocess *p) { + int status; +retry: + if (waitpid(p->pid, &status, 0) == -1) { + if (errno == EINTR) { + goto retry; + } + gpr_log(GPR_ERROR, "waitpid failed: %s", strerror(errno)); + return -1; + } + return status; +} + +void gpr_subprocess_interrupt(gpr_subprocess *p) { + if (!p->joined) { + kill(p->pid, SIGINT); + } +} + +#endif /* GPR_POSIX_SUBPROCESS */ diff --git a/src/core/lib/support/subprocess_windows.c b/src/core/lib/support/subprocess_windows.c new file mode 100644 index 0000000000..264306f1bd --- /dev/null +++ b/src/core/lib/support/subprocess_windows.c @@ -0,0 +1,141 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WINDOWS_SUBPROCESS + +#include <string.h> +#include <tchar.h> +#include <windows.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/subprocess.h> +#include "src/core/lib/support/string.h" +#include "src/core/lib/support/string_win32.h" + +struct gpr_subprocess { + PROCESS_INFORMATION pi; + int joined; + int interrupted; +}; + +const char *gpr_subprocess_binary_extension() { return ".exe"; } + +gpr_subprocess *gpr_subprocess_create(int argc, const char **argv) { + gpr_subprocess *r; + + STARTUPINFO si; + PROCESS_INFORMATION pi; + + char *args = gpr_strjoin_sep(argv, (size_t)argc, " ", NULL); + TCHAR *args_tchar; + + args_tchar = gpr_char_to_tchar(args); + gpr_free(args); + + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + memset(&pi, 0, sizeof(pi)); + + if (!CreateProcess(NULL, args_tchar, NULL, NULL, FALSE, + CREATE_NEW_PROCESS_GROUP, NULL, NULL, &si, &pi)) { + gpr_free(args_tchar); + return NULL; + } + gpr_free(args_tchar); + + r = gpr_malloc(sizeof(gpr_subprocess)); + memset(r, 0, sizeof(*r)); + r->pi = pi; + return r; +} + +void gpr_subprocess_destroy(gpr_subprocess *p) { + if (p) { + if (!p->joined) { + gpr_subprocess_interrupt(p); + gpr_subprocess_join(p); + } + if (p->pi.hProcess) { + CloseHandle(p->pi.hProcess); + } + if (p->pi.hThread) { + CloseHandle(p->pi.hThread); + } + gpr_free(p); + } +} + +int gpr_subprocess_join(gpr_subprocess *p) { + DWORD dwExitCode; + if (GetExitCodeProcess(p->pi.hProcess, &dwExitCode)) { + if (dwExitCode == STILL_ACTIVE) { + if (WaitForSingleObject(p->pi.hProcess, INFINITE) == WAIT_OBJECT_0) { + p->joined = 1; + goto getExitCode; + } + return -1; // failed to join + } else { + goto getExitCode; + } + } else { + return -1; // failed to get exit code + } + +getExitCode: + if (p->interrupted) { + return 0; + } + if (GetExitCodeProcess(p->pi.hProcess, &dwExitCode)) { + return (int)dwExitCode; + } else { + return -1; // failed to get exit code + } +} + +void gpr_subprocess_interrupt(gpr_subprocess *p) { + DWORD dwExitCode; + if (GetExitCodeProcess(p->pi.hProcess, &dwExitCode)) { + if (dwExitCode == STILL_ACTIVE) { + gpr_log(GPR_INFO, "sending ctrl-break"); + GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, p->pi.dwProcessId); + p->joined = 1; + p->interrupted = 1; + } + } + return; +} + +#endif /* GPR_WINDOWS_SUBPROCESS */ diff --git a/src/core/lib/support/sync.c b/src/core/lib/support/sync.c new file mode 100644 index 0000000000..800cf20287 --- /dev/null +++ b/src/core/lib/support/sync.c @@ -0,0 +1,127 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* Generic implementation of synchronization primitives. */ + +#include <grpc/support/atm.h> +#include <grpc/support/log.h> +#include <grpc/support/sync.h> + +/* Number of mutexes to allocate for events, to avoid lock contention. + Should be a prime. */ +enum { event_sync_partitions = 31 }; + +/* Events are partitioned by address to avoid lock contention. */ +static struct sync_array_s { + gpr_mu mu; + gpr_cv cv; +} sync_array[event_sync_partitions]; + +/* This routine is executed once on first use, via event_once */ +static gpr_once event_once = GPR_ONCE_INIT; +static void event_initialize(void) { + int i; + for (i = 0; i != event_sync_partitions; i++) { + gpr_mu_init(&sync_array[i].mu); + gpr_cv_init(&sync_array[i].cv); + } +} + +/* Hash ev into an element of sync_array[]. */ +static struct sync_array_s *hash(gpr_event *ev) { + return &sync_array[((uintptr_t)ev) % event_sync_partitions]; +} + +void gpr_event_init(gpr_event *ev) { + gpr_once_init(&event_once, &event_initialize); + ev->state = 0; +} + +void gpr_event_set(gpr_event *ev, void *value) { + struct sync_array_s *s = hash(ev); + gpr_mu_lock(&s->mu); + GPR_ASSERT(gpr_atm_acq_load(&ev->state) == 0); + gpr_atm_rel_store(&ev->state, (gpr_atm)value); + gpr_cv_broadcast(&s->cv); + gpr_mu_unlock(&s->mu); + GPR_ASSERT(value != NULL); +} + +void *gpr_event_get(gpr_event *ev) { + return (void *)gpr_atm_acq_load(&ev->state); +} + +void *gpr_event_wait(gpr_event *ev, gpr_timespec abs_deadline) { + void *result = (void *)gpr_atm_acq_load(&ev->state); + if (result == NULL) { + struct sync_array_s *s = hash(ev); + gpr_mu_lock(&s->mu); + do { + result = (void *)gpr_atm_acq_load(&ev->state); + } while (result == NULL && !gpr_cv_wait(&s->cv, &s->mu, abs_deadline)); + gpr_mu_unlock(&s->mu); + } + return result; +} + +void gpr_ref_init(gpr_refcount *r, int n) { gpr_atm_rel_store(&r->count, n); } + +void gpr_ref(gpr_refcount *r) { gpr_atm_no_barrier_fetch_add(&r->count, 1); } + +void gpr_ref_non_zero(gpr_refcount *r) { + gpr_atm prior = gpr_atm_no_barrier_fetch_add(&r->count, 1); + GPR_ASSERT(prior > 0); +} + +void gpr_refn(gpr_refcount *r, int n) { + gpr_atm_no_barrier_fetch_add(&r->count, n); +} + +int gpr_unref(gpr_refcount *r) { + gpr_atm prior = gpr_atm_full_fetch_add(&r->count, -1); + GPR_ASSERT(prior > 0); + return prior == 1; +} + +void gpr_stats_init(gpr_stats_counter *c, intptr_t n) { + gpr_atm_rel_store(&c->value, n); +} + +void gpr_stats_inc(gpr_stats_counter *c, intptr_t inc) { + gpr_atm_no_barrier_fetch_add(&c->value, inc); +} + +intptr_t gpr_stats_read(const gpr_stats_counter *c) { + /* don't need acquire-load, but we have no no-barrier load yet */ + return gpr_atm_acq_load(&c->value); +} diff --git a/src/core/lib/support/sync_posix.c b/src/core/lib/support/sync_posix.c new file mode 100644 index 0000000000..a5e59db8c7 --- /dev/null +++ b/src/core/lib/support/sync_posix.c @@ -0,0 +1,104 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_SYNC + +#include <errno.h> +#include <grpc/support/log.h> +#include <grpc/support/sync.h> +#include <grpc/support/time.h> +#include <time.h> +#include "src/core/lib/profiling/timers.h" + +void gpr_mu_init(gpr_mu* mu) { GPR_ASSERT(pthread_mutex_init(mu, NULL) == 0); } + +void gpr_mu_destroy(gpr_mu* mu) { GPR_ASSERT(pthread_mutex_destroy(mu) == 0); } + +void gpr_mu_lock(gpr_mu* mu) { + GPR_TIMER_BEGIN("gpr_mu_lock", 0); + GPR_ASSERT(pthread_mutex_lock(mu) == 0); + GPR_TIMER_END("gpr_mu_lock", 0); +} + +void gpr_mu_unlock(gpr_mu* mu) { + GPR_TIMER_BEGIN("gpr_mu_unlock", 0); + GPR_ASSERT(pthread_mutex_unlock(mu) == 0); + GPR_TIMER_END("gpr_mu_unlock", 0); +} + +int gpr_mu_trylock(gpr_mu* mu) { + int err; + GPR_TIMER_BEGIN("gpr_mu_trylock", 0); + err = pthread_mutex_trylock(mu); + GPR_ASSERT(err == 0 || err == EBUSY); + GPR_TIMER_END("gpr_mu_trylock", 0); + return err == 0; +} + +/*----------------------------------------*/ + +void gpr_cv_init(gpr_cv* cv) { GPR_ASSERT(pthread_cond_init(cv, NULL) == 0); } + +void gpr_cv_destroy(gpr_cv* cv) { GPR_ASSERT(pthread_cond_destroy(cv) == 0); } + +int gpr_cv_wait(gpr_cv* cv, gpr_mu* mu, gpr_timespec abs_deadline) { + int err = 0; + if (gpr_time_cmp(abs_deadline, gpr_inf_future(abs_deadline.clock_type)) == + 0) { + err = pthread_cond_wait(cv, mu); + } else { + struct timespec abs_deadline_ts; + abs_deadline = gpr_convert_clock_type(abs_deadline, GPR_CLOCK_REALTIME); + abs_deadline_ts.tv_sec = (time_t)abs_deadline.tv_sec; + abs_deadline_ts.tv_nsec = abs_deadline.tv_nsec; + err = pthread_cond_timedwait(cv, mu, &abs_deadline_ts); + } + GPR_ASSERT(err == 0 || err == ETIMEDOUT || err == EAGAIN); + return err == ETIMEDOUT; +} + +void gpr_cv_signal(gpr_cv* cv) { GPR_ASSERT(pthread_cond_signal(cv) == 0); } + +void gpr_cv_broadcast(gpr_cv* cv) { + GPR_ASSERT(pthread_cond_broadcast(cv) == 0); +} + +/*----------------------------------------*/ + +void gpr_once_init(gpr_once* once, void (*init_function)(void)) { + GPR_ASSERT(pthread_once(once, init_function) == 0); +} + +#endif /* GRP_POSIX_SYNC */ diff --git a/src/core/lib/support/sync_win32.c b/src/core/lib/support/sync_win32.c new file mode 100644 index 0000000000..41998ebcb6 --- /dev/null +++ b/src/core/lib/support/sync_win32.c @@ -0,0 +1,133 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* Win32 code for gpr synchronization support. */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WIN32 + +#include <grpc/support/log.h> +#include <grpc/support/sync.h> +#include <grpc/support/time.h> + +void gpr_mu_init(gpr_mu *mu) { + InitializeCriticalSection(&mu->cs); + mu->locked = 0; +} + +void gpr_mu_destroy(gpr_mu *mu) { DeleteCriticalSection(&mu->cs); } + +void gpr_mu_lock(gpr_mu *mu) { + EnterCriticalSection(&mu->cs); + GPR_ASSERT(!mu->locked); + mu->locked = 1; +} + +void gpr_mu_unlock(gpr_mu *mu) { + mu->locked = 0; + LeaveCriticalSection(&mu->cs); +} + +int gpr_mu_trylock(gpr_mu *mu) { + int result = TryEnterCriticalSection(&mu->cs); + if (result) { + if (mu->locked) { /* This thread already holds the lock. */ + LeaveCriticalSection(&mu->cs); /* Decrement lock count. */ + result = 0; /* Indicate failure */ + } + mu->locked = 1; + } + return result; +} + +/*----------------------------------------*/ + +void gpr_cv_init(gpr_cv *cv) { InitializeConditionVariable(cv); } + +void gpr_cv_destroy(gpr_cv *cv) { + /* Condition variables don't need destruction in Win32. */ +} + +int gpr_cv_wait(gpr_cv *cv, gpr_mu *mu, gpr_timespec abs_deadline) { + int timeout = 0; + DWORD timeout_max_ms; + mu->locked = 0; + if (gpr_time_cmp(abs_deadline, gpr_inf_future(abs_deadline.clock_type)) == + 0) { + SleepConditionVariableCS(cv, &mu->cs, INFINITE); + } else { + abs_deadline = gpr_convert_clock_type(abs_deadline, GPR_CLOCK_REALTIME); + gpr_timespec now = gpr_now(abs_deadline.clock_type); + int64_t now_ms = (int64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000; + int64_t deadline_ms = + (int64_t)abs_deadline.tv_sec * 1000 + abs_deadline.tv_nsec / 1000000; + if (now_ms >= deadline_ms) { + timeout = 1; + } else { + if ((deadline_ms - now_ms) >= INFINITE) { + timeout_max_ms = INFINITE - 1; + } else { + timeout_max_ms = (DWORD)(deadline_ms - now_ms); + } + timeout = (SleepConditionVariableCS(cv, &mu->cs, timeout_max_ms) == 0 && + GetLastError() == ERROR_TIMEOUT); + } + } + mu->locked = 1; + return timeout; +} + +void gpr_cv_signal(gpr_cv *cv) { WakeConditionVariable(cv); } + +void gpr_cv_broadcast(gpr_cv *cv) { WakeAllConditionVariable(cv); } + +/*----------------------------------------*/ + +static void *dummy; +struct run_once_func_arg { + void (*init_function)(void); +}; +static BOOL CALLBACK run_once_func(gpr_once *once, void *v, void **pv) { + struct run_once_func_arg *arg = v; + (*arg->init_function)(); + return 1; +} + +void gpr_once_init(gpr_once *once, void (*init_function)(void)) { + struct run_once_func_arg arg; + arg.init_function = init_function; + InitOnceExecuteOnce(once, run_once_func, &arg, &dummy); +} + +#endif /* GPR_WIN32 */ diff --git a/src/core/lib/support/thd.c b/src/core/lib/support/thd.c new file mode 100644 index 0000000000..d59aace38d --- /dev/null +++ b/src/core/lib/support/thd.c @@ -0,0 +1,64 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* Posix implementation for gpr threads. */ + +#include <memory.h> + +#include <grpc/support/thd.h> + +enum { GPR_THD_JOINABLE = 1 }; + +gpr_thd_options gpr_thd_options_default(void) { + gpr_thd_options options; + memset(&options, 0, sizeof(options)); + return options; +} + +void gpr_thd_options_set_detached(gpr_thd_options* options) { + options->flags &= ~GPR_THD_JOINABLE; +} + +void gpr_thd_options_set_joinable(gpr_thd_options* options) { + options->flags |= GPR_THD_JOINABLE; +} + +int gpr_thd_options_is_detached(const gpr_thd_options* options) { + if (!options) return 1; + return (options->flags & GPR_THD_JOINABLE) == 0; +} + +int gpr_thd_options_is_joinable(const gpr_thd_options* options) { + if (!options) return 0; + return (options->flags & GPR_THD_JOINABLE) == GPR_THD_JOINABLE; +} diff --git a/src/core/lib/support/thd_internal.h b/src/core/lib/support/thd_internal.h new file mode 100644 index 0000000000..f269a3249e --- /dev/null +++ b/src/core/lib/support/thd_internal.h @@ -0,0 +1,39 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SUPPORT_THD_INTERNAL_H +#define GRPC_CORE_LIB_SUPPORT_THD_INTERNAL_H + +/* Internal interfaces between modules within the gpr support library. */ + +#endif /* GRPC_CORE_LIB_SUPPORT_THD_INTERNAL_H */ diff --git a/src/core/lib/support/thd_posix.c b/src/core/lib/support/thd_posix.c new file mode 100644 index 0000000000..4d874d3656 --- /dev/null +++ b/src/core/lib/support/thd_posix.c @@ -0,0 +1,94 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* Posix implementation for gpr threads. */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_SYNC + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/thd.h> +#include <grpc/support/useful.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> + +struct thd_arg { + void (*body)(void *arg); /* body of a thread */ + void *arg; /* argument to a thread */ +}; + +/* Body of every thread started via gpr_thd_new. */ +static void *thread_body(void *v) { + struct thd_arg a = *(struct thd_arg *)v; + free(v); + (*a.body)(a.arg); + return NULL; +} + +int gpr_thd_new(gpr_thd_id *t, void (*thd_body)(void *arg), void *arg, + const gpr_thd_options *options) { + int thread_started; + pthread_attr_t attr; + pthread_t p; + /* don't use gpr_malloc as we may cause an infinite recursion with + * the profiling code */ + struct thd_arg *a = malloc(sizeof(*a)); + GPR_ASSERT(a != NULL); + a->body = thd_body; + a->arg = arg; + + GPR_ASSERT(pthread_attr_init(&attr) == 0); + if (gpr_thd_options_is_detached(options)) { + GPR_ASSERT(pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) == + 0); + } else { + GPR_ASSERT(pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE) == + 0); + } + thread_started = (pthread_create(&p, &attr, &thread_body, a) == 0); + GPR_ASSERT(pthread_attr_destroy(&attr) == 0); + if (!thread_started) { + free(a); + } + *t = (gpr_thd_id)p; + return thread_started; +} + +gpr_thd_id gpr_thd_currentid(void) { return (gpr_thd_id)pthread_self(); } + +void gpr_thd_join(gpr_thd_id t) { pthread_join((pthread_t)t, NULL); } + +#endif /* GPR_POSIX_SYNC */ diff --git a/src/core/lib/support/thd_win32.c b/src/core/lib/support/thd_win32.c new file mode 100644 index 0000000000..630eb7f625 --- /dev/null +++ b/src/core/lib/support/thd_win32.c @@ -0,0 +1,117 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* Windows implementation for gpr threads. */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WIN32 + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/thd.h> +#include <string.h> + +#if defined(_MSC_VER) +#define thread_local __declspec(thread) +#elif defined(__GNUC__) +#define thread_local __thread +#else +#error "Unknown compiler - please file a bug report" +#endif + +struct thd_info { + void (*body)(void *arg); /* body of a thread */ + void *arg; /* argument to a thread */ + HANDLE join_event; /* if joinable, the join event */ + int joinable; /* true if not detached */ +}; + +static thread_local struct thd_info *g_thd_info; + +/* Destroys a thread info */ +static void destroy_thread(struct thd_info *t) { + if (t->joinable) CloseHandle(t->join_event); + gpr_free(t); +} + +/* Body of every thread started via gpr_thd_new. */ +static DWORD WINAPI thread_body(void *v) { + g_thd_info = (struct thd_info *)v; + g_thd_info->body(g_thd_info->arg); + if (g_thd_info->joinable) { + BOOL ret = SetEvent(g_thd_info->join_event); + GPR_ASSERT(ret); + } else { + destroy_thread(g_thd_info); + } + return 0; +} + +int gpr_thd_new(gpr_thd_id *t, void (*thd_body)(void *arg), void *arg, + const gpr_thd_options *options) { + HANDLE handle; + struct thd_info *info = gpr_malloc(sizeof(*info)); + info->body = thd_body; + info->arg = arg; + *t = 0; + if (gpr_thd_options_is_joinable(options)) { + info->joinable = 1; + info->join_event = CreateEvent(NULL, FALSE, FALSE, NULL); + if (info->join_event == NULL) { + gpr_free(info); + return 0; + } + } else { + info->joinable = 0; + } + handle = CreateThread(NULL, 64 * 1024, thread_body, info, 0, NULL); + if (handle == NULL) { + destroy_thread(info); + } else { + *t = (gpr_thd_id)info; + CloseHandle(handle); + } + return handle != NULL; +} + +gpr_thd_id gpr_thd_currentid(void) { return (gpr_thd_id)g_thd_info; } + +void gpr_thd_join(gpr_thd_id t) { + struct thd_info *info = (struct thd_info *)t; + DWORD ret = WaitForSingleObject(info->join_event, INFINITE); + GPR_ASSERT(ret == WAIT_OBJECT_0); + destroy_thread(info); +} + +#endif /* GPR_WIN32 */ diff --git a/src/core/lib/support/time.c b/src/core/lib/support/time.c new file mode 100644 index 0000000000..0e2c8fcf1a --- /dev/null +++ b/src/core/lib/support/time.c @@ -0,0 +1,304 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* Generic implementation of time calls. */ + +#include <grpc/support/log.h> +#include <grpc/support/time.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> + +int gpr_time_cmp(gpr_timespec a, gpr_timespec b) { + int cmp = (a.tv_sec > b.tv_sec) - (a.tv_sec < b.tv_sec); + GPR_ASSERT(a.clock_type == b.clock_type); + if (cmp == 0) { + cmp = (a.tv_nsec > b.tv_nsec) - (a.tv_nsec < b.tv_nsec); + } + return cmp; +} + +gpr_timespec gpr_time_min(gpr_timespec a, gpr_timespec b) { + return gpr_time_cmp(a, b) < 0 ? a : b; +} + +gpr_timespec gpr_time_max(gpr_timespec a, gpr_timespec b) { + return gpr_time_cmp(a, b) > 0 ? a : b; +} + +gpr_timespec gpr_time_0(gpr_clock_type type) { + gpr_timespec out; + out.tv_sec = 0; + out.tv_nsec = 0; + out.clock_type = type; + return out; +} + +gpr_timespec gpr_inf_future(gpr_clock_type type) { + gpr_timespec out; + out.tv_sec = INT64_MAX; + out.tv_nsec = 0; + out.clock_type = type; + return out; +} + +gpr_timespec gpr_inf_past(gpr_clock_type type) { + gpr_timespec out; + out.tv_sec = INT64_MIN; + out.tv_nsec = 0; + out.clock_type = type; + return out; +} + +/* TODO(ctiller): consider merging _nanos, _micros, _millis into a single + function for maintainability. Similarly for _seconds, _minutes, and _hours */ + +gpr_timespec gpr_time_from_nanos(int64_t ns, gpr_clock_type type) { + gpr_timespec result; + result.clock_type = type; + if (ns == INT64_MAX) { + result = gpr_inf_future(type); + } else if (ns == INT64_MIN) { + result = gpr_inf_past(type); + } else if (ns >= 0) { + result.tv_sec = ns / GPR_NS_PER_SEC; + result.tv_nsec = (int32_t)(ns - result.tv_sec * GPR_NS_PER_SEC); + } else { + /* Calculation carefully formulated to avoid any possible under/overflow. */ + result.tv_sec = (-(999999999 - (ns + GPR_NS_PER_SEC)) / GPR_NS_PER_SEC) - 1; + result.tv_nsec = (int32_t)(ns - result.tv_sec * GPR_NS_PER_SEC); + } + return result; +} + +gpr_timespec gpr_time_from_micros(int64_t us, gpr_clock_type type) { + gpr_timespec result; + result.clock_type = type; + if (us == INT64_MAX) { + result = gpr_inf_future(type); + } else if (us == INT64_MIN) { + result = gpr_inf_past(type); + } else if (us >= 0) { + result.tv_sec = us / 1000000; + result.tv_nsec = (int32_t)((us - result.tv_sec * 1000000) * 1000); + } else { + /* Calculation carefully formulated to avoid any possible under/overflow. */ + result.tv_sec = (-(999999 - (us + 1000000)) / 1000000) - 1; + result.tv_nsec = (int32_t)((us - result.tv_sec * 1000000) * 1000); + } + return result; +} + +gpr_timespec gpr_time_from_millis(int64_t ms, gpr_clock_type type) { + gpr_timespec result; + result.clock_type = type; + if (ms == INT64_MAX) { + result = gpr_inf_future(type); + } else if (ms == INT64_MIN) { + result = gpr_inf_past(type); + } else if (ms >= 0) { + result.tv_sec = ms / 1000; + result.tv_nsec = (int32_t)((ms - result.tv_sec * 1000) * 1000000); + } else { + /* Calculation carefully formulated to avoid any possible under/overflow. */ + result.tv_sec = (-(999 - (ms + 1000)) / 1000) - 1; + result.tv_nsec = (int32_t)((ms - result.tv_sec * 1000) * 1000000); + } + return result; +} + +gpr_timespec gpr_time_from_seconds(int64_t s, gpr_clock_type type) { + gpr_timespec result; + result.clock_type = type; + if (s == INT64_MAX) { + result = gpr_inf_future(type); + } else if (s == INT64_MIN) { + result = gpr_inf_past(type); + } else { + result.tv_sec = s; + result.tv_nsec = 0; + } + return result; +} + +gpr_timespec gpr_time_from_minutes(int64_t m, gpr_clock_type type) { + gpr_timespec result; + result.clock_type = type; + if (m >= INT64_MAX / 60) { + result = gpr_inf_future(type); + } else if (m <= INT64_MIN / 60) { + result = gpr_inf_past(type); + } else { + result.tv_sec = m * 60; + result.tv_nsec = 0; + } + return result; +} + +gpr_timespec gpr_time_from_hours(int64_t h, gpr_clock_type type) { + gpr_timespec result; + result.clock_type = type; + if (h >= INT64_MAX / 3600) { + result = gpr_inf_future(type); + } else if (h <= INT64_MIN / 3600) { + result = gpr_inf_past(type); + } else { + result.tv_sec = h * 3600; + result.tv_nsec = 0; + } + return result; +} + +gpr_timespec gpr_time_add(gpr_timespec a, gpr_timespec b) { + gpr_timespec sum; + int64_t inc = 0; + GPR_ASSERT(b.clock_type == GPR_TIMESPAN); + sum.clock_type = a.clock_type; + sum.tv_nsec = a.tv_nsec + b.tv_nsec; + if (sum.tv_nsec >= GPR_NS_PER_SEC) { + sum.tv_nsec -= GPR_NS_PER_SEC; + inc++; + } + if (a.tv_sec == INT64_MAX || a.tv_sec == INT64_MIN) { + sum = a; + } else if (b.tv_sec == INT64_MAX || + (b.tv_sec >= 0 && a.tv_sec >= INT64_MAX - b.tv_sec)) { + sum = gpr_inf_future(sum.clock_type); + } else if (b.tv_sec == INT64_MIN || + (b.tv_sec <= 0 && a.tv_sec <= INT64_MIN - b.tv_sec)) { + sum = gpr_inf_past(sum.clock_type); + } else { + sum.tv_sec = a.tv_sec + b.tv_sec; + if (inc != 0 && sum.tv_sec == INT64_MAX - 1) { + sum = gpr_inf_future(sum.clock_type); + } else { + sum.tv_sec += inc; + } + } + return sum; +} + +gpr_timespec gpr_time_sub(gpr_timespec a, gpr_timespec b) { + gpr_timespec diff; + int64_t dec = 0; + if (b.clock_type == GPR_TIMESPAN) { + diff.clock_type = a.clock_type; + } else { + GPR_ASSERT(a.clock_type == b.clock_type); + diff.clock_type = GPR_TIMESPAN; + } + diff.tv_nsec = a.tv_nsec - b.tv_nsec; + if (diff.tv_nsec < 0) { + diff.tv_nsec += GPR_NS_PER_SEC; + dec++; + } + if (a.tv_sec == INT64_MAX || a.tv_sec == INT64_MIN) { + diff = a; + } else if (b.tv_sec == INT64_MIN || + (b.tv_sec <= 0 && a.tv_sec >= INT64_MAX + b.tv_sec)) { + diff = gpr_inf_future(GPR_CLOCK_REALTIME); + } else if (b.tv_sec == INT64_MAX || + (b.tv_sec >= 0 && a.tv_sec <= INT64_MIN + b.tv_sec)) { + diff = gpr_inf_past(GPR_CLOCK_REALTIME); + } else { + diff.tv_sec = a.tv_sec - b.tv_sec; + if (dec != 0 && diff.tv_sec == INT64_MIN + 1) { + diff = gpr_inf_past(GPR_CLOCK_REALTIME); + } else { + diff.tv_sec -= dec; + } + } + return diff; +} + +int gpr_time_similar(gpr_timespec a, gpr_timespec b, gpr_timespec threshold) { + int cmp_ab; + + GPR_ASSERT(a.clock_type == b.clock_type); + GPR_ASSERT(threshold.clock_type == GPR_TIMESPAN); + + cmp_ab = gpr_time_cmp(a, b); + if (cmp_ab == 0) return 1; + if (cmp_ab < 0) { + return gpr_time_cmp(gpr_time_sub(b, a), threshold) <= 0; + } else { + return gpr_time_cmp(gpr_time_sub(a, b), threshold) <= 0; + } +} + +int32_t gpr_time_to_millis(gpr_timespec t) { + if (t.tv_sec >= 2147483) { + if (t.tv_sec == 2147483 && t.tv_nsec < 648 * GPR_NS_PER_MS) { + return 2147483 * GPR_MS_PER_SEC + t.tv_nsec / GPR_NS_PER_MS; + } + return 2147483647; + } else if (t.tv_sec <= -2147483) { + /* TODO(ctiller): correct handling here (it's so far in the past do we + care?) */ + return -2147483647; + } else { + return (int32_t)(t.tv_sec * GPR_MS_PER_SEC + t.tv_nsec / GPR_NS_PER_MS); + } +} + +double gpr_timespec_to_micros(gpr_timespec t) { + return (double)t.tv_sec * GPR_US_PER_SEC + t.tv_nsec * 1e-3; +} + +gpr_timespec gpr_convert_clock_type(gpr_timespec t, gpr_clock_type clock_type) { + if (t.clock_type == clock_type) { + return t; + } + + if (t.tv_nsec == 0) { + if (t.tv_sec == INT64_MAX) { + t.clock_type = clock_type; + return t; + } + if (t.tv_sec == INT64_MIN) { + t.clock_type = clock_type; + return t; + } + } + + if (clock_type == GPR_TIMESPAN) { + return gpr_time_sub(t, gpr_now(t.clock_type)); + } + + if (t.clock_type == GPR_TIMESPAN) { + return gpr_time_add(gpr_now(clock_type), t); + } + + return gpr_time_add(gpr_now(clock_type), + gpr_time_sub(t, gpr_now(t.clock_type))); +} diff --git a/src/core/lib/support/time_posix.c b/src/core/lib/support/time_posix.c new file mode 100644 index 0000000000..fcfab2f2fa --- /dev/null +++ b/src/core/lib/support/time_posix.c @@ -0,0 +1,170 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> +#include "src/core/lib/support/time_precise.h" + +#ifdef GPR_POSIX_TIME + +#include <stdlib.h> +#include <time.h> +#include <unistd.h> +#ifdef __linux__ +#include <sys/syscall.h> +#endif +#include <grpc/support/log.h> +#include <grpc/support/time.h> +#include "src/core/lib/support/block_annotate.h" + +static struct timespec timespec_from_gpr(gpr_timespec gts) { + struct timespec rv; + if (sizeof(time_t) < sizeof(int64_t)) { + /* fine to assert, as this is only used in gpr_sleep_until */ + GPR_ASSERT(gts.tv_sec <= INT32_MAX && gts.tv_sec >= INT32_MIN); + } + rv.tv_sec = (time_t)gts.tv_sec; + rv.tv_nsec = gts.tv_nsec; + return rv; +} + +#if _POSIX_TIMERS > 0 +static gpr_timespec gpr_from_timespec(struct timespec ts, + gpr_clock_type clock_type) { + /* + * timespec.tv_sec can have smaller size than gpr_timespec.tv_sec, + * but we are only using this function to implement gpr_now + * so there's no need to handle "infinity" values. + */ + gpr_timespec rv; + rv.tv_sec = ts.tv_sec; + rv.tv_nsec = (int32_t)ts.tv_nsec; + rv.clock_type = clock_type; + return rv; +} + +/** maps gpr_clock_type --> clockid_t for clock_gettime */ +static const clockid_t clockid_for_gpr_clock[] = {CLOCK_MONOTONIC, + CLOCK_REALTIME}; + +void gpr_time_init(void) { gpr_precise_clock_init(); } + +gpr_timespec gpr_now(gpr_clock_type clock_type) { + struct timespec now; + GPR_ASSERT(clock_type != GPR_TIMESPAN); + if (clock_type == GPR_CLOCK_PRECISE) { + gpr_timespec ret; + gpr_precise_clock_now(&ret); + return ret; + } else { +#if defined(GPR_BACKWARDS_COMPATIBILITY_MODE) && defined(__linux__) + /* avoid ABI problems by invoking syscalls directly */ + syscall(SYS_clock_gettime, clockid_for_gpr_clock[clock_type], &now); +#else + clock_gettime(clockid_for_gpr_clock[clock_type], &now); +#endif + return gpr_from_timespec(now, clock_type); + } +} +#else +/* For some reason Apple's OSes haven't implemented clock_gettime. */ + +#include <mach/mach.h> +#include <mach/mach_time.h> +#include <sys/time.h> + +static double g_time_scale; +static uint64_t g_time_start; + +void gpr_time_init(void) { + mach_timebase_info_data_t tb = {0, 1}; + gpr_precise_clock_init(); + mach_timebase_info(&tb); + g_time_scale = tb.numer; + g_time_scale /= tb.denom; + g_time_start = mach_absolute_time(); +} + +gpr_timespec gpr_now(gpr_clock_type clock) { + gpr_timespec now; + struct timeval now_tv; + double now_dbl; + + now.clock_type = clock; + switch (clock) { + case GPR_CLOCK_REALTIME: + gettimeofday(&now_tv, NULL); + now.tv_sec = now_tv.tv_sec; + now.tv_nsec = now_tv.tv_usec * 1000; + break; + case GPR_CLOCK_MONOTONIC: + now_dbl = (mach_absolute_time() - g_time_start) * g_time_scale; + now.tv_sec = (int64_t)(now_dbl * 1e-9); + now.tv_nsec = (int32_t)(now_dbl - ((double)now.tv_sec) * 1e9); + break; + case GPR_CLOCK_PRECISE: + gpr_precise_clock_now(&now); + break; + case GPR_TIMESPAN: + abort(); + } + + return now; +} +#endif + +void gpr_sleep_until(gpr_timespec until) { + gpr_timespec now; + gpr_timespec delta; + struct timespec delta_ts; + int ns_result; + + for (;;) { + /* We could simplify by using clock_nanosleep instead, but it might be + * slightly less portable. */ + now = gpr_now(until.clock_type); + if (gpr_time_cmp(until, now) <= 0) { + return; + } + + delta = gpr_time_sub(until, now); + delta_ts = timespec_from_gpr(delta); + GRPC_SCHEDULING_START_BLOCKING_REGION; + ns_result = nanosleep(&delta_ts, NULL); + GRPC_SCHEDULING_END_BLOCKING_REGION; + if (ns_result == 0) { + break; + } + } +} + +#endif /* GPR_POSIX_TIME */ diff --git a/src/core/lib/support/time_precise.c b/src/core/lib/support/time_precise.c new file mode 100644 index 0000000000..31ac47e0f8 --- /dev/null +++ b/src/core/lib/support/time_precise.c @@ -0,0 +1,89 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/log.h> +#include <grpc/support/time.h> +#include <stdio.h> + +#ifdef GRPC_TIMERS_RDTSC +#if defined(__i386__) +static void gpr_get_cycle_counter(long long int *clk) { + long long int ret; + __asm__ volatile("rdtsc" : "=A"(ret)); + *clk = ret; +} + +// ---------------------------------------------------------------- +#elif defined(__x86_64__) || defined(__amd64__) +static void gpr_get_cycle_counter(long long int *clk) { + unsigned long long low, high; + __asm__ volatile("rdtsc" : "=a"(low), "=d"(high)); + *clk = (long long)(high << 32) | (long long)low; +} +#endif + +static double cycles_per_second = 0; +static long long int start_cycle; +void gpr_precise_clock_init(void) { + time_t start; + long long end_cycle; + gpr_log(GPR_DEBUG, "Calibrating timers"); + start = time(NULL); + while (time(NULL) == start) + ; + gpr_get_cycle_counter(&start_cycle); + while (time(NULL) <= start + 10) + ; + gpr_get_cycle_counter(&end_cycle); + cycles_per_second = (double)(end_cycle - start_cycle) / 10.0; + gpr_log(GPR_DEBUG, "... cycles_per_second = %f\n", cycles_per_second); +} + +void gpr_precise_clock_now(gpr_timespec *clk) { + long long int counter; + double secs; + gpr_get_cycle_counter(&counter); + secs = (double)(counter - start_cycle) / cycles_per_second; + clk->clock_type = GPR_CLOCK_PRECISE; + clk->tv_sec = (int64_t)secs; + clk->tv_nsec = (int32_t)(1e9 * (secs - (double)clk->tv_sec)); +} + +#else /* GRPC_TIMERS_RDTSC */ +void gpr_precise_clock_init(void) {} + +void gpr_precise_clock_now(gpr_timespec *clk) { + *clk = gpr_now(GPR_CLOCK_REALTIME); + clk->clock_type = GPR_CLOCK_PRECISE; +} +#endif /* GRPC_TIMERS_RDTSC */ diff --git a/src/core/lib/support/time_precise.h b/src/core/lib/support/time_precise.h new file mode 100644 index 0000000000..e1faee1f9f --- /dev/null +++ b/src/core/lib/support/time_precise.h @@ -0,0 +1,42 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SUPPORT_TIME_PRECISE_H +#define GRPC_CORE_LIB_SUPPORT_TIME_PRECISE_H + +#include <grpc/support/time.h> + +void gpr_precise_clock_init(void); +void gpr_precise_clock_now(gpr_timespec *clk); + +#endif /* GRPC_CORE_LIB_SUPPORT_TIME_PRECISE_H */ diff --git a/src/core/lib/support/time_win32.c b/src/core/lib/support/time_win32.c new file mode 100644 index 0000000000..a6ac003fb8 --- /dev/null +++ b/src/core/lib/support/time_win32.c @@ -0,0 +1,110 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* Win32 code for gpr time support. */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WIN32 + +#include <grpc/support/log.h> +#include <grpc/support/time.h> +#include <limits.h> +#include <process.h> +#include <sys/timeb.h> + +#include "src/core/lib/support/block_annotate.h" +#include "src/core/lib/support/time_precise.h" + +static LARGE_INTEGER g_start_time; +static double g_time_scale; + +void gpr_time_init(void) { + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); + QueryPerformanceCounter(&g_start_time); + g_time_scale = 1.0 / (double)frequency.QuadPart; +} + +gpr_timespec gpr_now(gpr_clock_type clock) { + gpr_timespec now_tv; + LONGLONG diff; + struct _timeb now_tb; + LARGE_INTEGER timestamp; + double now_dbl; + now_tv.clock_type = clock; + switch (clock) { + case GPR_CLOCK_REALTIME: + _ftime_s(&now_tb); + now_tv.tv_sec = (int64_t)now_tb.time; + now_tv.tv_nsec = now_tb.millitm * 1000000; + break; + case GPR_CLOCK_MONOTONIC: + case GPR_CLOCK_PRECISE: + QueryPerformanceCounter(×tamp); + diff = timestamp.QuadPart - g_start_time.QuadPart; + now_dbl = (double)diff * g_time_scale; + now_tv.tv_sec = (int64_t)now_dbl; + now_tv.tv_nsec = (int32_t)((now_dbl - (double)now_tv.tv_sec) * 1e9); + break; + case GPR_TIMESPAN: + abort(); + break; + } + return now_tv; +} + +void gpr_sleep_until(gpr_timespec until) { + gpr_timespec now; + gpr_timespec delta; + int64_t sleep_millis; + + for (;;) { + /* We could simplify by using clock_nanosleep instead, but it might be + * slightly less portable. */ + now = gpr_now(until.clock_type); + if (gpr_time_cmp(until, now) <= 0) { + return; + } + + delta = gpr_time_sub(until, now); + sleep_millis = + delta.tv_sec * GPR_MS_PER_SEC + delta.tv_nsec / GPR_NS_PER_MS; + GPR_ASSERT((sleep_millis >= 0) && (sleep_millis <= INT_MAX)); + GRPC_SCHEDULING_START_BLOCKING_REGION; + Sleep((DWORD)sleep_millis); + GRPC_SCHEDULING_END_BLOCKING_REGION; + } +} + +#endif /* GPR_WIN32 */ diff --git a/src/core/lib/support/tls_pthread.c b/src/core/lib/support/tls_pthread.c new file mode 100644 index 0000000000..bdc7ed14ae --- /dev/null +++ b/src/core/lib/support/tls_pthread.c @@ -0,0 +1,45 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_PTHREAD_TLS + +#include <grpc/support/tls.h> + +intptr_t gpr_tls_set(struct gpr_pthread_thread_local *tls, intptr_t value) { + GPR_ASSERT(0 == pthread_setspecific(tls->key, (void *)value)); + return value; +} + +#endif /* GPR_PTHREAD_TLS */ diff --git a/src/core/lib/support/tmpfile.h b/src/core/lib/support/tmpfile.h new file mode 100644 index 0000000000..4fec2076e3 --- /dev/null +++ b/src/core/lib/support/tmpfile.h @@ -0,0 +1,55 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SUPPORT_TMPFILE_H +#define GRPC_CORE_LIB_SUPPORT_TMPFILE_H + +#include <stdio.h> + +#include <grpc/support/slice.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Creates a temporary file from a prefix. + If tmp_filename is not NULL, *tmp_filename is assigned the name of the + created file and it is the responsibility of the caller to gpr_free it + unless an error occurs in which case it will be set to NULL. */ +FILE *gpr_tmpfile(const char *prefix, char **tmp_filename); + +#ifdef __cplusplus +} +#endif + +#endif /* GRPC_CORE_LIB_SUPPORT_TMPFILE_H */ diff --git a/src/core/lib/support/tmpfile_posix.c b/src/core/lib/support/tmpfile_posix.c new file mode 100644 index 0000000000..743f45e1bc --- /dev/null +++ b/src/core/lib/support/tmpfile_posix.c @@ -0,0 +1,85 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_FILE + +#include "src/core/lib/support/tmpfile.h" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> + +#include "src/core/lib/support/string.h" + +FILE *gpr_tmpfile(const char *prefix, char **tmp_filename) { + FILE *result = NULL; + char *template; + int fd; + + if (tmp_filename != NULL) *tmp_filename = NULL; + + gpr_asprintf(&template, "/tmp/%s_XXXXXX", prefix); + GPR_ASSERT(template != NULL); + + fd = mkstemp(template); + if (fd == -1) { + gpr_log(GPR_ERROR, "mkstemp failed for template %s with error %s.", + template, strerror(errno)); + goto end; + } + result = fdopen(fd, "w+"); + if (result == NULL) { + gpr_log(GPR_ERROR, "Could not open file %s from fd %d (error = %s).", + template, fd, strerror(errno)); + unlink(template); + close(fd); + goto end; + } + +end: + if (result != NULL && tmp_filename != NULL) { + *tmp_filename = template; + } else { + gpr_free(template); + } + return result; +} + +#endif /* GPR_POSIX_FILE */ diff --git a/src/core/lib/support/tmpfile_win32.c b/src/core/lib/support/tmpfile_win32.c new file mode 100644 index 0000000000..05d92b6036 --- /dev/null +++ b/src/core/lib/support/tmpfile_win32.c @@ -0,0 +1,84 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WIN32 + +#include <io.h> +#include <stdio.h> +#include <string.h> +#include <tchar.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> + +#include "src/core/lib/support/string_win32.h" +#include "src/core/lib/support/tmpfile.h" + +FILE *gpr_tmpfile(const char *prefix, char **tmp_filename_out) { + FILE *result = NULL; + LPTSTR template_string = NULL; + TCHAR tmp_path[MAX_PATH]; + TCHAR tmp_filename[MAX_PATH]; + DWORD status; + UINT success; + + if (tmp_filename_out != NULL) *tmp_filename_out = NULL; + + /* Convert our prefix to TCHAR. */ + template_string = gpr_char_to_tchar(prefix); + GPR_ASSERT(template_string); + + /* Get the path to the best temporary folder available. */ + status = GetTempPath(MAX_PATH, tmp_path); + if (status == 0 || status > MAX_PATH) goto end; + + /* Generate a unique filename with our template + temporary path. */ + success = GetTempFileName(tmp_path, template_string, 0, tmp_filename); + if (!success) goto end; + + /* Open a file there. */ + if (_tfopen_s(&result, tmp_filename, TEXT("wb+")) != 0) goto end; + +end: + if (result && tmp_filename_out) { + *tmp_filename_out = gpr_tchar_to_char(tmp_filename); + } + + gpr_free(template_string); + return result; +} + +#endif /* GPR_WIN32 */ diff --git a/src/core/lib/support/wrap_memcpy.c b/src/core/lib/support/wrap_memcpy.c new file mode 100644 index 0000000000..15c289f7b8 --- /dev/null +++ b/src/core/lib/support/wrap_memcpy.c @@ -0,0 +1,53 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <string.h> + +/* Provide a wrapped memcpy for targets that need to be backwards + * compatible with older libc's. + * + * Enable by setting LDFLAGS=-Wl,-wrap,memcpy when linking. + */ + +#ifdef __linux__ +#ifdef __x86_64__ +__asm__(".symver memcpy,memcpy@GLIBC_2.2.5"); +void *__wrap_memcpy(void *destination, const void *source, size_t num) { + return memcpy(destination, source, num); +} +#else /* !__x86_64__ */ +void *__wrap_memcpy(void *destination, const void *source, size_t num) { + return memmove(destination, source, num); +} +#endif +#endif diff --git a/src/core/lib/surface/alarm.c b/src/core/lib/surface/alarm.c new file mode 100644 index 0000000000..368683378e --- /dev/null +++ b/src/core/lib/surface/alarm.c @@ -0,0 +1,84 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/grpc.h> +#include <grpc/support/alloc.h> +#include "src/core/lib/iomgr/timer.h" +#include "src/core/lib/surface/completion_queue.h" + +struct grpc_alarm { + grpc_timer alarm; + grpc_cq_completion completion; + /** completion queue where events about this alarm will be posted */ + grpc_completion_queue *cq; + /** user supplied tag */ + void *tag; +}; + +static void do_nothing_end_completion(grpc_exec_ctx *exec_ctx, void *arg, + grpc_cq_completion *c) {} + +static void alarm_cb(grpc_exec_ctx *exec_ctx, void *arg, bool success) { + grpc_alarm *alarm = arg; + grpc_cq_end_op(exec_ctx, alarm->cq, alarm->tag, success, + do_nothing_end_completion, NULL, &alarm->completion); +} + +grpc_alarm *grpc_alarm_create(grpc_completion_queue *cq, gpr_timespec deadline, + void *tag) { + grpc_alarm *alarm = gpr_malloc(sizeof(grpc_alarm)); + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + + GRPC_CQ_INTERNAL_REF(cq, "alarm"); + alarm->cq = cq; + alarm->tag = tag; + + grpc_cq_begin_op(cq, tag); + grpc_timer_init(&exec_ctx, &alarm->alarm, + gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC), + alarm_cb, alarm, gpr_now(GPR_CLOCK_MONOTONIC)); + grpc_exec_ctx_finish(&exec_ctx); + return alarm; +} + +void grpc_alarm_cancel(grpc_alarm *alarm) { + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + grpc_timer_cancel(&exec_ctx, &alarm->alarm); + grpc_exec_ctx_finish(&exec_ctx); +} + +void grpc_alarm_destroy(grpc_alarm *alarm) { + grpc_alarm_cancel(alarm); + GRPC_CQ_INTERNAL_UNREF(alarm->cq, "alarm"); + gpr_free(alarm); +} diff --git a/src/core/lib/surface/api_trace.c b/src/core/lib/surface/api_trace.c new file mode 100644 index 0000000000..3702c024db --- /dev/null +++ b/src/core/lib/surface/api_trace.c @@ -0,0 +1,36 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/surface/api_trace.h" + +int grpc_api_trace = 0; diff --git a/src/core/lib/surface/api_trace.h b/src/core/lib/surface/api_trace.h new file mode 100644 index 0000000000..b50011c9e5 --- /dev/null +++ b/src/core/lib/surface/api_trace.h @@ -0,0 +1,65 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SURFACE_API_TRACE_H +#define GRPC_CORE_LIB_SURFACE_API_TRACE_H + +#include <grpc/support/log.h> +#include "src/core/lib/debug/trace.h" + +extern int grpc_api_trace; + +/* Provide unwrapping macros because we're in C89 and variadic macros weren't + introduced until C99... */ +#define GRPC_API_TRACE_UNWRAP0() +#define GRPC_API_TRACE_UNWRAP1(a) , a +#define GRPC_API_TRACE_UNWRAP2(a, b) , a, b +#define GRPC_API_TRACE_UNWRAP3(a, b, c) , a, b, c +#define GRPC_API_TRACE_UNWRAP4(a, b, c, d) , a, b, c, d +#define GRPC_API_TRACE_UNWRAP5(a, b, c, d, e) , a, b, c, d, e +#define GRPC_API_TRACE_UNWRAP6(a, b, c, d, e, f) , a, b, c, d, e, f +#define GRPC_API_TRACE_UNWRAP7(a, b, c, d, e, f, g) , a, b, c, d, e, f, g +#define GRPC_API_TRACE_UNWRAP8(a, b, c, d, e, f, g, h) , a, b, c, d, e, f, g, h +#define GRPC_API_TRACE_UNWRAP9(a, b, c, d, e, f, g, h, i) \ + , a, b, c, d, e, f, g, h, i +#define GRPC_API_TRACE_UNWRAP10(a, b, c, d, e, f, g, h, i, j) \ + , a, b, c, d, e, f, g, h, i, j + +/* Due to the limitations of C89's preprocessor, the arity of the var-arg list + 'nargs' must be specified. */ +#define GRPC_API_TRACE(fmt, nargs, args) \ + if (grpc_api_trace) { \ + gpr_log(GPR_INFO, fmt GRPC_API_TRACE_UNWRAP##nargs args); \ + } + +#endif /* GRPC_CORE_LIB_SURFACE_API_TRACE_H */ diff --git a/src/core/lib/surface/byte_buffer.c b/src/core/lib/surface/byte_buffer.c new file mode 100644 index 0000000000..03071ef92c --- /dev/null +++ b/src/core/lib/surface/byte_buffer.c @@ -0,0 +1,97 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/byte_buffer.h> +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +grpc_byte_buffer *grpc_raw_byte_buffer_create(gpr_slice *slices, + size_t nslices) { + return grpc_raw_compressed_byte_buffer_create(slices, nslices, + GRPC_COMPRESS_NONE); +} + +grpc_byte_buffer *grpc_raw_compressed_byte_buffer_create( + gpr_slice *slices, size_t nslices, grpc_compression_algorithm compression) { + size_t i; + grpc_byte_buffer *bb = malloc(sizeof(grpc_byte_buffer)); + bb->type = GRPC_BB_RAW; + bb->data.raw.compression = compression; + gpr_slice_buffer_init(&bb->data.raw.slice_buffer); + for (i = 0; i < nslices; i++) { + gpr_slice_ref(slices[i]); + gpr_slice_buffer_add(&bb->data.raw.slice_buffer, slices[i]); + } + return bb; +} + +grpc_byte_buffer *grpc_raw_byte_buffer_from_reader( + grpc_byte_buffer_reader *reader) { + grpc_byte_buffer *bb = malloc(sizeof(grpc_byte_buffer)); + gpr_slice slice; + bb->type = GRPC_BB_RAW; + bb->data.raw.compression = GRPC_COMPRESS_NONE; + gpr_slice_buffer_init(&bb->data.raw.slice_buffer); + + while (grpc_byte_buffer_reader_next(reader, &slice)) { + gpr_slice_buffer_add(&bb->data.raw.slice_buffer, slice); + } + return bb; +} + +grpc_byte_buffer *grpc_byte_buffer_copy(grpc_byte_buffer *bb) { + switch (bb->type) { + case GRPC_BB_RAW: + return grpc_raw_byte_buffer_create(bb->data.raw.slice_buffer.slices, + bb->data.raw.slice_buffer.count); + } + GPR_UNREACHABLE_CODE(return NULL); +} + +void grpc_byte_buffer_destroy(grpc_byte_buffer *bb) { + if (!bb) return; + switch (bb->type) { + case GRPC_BB_RAW: + gpr_slice_buffer_destroy(&bb->data.raw.slice_buffer); + break; + } + free(bb); +} + +size_t grpc_byte_buffer_length(grpc_byte_buffer *bb) { + switch (bb->type) { + case GRPC_BB_RAW: + return bb->data.raw.slice_buffer.length; + } + GPR_UNREACHABLE_CODE(return 0); +} diff --git a/src/core/lib/surface/byte_buffer_reader.c b/src/core/lib/surface/byte_buffer_reader.c new file mode 100644 index 0000000000..7248f5fe71 --- /dev/null +++ b/src/core/lib/surface/byte_buffer_reader.c @@ -0,0 +1,123 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/byte_buffer_reader.h> +#include <string.h> + +#include <grpc/byte_buffer.h> +#include <grpc/compression.h> +#include <grpc/grpc.h> +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/slice_buffer.h> + +#include "src/core/lib/compression/message_compress.h" + +static int is_compressed(grpc_byte_buffer *buffer) { + switch (buffer->type) { + case GRPC_BB_RAW: + if (buffer->data.raw.compression == GRPC_COMPRESS_NONE) { + return 0 /* GPR_FALSE */; + } + break; + } + return 1 /* GPR_TRUE */; +} + +void grpc_byte_buffer_reader_init(grpc_byte_buffer_reader *reader, + grpc_byte_buffer *buffer) { + gpr_slice_buffer decompressed_slices_buffer; + reader->buffer_in = buffer; + switch (reader->buffer_in->type) { + case GRPC_BB_RAW: + gpr_slice_buffer_init(&decompressed_slices_buffer); + if (is_compressed(reader->buffer_in)) { + grpc_msg_decompress(reader->buffer_in->data.raw.compression, + &reader->buffer_in->data.raw.slice_buffer, + &decompressed_slices_buffer); + reader->buffer_out = + grpc_raw_byte_buffer_create(decompressed_slices_buffer.slices, + decompressed_slices_buffer.count); + gpr_slice_buffer_destroy(&decompressed_slices_buffer); + } else { /* not compressed, use the input buffer as output */ + reader->buffer_out = reader->buffer_in; + } + reader->current.index = 0; + break; + } +} + +void grpc_byte_buffer_reader_destroy(grpc_byte_buffer_reader *reader) { + switch (reader->buffer_in->type) { + case GRPC_BB_RAW: + /* keeping the same if-else structure as in the init function */ + if (is_compressed(reader->buffer_in)) { + grpc_byte_buffer_destroy(reader->buffer_out); + } + break; + } +} + +int grpc_byte_buffer_reader_next(grpc_byte_buffer_reader *reader, + gpr_slice *slice) { + switch (reader->buffer_in->type) { + case GRPC_BB_RAW: { + gpr_slice_buffer *slice_buffer; + slice_buffer = &reader->buffer_out->data.raw.slice_buffer; + if (reader->current.index < slice_buffer->count) { + *slice = gpr_slice_ref(slice_buffer->slices[reader->current.index]); + reader->current.index += 1; + return 1; + } + break; + } + } + return 0; +} + +gpr_slice grpc_byte_buffer_reader_readall(grpc_byte_buffer_reader *reader) { + gpr_slice in_slice; + size_t bytes_read = 0; + const size_t input_size = grpc_byte_buffer_length(reader->buffer_out); + gpr_slice out_slice = gpr_slice_malloc(input_size); + uint8_t *const outbuf = GPR_SLICE_START_PTR(out_slice); /* just an alias */ + + while (grpc_byte_buffer_reader_next(reader, &in_slice) != 0) { + const size_t slice_length = GPR_SLICE_LENGTH(in_slice); + memcpy(&(outbuf[bytes_read]), GPR_SLICE_START_PTR(in_slice), slice_length); + bytes_read += slice_length; + gpr_slice_unref(in_slice); + GPR_ASSERT(bytes_read <= input_size); + } + return out_slice; +} diff --git a/src/core/lib/surface/call.c b/src/core/lib/surface/call.c new file mode 100644 index 0000000000..d63a4a7401 --- /dev/null +++ b/src/core/lib/surface/call.c @@ -0,0 +1,1491 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +#include <assert.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <grpc/compression.h> +#include <grpc/grpc.h> +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> +#include <grpc/support/useful.h> + +#include "src/core/lib/channel/channel_stack.h" +#include "src/core/lib/compression/algorithm_metadata.h" +#include "src/core/lib/iomgr/timer.h" +#include "src/core/lib/profiling/timers.h" +#include "src/core/lib/support/string.h" +#include "src/core/lib/surface/api_trace.h" +#include "src/core/lib/surface/call.h" +#include "src/core/lib/surface/channel.h" +#include "src/core/lib/surface/completion_queue.h" +#include "src/core/lib/transport/static_metadata.h" + +/** The maximum number of concurrent batches possible. + Based upon the maximum number of individually queueable ops in the batch + api: + - initial metadata send + - message send + - status/close send (depending on client/server) + - initial metadata recv + - message recv + - status/close recv (depending on client/server) */ +#define MAX_CONCURRENT_BATCHES 6 + +typedef struct { + grpc_ioreq_completion_func on_complete; + void *user_data; + int success; +} completed_request; + +#define MAX_SEND_EXTRA_METADATA_COUNT 3 + +/* Status data for a request can come from several sources; this + enumerates them all, and acts as a priority sorting for which + status to return to the application - earlier entries override + later ones */ +typedef enum { + /* Status came from the application layer overriding whatever + the wire says */ + STATUS_FROM_API_OVERRIDE = 0, + /* Status was created by some internal channel stack operation */ + STATUS_FROM_CORE, + /* Status came from 'the wire' - or somewhere below the surface + layer */ + STATUS_FROM_WIRE, + /* Status came from the server sending status */ + STATUS_FROM_SERVER_STATUS, + STATUS_SOURCE_COUNT +} status_source; + +typedef struct { + uint8_t is_set; + grpc_status_code code; + grpc_mdstr *details; +} received_status; + +/* How far through the GRPC stream have we read? */ +typedef enum { + /* We are still waiting for initial metadata to complete */ + READ_STATE_INITIAL = 0, + /* We have gotten initial metadata, and are reading either + messages or trailing metadata */ + READ_STATE_GOT_INITIAL_METADATA, + /* The stream is closed for reading */ + READ_STATE_READ_CLOSED, + /* The stream is closed for reading & writing */ + READ_STATE_STREAM_CLOSED +} read_state; + +typedef enum { + WRITE_STATE_INITIAL = 0, + WRITE_STATE_STARTED, + WRITE_STATE_WRITE_CLOSED +} write_state; + +typedef struct batch_control { + grpc_call *call; + grpc_cq_completion cq_completion; + grpc_closure finish_batch; + void *notify_tag; + gpr_refcount steps_to_complete; + + uint8_t send_initial_metadata; + uint8_t send_message; + uint8_t send_final_op; + uint8_t recv_initial_metadata; + uint8_t recv_message; + uint8_t recv_final_op; + uint8_t is_notify_tag_closure; + uint8_t success; +} batch_control; + +struct grpc_call { + grpc_completion_queue *cq; + grpc_channel *channel; + grpc_call *parent; + grpc_call *first_child; + /* TODO(ctiller): share with cq if possible? */ + gpr_mu mu; + + /* client or server call */ + uint8_t is_client; + /* is the alarm set */ + uint8_t have_alarm; + /** has grpc_call_destroy been called */ + uint8_t destroy_called; + /** flag indicating that cancellation is inherited */ + uint8_t cancellation_is_inherited; + /** bitmask of live batches */ + uint8_t used_batches; + /** which ops are in-flight */ + uint8_t sent_initial_metadata; + uint8_t sending_message; + uint8_t sent_final_op; + uint8_t received_initial_metadata; + uint8_t receiving_message; + uint8_t received_final_op; + + /* have we received initial metadata */ + bool has_initial_md_been_received; + + batch_control active_batches[MAX_CONCURRENT_BATCHES]; + + /* first idx: is_receiving, second idx: is_trailing */ + grpc_metadata_batch metadata_batch[2][2]; + + /* Buffered read metadata waiting to be returned to the application. + Element 0 is initial metadata, element 1 is trailing metadata. */ + grpc_metadata_array *buffered_metadata[2]; + + /* Received call statuses from various sources */ + received_status status[STATUS_SOURCE_COUNT]; + + /* Compression algorithm for the call */ + grpc_compression_algorithm compression_algorithm; + /* Supported encodings (compression algorithms), a bitset */ + uint32_t encodings_accepted_by_peer; + + /* Contexts for various subsystems (security, tracing, ...). */ + grpc_call_context_element context[GRPC_CONTEXT_COUNT]; + + /* Deadline alarm - if have_alarm is non-zero */ + grpc_timer alarm; + + /* for the client, extra metadata is initial metadata; for the + server, it's trailing metadata */ + grpc_linked_mdelem send_extra_metadata[MAX_SEND_EXTRA_METADATA_COUNT]; + int send_extra_metadata_count; + gpr_timespec send_deadline; + + /** siblings: children of the same parent form a list, and this list is + protected under + parent->mu */ + grpc_call *sibling_next; + grpc_call *sibling_prev; + + grpc_slice_buffer_stream sending_stream; + grpc_byte_stream *receiving_stream; + grpc_byte_buffer **receiving_buffer; + gpr_slice receiving_slice; + grpc_closure receiving_slice_ready; + grpc_closure receiving_stream_ready; + grpc_closure receiving_initial_metadata_ready; + uint32_t test_only_last_message_flags; + + union { + struct { + grpc_status_code *status; + char **status_details; + size_t *status_details_capacity; + } client; + struct { + int *cancelled; + } server; + } final_op; + + struct { + void *bctlp; + bool success; + } saved_receiving_stream_ready_ctx; +}; + +#define CALL_STACK_FROM_CALL(call) ((grpc_call_stack *)((call) + 1)) +#define CALL_FROM_CALL_STACK(call_stack) (((grpc_call *)(call_stack)) - 1) +#define CALL_ELEM_FROM_CALL(call, idx) \ + grpc_call_stack_element(CALL_STACK_FROM_CALL(call), idx) +#define CALL_FROM_TOP_ELEM(top_elem) \ + CALL_FROM_CALL_STACK(grpc_call_stack_from_top_element(top_elem)) + +static void set_deadline_alarm(grpc_exec_ctx *exec_ctx, grpc_call *call, + gpr_timespec deadline); +static void execute_op(grpc_exec_ctx *exec_ctx, grpc_call *call, + grpc_transport_stream_op *op); +static grpc_call_error cancel_with_status(grpc_exec_ctx *exec_ctx, grpc_call *c, + grpc_status_code status, + const char *description); +static void destroy_call(grpc_exec_ctx *exec_ctx, void *call_stack, + bool success); +static void receiving_slice_ready(grpc_exec_ctx *exec_ctx, void *bctlp, + bool success); + +grpc_call *grpc_call_create(grpc_channel *channel, grpc_call *parent_call, + uint32_t propagation_mask, + grpc_completion_queue *cq, + const void *server_transport_data, + grpc_mdelem **add_initial_metadata, + size_t add_initial_metadata_count, + gpr_timespec send_deadline) { + size_t i, j; + grpc_channel_stack *channel_stack = grpc_channel_get_channel_stack(channel); + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + grpc_call *call; + GPR_TIMER_BEGIN("grpc_call_create", 0); + call = gpr_malloc(sizeof(grpc_call) + channel_stack->call_stack_size); + memset(call, 0, sizeof(grpc_call)); + gpr_mu_init(&call->mu); + call->channel = channel; + call->cq = cq; + call->parent = parent_call; + call->is_client = server_transport_data == NULL; + if (call->is_client) { + GPR_ASSERT(add_initial_metadata_count < MAX_SEND_EXTRA_METADATA_COUNT); + for (i = 0; i < add_initial_metadata_count; i++) { + call->send_extra_metadata[i].md = add_initial_metadata[i]; + } + call->send_extra_metadata_count = (int)add_initial_metadata_count; + } else { + GPR_ASSERT(add_initial_metadata_count == 0); + call->send_extra_metadata_count = 0; + } + for (i = 0; i < 2; i++) { + for (j = 0; j < 2; j++) { + call->metadata_batch[i][j].deadline = gpr_inf_future(GPR_CLOCK_MONOTONIC); + } + } + call->send_deadline = send_deadline; + GRPC_CHANNEL_INTERNAL_REF(channel, "call"); + /* initial refcount dropped by grpc_call_destroy */ + grpc_call_stack_init(&exec_ctx, channel_stack, 1, destroy_call, call, + call->context, server_transport_data, + CALL_STACK_FROM_CALL(call)); + if (cq != NULL) { + GRPC_CQ_INTERNAL_REF(cq, "bind"); + grpc_call_stack_set_pollset(&exec_ctx, CALL_STACK_FROM_CALL(call), + grpc_cq_pollset(cq)); + } + if (parent_call != NULL) { + GRPC_CALL_INTERNAL_REF(parent_call, "child"); + GPR_ASSERT(call->is_client); + GPR_ASSERT(!parent_call->is_client); + + gpr_mu_lock(&parent_call->mu); + + if (propagation_mask & GRPC_PROPAGATE_DEADLINE) { + send_deadline = gpr_time_min( + gpr_convert_clock_type(send_deadline, + parent_call->send_deadline.clock_type), + parent_call->send_deadline); + } + /* for now GRPC_PROPAGATE_TRACING_CONTEXT *MUST* be passed with + * GRPC_PROPAGATE_STATS_CONTEXT */ + /* TODO(ctiller): This should change to use the appropriate census start_op + * call. */ + if (propagation_mask & GRPC_PROPAGATE_CENSUS_TRACING_CONTEXT) { + GPR_ASSERT(propagation_mask & GRPC_PROPAGATE_CENSUS_STATS_CONTEXT); + grpc_call_context_set(call, GRPC_CONTEXT_TRACING, + parent_call->context[GRPC_CONTEXT_TRACING].value, + NULL); + } else { + GPR_ASSERT(propagation_mask & GRPC_PROPAGATE_CENSUS_STATS_CONTEXT); + } + if (propagation_mask & GRPC_PROPAGATE_CANCELLATION) { + call->cancellation_is_inherited = 1; + } + + if (parent_call->first_child == NULL) { + parent_call->first_child = call; + call->sibling_next = call->sibling_prev = call; + } else { + call->sibling_next = parent_call->first_child; + call->sibling_prev = parent_call->first_child->sibling_prev; + call->sibling_next->sibling_prev = call->sibling_prev->sibling_next = + call; + } + + gpr_mu_unlock(&parent_call->mu); + } + if (gpr_time_cmp(send_deadline, gpr_inf_future(send_deadline.clock_type)) != + 0) { + set_deadline_alarm(&exec_ctx, call, send_deadline); + } + grpc_exec_ctx_finish(&exec_ctx); + GPR_TIMER_END("grpc_call_create", 0); + return call; +} + +void grpc_call_set_completion_queue(grpc_exec_ctx *exec_ctx, grpc_call *call, + grpc_completion_queue *cq) { + GPR_ASSERT(cq); + call->cq = cq; + GRPC_CQ_INTERNAL_REF(cq, "bind"); + grpc_call_stack_set_pollset(exec_ctx, CALL_STACK_FROM_CALL(call), + grpc_cq_pollset(cq)); +} + +#ifdef GRPC_STREAM_REFCOUNT_DEBUG +#define REF_REASON reason +#define REF_ARG , const char *reason +#else +#define REF_REASON "" +#define REF_ARG +#endif +void grpc_call_internal_ref(grpc_call *c REF_ARG) { + GRPC_CALL_STACK_REF(CALL_STACK_FROM_CALL(c), REF_REASON); +} +void grpc_call_internal_unref(grpc_exec_ctx *exec_ctx, grpc_call *c REF_ARG) { + GRPC_CALL_STACK_UNREF(exec_ctx, CALL_STACK_FROM_CALL(c), REF_REASON); +} + +static void destroy_call(grpc_exec_ctx *exec_ctx, void *call, bool success) { + size_t i; + int ii; + grpc_call *c = call; + GPR_TIMER_BEGIN("destroy_call", 0); + for (i = 0; i < 2; i++) { + grpc_metadata_batch_destroy( + &c->metadata_batch[1 /* is_receiving */][i /* is_initial */]); + } + if (c->receiving_stream != NULL) { + grpc_byte_stream_destroy(exec_ctx, c->receiving_stream); + } + grpc_call_stack_destroy(exec_ctx, CALL_STACK_FROM_CALL(c)); + GRPC_CHANNEL_INTERNAL_UNREF(exec_ctx, c->channel, "call"); + gpr_mu_destroy(&c->mu); + for (i = 0; i < STATUS_SOURCE_COUNT; i++) { + if (c->status[i].details) { + GRPC_MDSTR_UNREF(c->status[i].details); + } + } + for (ii = 0; ii < c->send_extra_metadata_count; ii++) { + GRPC_MDELEM_UNREF(c->send_extra_metadata[ii].md); + } + for (i = 0; i < GRPC_CONTEXT_COUNT; i++) { + if (c->context[i].destroy) { + c->context[i].destroy(c->context[i].value); + } + } + if (c->cq) { + GRPC_CQ_INTERNAL_UNREF(c->cq, "bind"); + } + gpr_free(c); + GPR_TIMER_END("destroy_call", 0); +} + +static void set_status_code(grpc_call *call, status_source source, + uint32_t status) { + if (call->status[source].is_set) return; + + call->status[source].is_set = 1; + call->status[source].code = (grpc_status_code)status; + + /* TODO(ctiller): what to do about the flush that was previously here */ +} + +static void set_compression_algorithm(grpc_call *call, + grpc_compression_algorithm algo) { + call->compression_algorithm = algo; +} + +grpc_compression_algorithm grpc_call_test_only_get_compression_algorithm( + grpc_call *call) { + grpc_compression_algorithm algorithm; + gpr_mu_lock(&call->mu); + algorithm = call->compression_algorithm; + gpr_mu_unlock(&call->mu); + return algorithm; +} + +uint32_t grpc_call_test_only_get_message_flags(grpc_call *call) { + uint32_t flags; + gpr_mu_lock(&call->mu); + flags = call->test_only_last_message_flags; + gpr_mu_unlock(&call->mu); + return flags; +} + +static void destroy_encodings_accepted_by_peer(void *p) { return; } + +static void set_encodings_accepted_by_peer(grpc_call *call, grpc_mdelem *mdel) { + size_t i; + grpc_compression_algorithm algorithm; + gpr_slice_buffer accept_encoding_parts; + gpr_slice accept_encoding_slice; + void *accepted_user_data; + + accepted_user_data = + grpc_mdelem_get_user_data(mdel, destroy_encodings_accepted_by_peer); + if (accepted_user_data != NULL) { + call->encodings_accepted_by_peer = + (uint32_t)(((uintptr_t)accepted_user_data) - 1); + return; + } + + accept_encoding_slice = mdel->value->slice; + gpr_slice_buffer_init(&accept_encoding_parts); + gpr_slice_split(accept_encoding_slice, ",", &accept_encoding_parts); + + /* No need to zero call->encodings_accepted_by_peer: grpc_call_create already + * zeroes the whole grpc_call */ + /* Always support no compression */ + GPR_BITSET(&call->encodings_accepted_by_peer, GRPC_COMPRESS_NONE); + for (i = 0; i < accept_encoding_parts.count; i++) { + const gpr_slice *accept_encoding_entry_slice = + &accept_encoding_parts.slices[i]; + if (grpc_compression_algorithm_parse( + (const char *)GPR_SLICE_START_PTR(*accept_encoding_entry_slice), + GPR_SLICE_LENGTH(*accept_encoding_entry_slice), &algorithm)) { + GPR_BITSET(&call->encodings_accepted_by_peer, algorithm); + } else { + char *accept_encoding_entry_str = + gpr_dump_slice(*accept_encoding_entry_slice, GPR_DUMP_ASCII); + gpr_log(GPR_ERROR, + "Invalid entry in accept encoding metadata: '%s'. Ignoring.", + accept_encoding_entry_str); + gpr_free(accept_encoding_entry_str); + } + } + + gpr_slice_buffer_destroy(&accept_encoding_parts); + + grpc_mdelem_set_user_data( + mdel, destroy_encodings_accepted_by_peer, + (void *)(((uintptr_t)call->encodings_accepted_by_peer) + 1)); +} + +uint32_t grpc_call_test_only_get_encodings_accepted_by_peer(grpc_call *call) { + uint32_t encodings_accepted_by_peer; + gpr_mu_lock(&call->mu); + encodings_accepted_by_peer = call->encodings_accepted_by_peer; + gpr_mu_unlock(&call->mu); + return encodings_accepted_by_peer; +} + +static void set_status_details(grpc_call *call, status_source source, + grpc_mdstr *status) { + if (call->status[source].details != NULL) { + GRPC_MDSTR_UNREF(call->status[source].details); + } + call->status[source].details = status; +} + +static void get_final_status(grpc_call *call, + void (*set_value)(grpc_status_code code, + void *user_data), + void *set_value_user_data) { + int i; + for (i = 0; i < STATUS_SOURCE_COUNT; i++) { + if (call->status[i].is_set) { + set_value(call->status[i].code, set_value_user_data); + return; + } + } + if (call->is_client) { + set_value(GRPC_STATUS_UNKNOWN, set_value_user_data); + } else { + set_value(GRPC_STATUS_OK, set_value_user_data); + } +} + +static void get_final_details(grpc_call *call, char **out_details, + size_t *out_details_capacity) { + int i; + for (i = 0; i < STATUS_SOURCE_COUNT; i++) { + if (call->status[i].is_set) { + if (call->status[i].details) { + gpr_slice details = call->status[i].details->slice; + size_t len = GPR_SLICE_LENGTH(details); + if (len + 1 > *out_details_capacity) { + *out_details_capacity = + GPR_MAX(len + 1, *out_details_capacity * 3 / 2); + *out_details = gpr_realloc(*out_details, *out_details_capacity); + } + memcpy(*out_details, GPR_SLICE_START_PTR(details), len); + (*out_details)[len] = 0; + } else { + goto no_details; + } + return; + } + } + +no_details: + if (0 == *out_details_capacity) { + *out_details_capacity = 8; + *out_details = gpr_malloc(*out_details_capacity); + } + **out_details = 0; +} + +static grpc_linked_mdelem *linked_from_md(grpc_metadata *md) { + return (grpc_linked_mdelem *)&md->internal_data; +} + +static int prepare_application_metadata(grpc_call *call, int count, + grpc_metadata *metadata, + int is_trailing, + int prepend_extra_metadata) { + int i; + grpc_metadata_batch *batch = + &call->metadata_batch[0 /* is_receiving */][is_trailing]; + if (prepend_extra_metadata) { + if (call->send_extra_metadata_count == 0) { + prepend_extra_metadata = 0; + } else { + for (i = 0; i < call->send_extra_metadata_count; i++) { + GRPC_MDELEM_REF(call->send_extra_metadata[i].md); + } + for (i = 1; i < call->send_extra_metadata_count; i++) { + call->send_extra_metadata[i].prev = &call->send_extra_metadata[i - 1]; + } + for (i = 0; i < call->send_extra_metadata_count - 1; i++) { + call->send_extra_metadata[i].next = &call->send_extra_metadata[i + 1]; + } + } + } + for (i = 0; i < count; i++) { + grpc_metadata *md = &metadata[i]; + grpc_linked_mdelem *l = (grpc_linked_mdelem *)&md->internal_data; + GPR_ASSERT(sizeof(grpc_linked_mdelem) == sizeof(md->internal_data)); + l->md = grpc_mdelem_from_string_and_buffer( + md->key, (const uint8_t *)md->value, md->value_length); + if (!grpc_header_key_is_legal(grpc_mdstr_as_c_string(l->md->key), + GRPC_MDSTR_LENGTH(l->md->key))) { + gpr_log(GPR_ERROR, "attempt to send invalid metadata key: %s", + grpc_mdstr_as_c_string(l->md->key)); + return 0; + } else if (!grpc_is_binary_header(grpc_mdstr_as_c_string(l->md->key), + GRPC_MDSTR_LENGTH(l->md->key)) && + !grpc_header_nonbin_value_is_legal( + grpc_mdstr_as_c_string(l->md->value), + GRPC_MDSTR_LENGTH(l->md->value))) { + gpr_log(GPR_ERROR, "attempt to send invalid metadata value"); + return 0; + } + } + for (i = 1; i < count; i++) { + linked_from_md(&metadata[i])->prev = linked_from_md(&metadata[i - 1]); + } + for (i = 0; i < count - 1; i++) { + linked_from_md(&metadata[i])->next = linked_from_md(&metadata[i + 1]); + } + switch (prepend_extra_metadata * 2 + (count != 0)) { + case 0: + /* no prepend, no metadata => nothing to do */ + batch->list.head = batch->list.tail = NULL; + break; + case 1: + /* metadata, but no prepend */ + batch->list.head = linked_from_md(&metadata[0]); + batch->list.tail = linked_from_md(&metadata[count - 1]); + batch->list.head->prev = NULL; + batch->list.tail->next = NULL; + break; + case 2: + /* prepend, but no md */ + batch->list.head = &call->send_extra_metadata[0]; + batch->list.tail = + &call->send_extra_metadata[call->send_extra_metadata_count - 1]; + batch->list.head->prev = NULL; + batch->list.tail->next = NULL; + break; + case 3: + /* prepend AND md */ + batch->list.head = &call->send_extra_metadata[0]; + call->send_extra_metadata[call->send_extra_metadata_count - 1].next = + linked_from_md(&metadata[0]); + linked_from_md(&metadata[0])->prev = + &call->send_extra_metadata[call->send_extra_metadata_count - 1]; + batch->list.tail = linked_from_md(&metadata[count - 1]); + batch->list.head->prev = NULL; + batch->list.tail->next = NULL; + break; + default: + GPR_UNREACHABLE_CODE(return 0); + } + + return 1; +} + +void grpc_call_destroy(grpc_call *c) { + int cancel; + grpc_call *parent = c->parent; + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + + GPR_TIMER_BEGIN("grpc_call_destroy", 0); + GRPC_API_TRACE("grpc_call_destroy(c=%p)", 1, (c)); + + if (parent) { + gpr_mu_lock(&parent->mu); + if (c == parent->first_child) { + parent->first_child = c->sibling_next; + if (c == parent->first_child) { + parent->first_child = NULL; + } + c->sibling_prev->sibling_next = c->sibling_next; + c->sibling_next->sibling_prev = c->sibling_prev; + } + gpr_mu_unlock(&parent->mu); + GRPC_CALL_INTERNAL_UNREF(&exec_ctx, parent, "child"); + } + + gpr_mu_lock(&c->mu); + GPR_ASSERT(!c->destroy_called); + c->destroy_called = 1; + if (c->have_alarm) { + grpc_timer_cancel(&exec_ctx, &c->alarm); + } + cancel = !c->received_final_op; + gpr_mu_unlock(&c->mu); + if (cancel) grpc_call_cancel(c, NULL); + GRPC_CALL_INTERNAL_UNREF(&exec_ctx, c, "destroy"); + grpc_exec_ctx_finish(&exec_ctx); + GPR_TIMER_END("grpc_call_destroy", 0); +} + +grpc_call_error grpc_call_cancel(grpc_call *call, void *reserved) { + GRPC_API_TRACE("grpc_call_cancel(call=%p, reserved=%p)", 2, (call, reserved)); + GPR_ASSERT(!reserved); + return grpc_call_cancel_with_status(call, GRPC_STATUS_CANCELLED, "Cancelled", + NULL); +} + +grpc_call_error grpc_call_cancel_with_status(grpc_call *c, + grpc_status_code status, + const char *description, + void *reserved) { + grpc_call_error r; + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + GRPC_API_TRACE( + "grpc_call_cancel_with_status(" + "c=%p, status=%d, description=%s, reserved=%p)", + 4, (c, (int)status, description, reserved)); + GPR_ASSERT(reserved == NULL); + gpr_mu_lock(&c->mu); + r = cancel_with_status(&exec_ctx, c, status, description); + gpr_mu_unlock(&c->mu); + grpc_exec_ctx_finish(&exec_ctx); + return r; +} + +typedef struct cancel_closure { + grpc_closure closure; + grpc_call *call; + grpc_status_code status; +} cancel_closure; + +static void done_cancel(grpc_exec_ctx *exec_ctx, void *ccp, bool success) { + cancel_closure *cc = ccp; + GRPC_CALL_INTERNAL_UNREF(exec_ctx, cc->call, "cancel"); + gpr_free(cc); +} + +static void send_cancel(grpc_exec_ctx *exec_ctx, void *ccp, bool success) { + grpc_transport_stream_op op; + cancel_closure *cc = ccp; + memset(&op, 0, sizeof(op)); + op.cancel_with_status = cc->status; + /* reuse closure to catch completion */ + grpc_closure_init(&cc->closure, done_cancel, cc); + op.on_complete = &cc->closure; + execute_op(exec_ctx, cc->call, &op); +} + +static grpc_call_error cancel_with_status(grpc_exec_ctx *exec_ctx, grpc_call *c, + grpc_status_code status, + const char *description) { + grpc_mdstr *details = + description ? grpc_mdstr_from_string(description) : NULL; + cancel_closure *cc = gpr_malloc(sizeof(*cc)); + + GPR_ASSERT(status != GRPC_STATUS_OK); + + set_status_code(c, STATUS_FROM_API_OVERRIDE, (uint32_t)status); + set_status_details(c, STATUS_FROM_API_OVERRIDE, details); + + grpc_closure_init(&cc->closure, send_cancel, cc); + cc->call = c; + cc->status = status; + GRPC_CALL_INTERNAL_REF(c, "cancel"); + grpc_exec_ctx_enqueue(exec_ctx, &cc->closure, true, NULL); + + return GRPC_CALL_OK; +} + +static void execute_op(grpc_exec_ctx *exec_ctx, grpc_call *call, + grpc_transport_stream_op *op) { + grpc_call_element *elem; + + GPR_TIMER_BEGIN("execute_op", 0); + elem = CALL_ELEM_FROM_CALL(call, 0); + op->context = call->context; + elem->filter->start_transport_stream_op(exec_ctx, elem, op); + GPR_TIMER_END("execute_op", 0); +} + +char *grpc_call_get_peer(grpc_call *call) { + grpc_call_element *elem = CALL_ELEM_FROM_CALL(call, 0); + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + char *result; + GRPC_API_TRACE("grpc_call_get_peer(%p)", 1, (call)); + result = elem->filter->get_peer(&exec_ctx, elem); + if (result == NULL) { + result = grpc_channel_get_target(call->channel); + } + if (result == NULL) { + result = gpr_strdup("unknown"); + } + grpc_exec_ctx_finish(&exec_ctx); + return result; +} + +grpc_call *grpc_call_from_top_element(grpc_call_element *elem) { + return CALL_FROM_TOP_ELEM(elem); +} + +static void call_alarm(grpc_exec_ctx *exec_ctx, void *arg, bool success) { + grpc_call *call = arg; + gpr_mu_lock(&call->mu); + call->have_alarm = 0; + if (success) { + cancel_with_status(exec_ctx, call, GRPC_STATUS_DEADLINE_EXCEEDED, + "Deadline Exceeded"); + } + gpr_mu_unlock(&call->mu); + GRPC_CALL_INTERNAL_UNREF(exec_ctx, call, "alarm"); +} + +static void set_deadline_alarm(grpc_exec_ctx *exec_ctx, grpc_call *call, + gpr_timespec deadline) { + if (call->have_alarm) { + gpr_log(GPR_ERROR, "Attempt to set deadline alarm twice"); + assert(0); + return; + } + GRPC_CALL_INTERNAL_REF(call, "alarm"); + call->have_alarm = 1; + call->send_deadline = gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC); + grpc_timer_init(exec_ctx, &call->alarm, call->send_deadline, call_alarm, call, + gpr_now(GPR_CLOCK_MONOTONIC)); +} + +/* we offset status by a small amount when storing it into transport metadata + as metadata cannot store a 0 value (which is used as OK for grpc_status_codes + */ +#define STATUS_OFFSET 1 +static void destroy_status(void *ignored) {} + +static uint32_t decode_status(grpc_mdelem *md) { + uint32_t status; + void *user_data; + if (md == GRPC_MDELEM_GRPC_STATUS_0) return 0; + if (md == GRPC_MDELEM_GRPC_STATUS_1) return 1; + if (md == GRPC_MDELEM_GRPC_STATUS_2) return 2; + user_data = grpc_mdelem_get_user_data(md, destroy_status); + if (user_data != NULL) { + status = ((uint32_t)(intptr_t)user_data) - STATUS_OFFSET; + } else { + if (!gpr_parse_bytes_to_uint32(grpc_mdstr_as_c_string(md->value), + GPR_SLICE_LENGTH(md->value->slice), + &status)) { + status = GRPC_STATUS_UNKNOWN; /* could not parse status code */ + } + grpc_mdelem_set_user_data(md, destroy_status, + (void *)(intptr_t)(status + STATUS_OFFSET)); + } + return status; +} + +static uint32_t decode_compression(grpc_mdelem *md) { + grpc_compression_algorithm algorithm = + grpc_compression_algorithm_from_mdstr(md->value); + if (algorithm == GRPC_COMPRESS_ALGORITHMS_COUNT) { + const char *md_c_str = grpc_mdstr_as_c_string(md->value); + gpr_log(GPR_ERROR, "Invalid compression algorithm: '%s'", md_c_str); + } + return algorithm; +} + +static grpc_mdelem *recv_common_filter(grpc_call *call, grpc_mdelem *elem) { + if (elem->key == GRPC_MDSTR_GRPC_STATUS) { + GPR_TIMER_BEGIN("status", 0); + set_status_code(call, STATUS_FROM_WIRE, decode_status(elem)); + GPR_TIMER_END("status", 0); + return NULL; + } else if (elem->key == GRPC_MDSTR_GRPC_MESSAGE) { + GPR_TIMER_BEGIN("status-details", 0); + set_status_details(call, STATUS_FROM_WIRE, GRPC_MDSTR_REF(elem->value)); + GPR_TIMER_END("status-details", 0); + return NULL; + } + return elem; +} + +static grpc_mdelem *publish_app_metadata(grpc_call *call, grpc_mdelem *elem, + int is_trailing) { + grpc_metadata_array *dest; + grpc_metadata *mdusr; + GPR_TIMER_BEGIN("publish_app_metadata", 0); + dest = call->buffered_metadata[is_trailing]; + if (dest->count == dest->capacity) { + dest->capacity = GPR_MAX(dest->capacity + 8, dest->capacity * 2); + dest->metadata = + gpr_realloc(dest->metadata, sizeof(grpc_metadata) * dest->capacity); + } + mdusr = &dest->metadata[dest->count++]; + mdusr->key = grpc_mdstr_as_c_string(elem->key); + mdusr->value = grpc_mdstr_as_c_string(elem->value); + mdusr->value_length = GPR_SLICE_LENGTH(elem->value->slice); + GPR_TIMER_END("publish_app_metadata", 0); + return elem; +} + +static grpc_mdelem *recv_initial_filter(void *callp, grpc_mdelem *elem) { + grpc_call *call = callp; + elem = recv_common_filter(call, elem); + if (elem == NULL) { + return NULL; + } else if (elem->key == GRPC_MDSTR_GRPC_ENCODING) { + GPR_TIMER_BEGIN("compression_algorithm", 0); + set_compression_algorithm(call, decode_compression(elem)); + GPR_TIMER_END("compression_algorithm", 0); + return NULL; + } else if (elem->key == GRPC_MDSTR_GRPC_ACCEPT_ENCODING) { + GPR_TIMER_BEGIN("encodings_accepted_by_peer", 0); + set_encodings_accepted_by_peer(call, elem); + GPR_TIMER_END("encodings_accepted_by_peer", 0); + return NULL; + } else { + return publish_app_metadata(call, elem, 0); + } +} + +static grpc_mdelem *recv_trailing_filter(void *callp, grpc_mdelem *elem) { + grpc_call *call = callp; + elem = recv_common_filter(call, elem); + if (elem == NULL) { + return NULL; + } else { + return publish_app_metadata(call, elem, 1); + } +} + +grpc_call_stack *grpc_call_get_call_stack(grpc_call *call) { + return CALL_STACK_FROM_CALL(call); +} + +/* + * BATCH API IMPLEMENTATION + */ + +static void set_status_value_directly(grpc_status_code status, void *dest) { + *(grpc_status_code *)dest = status; +} + +static void set_cancelled_value(grpc_status_code status, void *dest) { + *(int *)dest = (status != GRPC_STATUS_OK); +} + +static int are_write_flags_valid(uint32_t flags) { + /* check that only bits in GRPC_WRITE_(INTERNAL?)_USED_MASK are set */ + const uint32_t allowed_write_positions = + (GRPC_WRITE_USED_MASK | GRPC_WRITE_INTERNAL_USED_MASK); + const uint32_t invalid_positions = ~allowed_write_positions; + return !(flags & invalid_positions); +} + +static batch_control *allocate_batch_control(grpc_call *call) { + size_t i; + for (i = 0; i < MAX_CONCURRENT_BATCHES; i++) { + if ((call->used_batches & (1 << i)) == 0) { + call->used_batches = (uint8_t)(call->used_batches | (uint8_t)(1 << i)); + return &call->active_batches[i]; + } + } + return NULL; +} + +static void finish_batch_completion(grpc_exec_ctx *exec_ctx, void *user_data, + grpc_cq_completion *storage) { + batch_control *bctl = user_data; + grpc_call *call = bctl->call; + gpr_mu_lock(&call->mu); + call->used_batches = (uint8_t)( + call->used_batches & ~(uint8_t)(1 << (bctl - call->active_batches))); + gpr_mu_unlock(&call->mu); + GRPC_CALL_INTERNAL_UNREF(exec_ctx, call, "completion"); +} + +static void post_batch_completion(grpc_exec_ctx *exec_ctx, + batch_control *bctl) { + grpc_call *call = bctl->call; + if (bctl->is_notify_tag_closure) { + grpc_exec_ctx_enqueue(exec_ctx, bctl->notify_tag, bctl->success, NULL); + gpr_mu_lock(&call->mu); + bctl->call->used_batches = + (uint8_t)(bctl->call->used_batches & + ~(uint8_t)(1 << (bctl - bctl->call->active_batches))); + gpr_mu_unlock(&call->mu); + GRPC_CALL_INTERNAL_UNREF(exec_ctx, call, "completion"); + } else { + grpc_cq_end_op(exec_ctx, bctl->call->cq, bctl->notify_tag, bctl->success, + finish_batch_completion, bctl, &bctl->cq_completion); + } +} + +static void continue_receiving_slices(grpc_exec_ctx *exec_ctx, + batch_control *bctl) { + grpc_call *call = bctl->call; + for (;;) { + size_t remaining = call->receiving_stream->length - + (*call->receiving_buffer)->data.raw.slice_buffer.length; + if (remaining == 0) { + call->receiving_message = 0; + grpc_byte_stream_destroy(exec_ctx, call->receiving_stream); + call->receiving_stream = NULL; + if (gpr_unref(&bctl->steps_to_complete)) { + post_batch_completion(exec_ctx, bctl); + } + return; + } + if (grpc_byte_stream_next(exec_ctx, call->receiving_stream, + &call->receiving_slice, remaining, + &call->receiving_slice_ready)) { + gpr_slice_buffer_add(&(*call->receiving_buffer)->data.raw.slice_buffer, + call->receiving_slice); + } else { + return; + } + } +} + +static void receiving_slice_ready(grpc_exec_ctx *exec_ctx, void *bctlp, + bool success) { + batch_control *bctl = bctlp; + grpc_call *call = bctl->call; + + if (success) { + gpr_slice_buffer_add(&(*call->receiving_buffer)->data.raw.slice_buffer, + call->receiving_slice); + continue_receiving_slices(exec_ctx, bctl); + } else { + grpc_byte_stream_destroy(exec_ctx, call->receiving_stream); + call->receiving_stream = NULL; + grpc_byte_buffer_destroy(*call->receiving_buffer); + *call->receiving_buffer = NULL; + if (gpr_unref(&bctl->steps_to_complete)) { + post_batch_completion(exec_ctx, bctl); + } + } +} + +static void process_data_after_md(grpc_exec_ctx *exec_ctx, batch_control *bctl, + bool success) { + grpc_call *call = bctl->call; + if (call->receiving_stream == NULL) { + *call->receiving_buffer = NULL; + call->receiving_message = 0; + if (gpr_unref(&bctl->steps_to_complete)) { + post_batch_completion(exec_ctx, bctl); + } + } else if (call->receiving_stream->length > + grpc_channel_get_max_message_length(call->channel)) { + cancel_with_status(exec_ctx, call, GRPC_STATUS_INTERNAL, + "Max message size exceeded"); + grpc_byte_stream_destroy(exec_ctx, call->receiving_stream); + call->receiving_stream = NULL; + *call->receiving_buffer = NULL; + call->receiving_message = 0; + if (gpr_unref(&bctl->steps_to_complete)) { + post_batch_completion(exec_ctx, bctl); + } + } else { + call->test_only_last_message_flags = call->receiving_stream->flags; + if ((call->receiving_stream->flags & GRPC_WRITE_INTERNAL_COMPRESS) && + (call->compression_algorithm > GRPC_COMPRESS_NONE)) { + *call->receiving_buffer = grpc_raw_compressed_byte_buffer_create( + NULL, 0, call->compression_algorithm); + } else { + *call->receiving_buffer = grpc_raw_byte_buffer_create(NULL, 0); + } + grpc_closure_init(&call->receiving_slice_ready, receiving_slice_ready, + bctl); + continue_receiving_slices(exec_ctx, bctl); + /* early out */ + return; + } +} + +static void receiving_stream_ready(grpc_exec_ctx *exec_ctx, void *bctlp, + bool success) { + batch_control *bctl = bctlp; + grpc_call *call = bctl->call; + + gpr_mu_lock(&bctl->call->mu); + if (bctl->call->has_initial_md_been_received) { + gpr_mu_unlock(&bctl->call->mu); + process_data_after_md(exec_ctx, bctlp, success); + } else { + call->saved_receiving_stream_ready_ctx.bctlp = bctlp; + call->saved_receiving_stream_ready_ctx.success = success; + gpr_mu_unlock(&bctl->call->mu); + } +} + +static void receiving_initial_metadata_ready(grpc_exec_ctx *exec_ctx, + void *bctlp, bool success) { + batch_control *bctl = bctlp; + grpc_call *call = bctl->call; + + gpr_mu_lock(&call->mu); + + grpc_metadata_batch *md = + &call->metadata_batch[1 /* is_receiving */][0 /* is_trailing */]; + grpc_metadata_batch_filter(md, recv_initial_filter, call); + call->has_initial_md_been_received = true; + + if (gpr_time_cmp(md->deadline, gpr_inf_future(md->deadline.clock_type)) != + 0 && + !call->is_client) { + GPR_TIMER_BEGIN("set_deadline_alarm", 0); + set_deadline_alarm(exec_ctx, call, md->deadline); + GPR_TIMER_END("set_deadline_alarm", 0); + } + + if (call->saved_receiving_stream_ready_ctx.bctlp != NULL) { + grpc_closure *saved_rsr_closure = grpc_closure_create( + receiving_stream_ready, call->saved_receiving_stream_ready_ctx.bctlp); + grpc_exec_ctx_enqueue(exec_ctx, saved_rsr_closure, + call->saved_receiving_stream_ready_ctx.success, NULL); + call->saved_receiving_stream_ready_ctx.bctlp = NULL; + } + + gpr_mu_unlock(&call->mu); + + if (gpr_unref(&bctl->steps_to_complete)) { + post_batch_completion(exec_ctx, bctl); + } +} + +static void finish_batch(grpc_exec_ctx *exec_ctx, void *bctlp, bool success) { + batch_control *bctl = bctlp; + grpc_call *call = bctl->call; + grpc_call *child_call; + grpc_call *next_child_call; + + gpr_mu_lock(&call->mu); + if (bctl->send_initial_metadata) { + grpc_metadata_batch_destroy( + &call->metadata_batch[0 /* is_receiving */][0 /* is_trailing */]); + } + if (bctl->send_message) { + call->sending_message = 0; + } + if (bctl->send_final_op) { + grpc_metadata_batch_destroy( + &call->metadata_batch[0 /* is_receiving */][1 /* is_trailing */]); + } + if (bctl->recv_final_op) { + grpc_metadata_batch *md = + &call->metadata_batch[1 /* is_receiving */][1 /* is_trailing */]; + grpc_metadata_batch_filter(md, recv_trailing_filter, call); + + if (call->have_alarm) { + grpc_timer_cancel(exec_ctx, &call->alarm); + } + /* propagate cancellation to any interested children */ + child_call = call->first_child; + if (child_call != NULL) { + do { + next_child_call = child_call->sibling_next; + if (child_call->cancellation_is_inherited) { + GRPC_CALL_INTERNAL_REF(child_call, "propagate_cancel"); + grpc_call_cancel(child_call, NULL); + GRPC_CALL_INTERNAL_UNREF(exec_ctx, child_call, "propagate_cancel"); + } + child_call = next_child_call; + } while (child_call != call->first_child); + } + + if (call->is_client) { + get_final_status(call, set_status_value_directly, + call->final_op.client.status); + get_final_details(call, call->final_op.client.status_details, + call->final_op.client.status_details_capacity); + } else { + get_final_status(call, set_cancelled_value, + call->final_op.server.cancelled); + } + + success = 1; + } + bctl->success = success != 0; + gpr_mu_unlock(&call->mu); + if (gpr_unref(&bctl->steps_to_complete)) { + post_batch_completion(exec_ctx, bctl); + } +} + +static grpc_call_error call_start_batch(grpc_exec_ctx *exec_ctx, + grpc_call *call, const grpc_op *ops, + size_t nops, void *notify_tag, + int is_notify_tag_closure) { + grpc_transport_stream_op stream_op; + size_t i; + const grpc_op *op; + batch_control *bctl; + int num_completion_callbacks_needed = 1; + grpc_call_error error = GRPC_CALL_OK; + + GPR_TIMER_BEGIN("grpc_call_start_batch", 0); + + GRPC_CALL_LOG_BATCH(GPR_INFO, call, ops, nops, notify_tag); + + memset(&stream_op, 0, sizeof(stream_op)); + + /* TODO(ctiller): this feels like it could be made lock-free */ + gpr_mu_lock(&call->mu); + bctl = allocate_batch_control(call); + memset(bctl, 0, sizeof(*bctl)); + bctl->call = call; + bctl->notify_tag = notify_tag; + bctl->is_notify_tag_closure = (uint8_t)(is_notify_tag_closure != 0); + + if (nops == 0) { + GRPC_CALL_INTERNAL_REF(call, "completion"); + bctl->success = 1; + if (!is_notify_tag_closure) { + grpc_cq_begin_op(call->cq, notify_tag); + } + gpr_mu_unlock(&call->mu); + post_batch_completion(exec_ctx, bctl); + error = GRPC_CALL_OK; + goto done; + } + + /* rewrite batch ops into a transport op */ + for (i = 0; i < nops; i++) { + op = &ops[i]; + if (op->reserved != NULL) { + error = GRPC_CALL_ERROR; + goto done_with_error; + } + switch (op->op) { + case GRPC_OP_SEND_INITIAL_METADATA: + /* Flag validation: currently allow no flags */ + if (op->flags != 0) { + error = GRPC_CALL_ERROR_INVALID_FLAGS; + goto done_with_error; + } + if (call->sent_initial_metadata) { + error = GRPC_CALL_ERROR_TOO_MANY_OPERATIONS; + goto done_with_error; + } + if (op->data.send_initial_metadata.count > INT_MAX) { + error = GRPC_CALL_ERROR_INVALID_METADATA; + goto done_with_error; + } + bctl->send_initial_metadata = 1; + call->sent_initial_metadata = 1; + if (!prepare_application_metadata( + call, (int)op->data.send_initial_metadata.count, + op->data.send_initial_metadata.metadata, 0, call->is_client)) { + error = GRPC_CALL_ERROR_INVALID_METADATA; + goto done_with_error; + } + /* TODO(ctiller): just make these the same variable? */ + call->metadata_batch[0][0].deadline = call->send_deadline; + stream_op.send_initial_metadata = + &call->metadata_batch[0 /* is_receiving */][0 /* is_trailing */]; + break; + case GRPC_OP_SEND_MESSAGE: + if (!are_write_flags_valid(op->flags)) { + error = GRPC_CALL_ERROR_INVALID_FLAGS; + goto done_with_error; + } + if (op->data.send_message == NULL) { + error = GRPC_CALL_ERROR_INVALID_MESSAGE; + goto done_with_error; + } + if (call->sending_message) { + error = GRPC_CALL_ERROR_TOO_MANY_OPERATIONS; + goto done_with_error; + } + bctl->send_message = 1; + call->sending_message = 1; + grpc_slice_buffer_stream_init( + &call->sending_stream, + &op->data.send_message->data.raw.slice_buffer, op->flags); + stream_op.send_message = &call->sending_stream.base; + break; + case GRPC_OP_SEND_CLOSE_FROM_CLIENT: + /* Flag validation: currently allow no flags */ + if (op->flags != 0) { + error = GRPC_CALL_ERROR_INVALID_FLAGS; + goto done_with_error; + } + if (!call->is_client) { + error = GRPC_CALL_ERROR_NOT_ON_SERVER; + goto done_with_error; + } + if (call->sent_final_op) { + error = GRPC_CALL_ERROR_TOO_MANY_OPERATIONS; + goto done_with_error; + } + bctl->send_final_op = 1; + call->sent_final_op = 1; + stream_op.send_trailing_metadata = + &call->metadata_batch[0 /* is_receiving */][1 /* is_trailing */]; + break; + case GRPC_OP_SEND_STATUS_FROM_SERVER: + /* Flag validation: currently allow no flags */ + if (op->flags != 0) { + error = GRPC_CALL_ERROR_INVALID_FLAGS; + goto done_with_error; + } + if (call->is_client) { + error = GRPC_CALL_ERROR_NOT_ON_CLIENT; + goto done_with_error; + } + if (call->sent_final_op) { + error = GRPC_CALL_ERROR_TOO_MANY_OPERATIONS; + goto done_with_error; + } + if (op->data.send_status_from_server.trailing_metadata_count > + INT_MAX) { + error = GRPC_CALL_ERROR_INVALID_METADATA; + goto done_with_error; + } + bctl->send_final_op = 1; + call->sent_final_op = 1; + call->send_extra_metadata_count = 1; + call->send_extra_metadata[0].md = grpc_channel_get_reffed_status_elem( + call->channel, op->data.send_status_from_server.status); + if (op->data.send_status_from_server.status_details != NULL) { + call->send_extra_metadata[1].md = grpc_mdelem_from_metadata_strings( + GRPC_MDSTR_GRPC_MESSAGE, + grpc_mdstr_from_string( + op->data.send_status_from_server.status_details)); + call->send_extra_metadata_count++; + set_status_details( + call, STATUS_FROM_API_OVERRIDE, + GRPC_MDSTR_REF(call->send_extra_metadata[1].md->value)); + } + set_status_code(call, STATUS_FROM_API_OVERRIDE, + (uint32_t)op->data.send_status_from_server.status); + if (!prepare_application_metadata( + call, + (int)op->data.send_status_from_server.trailing_metadata_count, + op->data.send_status_from_server.trailing_metadata, 1, 1)) { + error = GRPC_CALL_ERROR_INVALID_METADATA; + goto done_with_error; + } + stream_op.send_trailing_metadata = + &call->metadata_batch[0 /* is_receiving */][1 /* is_trailing */]; + break; + case GRPC_OP_RECV_INITIAL_METADATA: + /* Flag validation: currently allow no flags */ + if (op->flags != 0) { + error = GRPC_CALL_ERROR_INVALID_FLAGS; + goto done_with_error; + } + if (call->received_initial_metadata) { + error = GRPC_CALL_ERROR_TOO_MANY_OPERATIONS; + goto done_with_error; + } + call->received_initial_metadata = 1; + call->buffered_metadata[0] = op->data.recv_initial_metadata; + grpc_closure_init(&call->receiving_initial_metadata_ready, + receiving_initial_metadata_ready, bctl); + bctl->recv_initial_metadata = 1; + stream_op.recv_initial_metadata = + &call->metadata_batch[1 /* is_receiving */][0 /* is_trailing */]; + stream_op.recv_initial_metadata_ready = + &call->receiving_initial_metadata_ready; + num_completion_callbacks_needed++; + break; + case GRPC_OP_RECV_MESSAGE: + /* Flag validation: currently allow no flags */ + if (op->flags != 0) { + error = GRPC_CALL_ERROR_INVALID_FLAGS; + goto done_with_error; + } + if (call->receiving_message) { + error = GRPC_CALL_ERROR_TOO_MANY_OPERATIONS; + goto done_with_error; + } + call->receiving_message = 1; + bctl->recv_message = 1; + call->receiving_buffer = op->data.recv_message; + stream_op.recv_message = &call->receiving_stream; + grpc_closure_init(&call->receiving_stream_ready, receiving_stream_ready, + bctl); + stream_op.recv_message_ready = &call->receiving_stream_ready; + num_completion_callbacks_needed++; + break; + case GRPC_OP_RECV_STATUS_ON_CLIENT: + /* Flag validation: currently allow no flags */ + if (op->flags != 0) { + error = GRPC_CALL_ERROR_INVALID_FLAGS; + goto done_with_error; + } + if (!call->is_client) { + error = GRPC_CALL_ERROR_NOT_ON_SERVER; + goto done_with_error; + } + if (call->received_final_op) { + error = GRPC_CALL_ERROR_TOO_MANY_OPERATIONS; + goto done_with_error; + } + call->received_final_op = 1; + call->buffered_metadata[1] = + op->data.recv_status_on_client.trailing_metadata; + call->final_op.client.status = op->data.recv_status_on_client.status; + call->final_op.client.status_details = + op->data.recv_status_on_client.status_details; + call->final_op.client.status_details_capacity = + op->data.recv_status_on_client.status_details_capacity; + bctl->recv_final_op = 1; + stream_op.recv_trailing_metadata = + &call->metadata_batch[1 /* is_receiving */][1 /* is_trailing */]; + break; + case GRPC_OP_RECV_CLOSE_ON_SERVER: + /* Flag validation: currently allow no flags */ + if (op->flags != 0) { + error = GRPC_CALL_ERROR_INVALID_FLAGS; + goto done_with_error; + } + if (call->is_client) { + error = GRPC_CALL_ERROR_NOT_ON_CLIENT; + goto done_with_error; + } + if (call->received_final_op) { + error = GRPC_CALL_ERROR_TOO_MANY_OPERATIONS; + goto done_with_error; + } + call->received_final_op = 1; + call->final_op.server.cancelled = + op->data.recv_close_on_server.cancelled; + bctl->recv_final_op = 1; + stream_op.recv_trailing_metadata = + &call->metadata_batch[1 /* is_receiving */][1 /* is_trailing */]; + break; + } + } + + GRPC_CALL_INTERNAL_REF(call, "completion"); + if (!is_notify_tag_closure) { + grpc_cq_begin_op(call->cq, notify_tag); + } + gpr_ref_init(&bctl->steps_to_complete, num_completion_callbacks_needed); + + stream_op.context = call->context; + grpc_closure_init(&bctl->finish_batch, finish_batch, bctl); + stream_op.on_complete = &bctl->finish_batch; + gpr_mu_unlock(&call->mu); + + execute_op(exec_ctx, call, &stream_op); + +done: + GPR_TIMER_END("grpc_call_start_batch", 0); + return error; + +done_with_error: + /* reverse any mutations that occured */ + if (bctl->send_initial_metadata) { + call->sent_initial_metadata = 0; + grpc_metadata_batch_clear(&call->metadata_batch[0][0]); + } + if (bctl->send_message) { + call->sending_message = 0; + grpc_byte_stream_destroy(exec_ctx, &call->sending_stream.base); + } + if (bctl->send_final_op) { + call->sent_final_op = 0; + grpc_metadata_batch_clear(&call->metadata_batch[0][1]); + } + if (bctl->recv_initial_metadata) { + call->received_initial_metadata = 0; + } + if (bctl->recv_message) { + call->receiving_message = 0; + } + if (bctl->recv_final_op) { + call->received_final_op = 0; + } + gpr_mu_unlock(&call->mu); + goto done; +} + +grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops, + size_t nops, void *tag, void *reserved) { + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + grpc_call_error err; + + GRPC_API_TRACE( + "grpc_call_start_batch(call=%p, ops=%p, nops=%lu, tag=%p, reserved=%p)", + 5, (call, ops, (unsigned long)nops, tag, reserved)); + + if (reserved != NULL) { + err = GRPC_CALL_ERROR; + } else { + err = call_start_batch(&exec_ctx, call, ops, nops, tag, 0); + } + + grpc_exec_ctx_finish(&exec_ctx); + return err; +} + +grpc_call_error grpc_call_start_batch_and_execute(grpc_exec_ctx *exec_ctx, + grpc_call *call, + const grpc_op *ops, + size_t nops, + grpc_closure *closure) { + return call_start_batch(exec_ctx, call, ops, nops, closure, 1); +} + +void grpc_call_context_set(grpc_call *call, grpc_context_index elem, + void *value, void (*destroy)(void *value)) { + if (call->context[elem].destroy) { + call->context[elem].destroy(call->context[elem].value); + } + call->context[elem].value = value; + call->context[elem].destroy = destroy; +} + +void *grpc_call_context_get(grpc_call *call, grpc_context_index elem) { + return call->context[elem].value; +} + +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) { + gpr_mu_lock(&call->mu); + const uint32_t accepted_encodings = call->encodings_accepted_by_peer; + gpr_mu_unlock(&call->mu); + return grpc_compression_algorithm_for_level(level, accepted_encodings); +} diff --git a/src/core/lib/surface/call.h b/src/core/lib/surface/call.h new file mode 100644 index 0000000000..e2e75865be --- /dev/null +++ b/src/core/lib/surface/call.h @@ -0,0 +1,116 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SURFACE_CALL_H +#define GRPC_CORE_LIB_SURFACE_CALL_H + +#include "src/core/lib/channel/channel_stack.h" +#include "src/core/lib/channel/context.h" +#include "src/core/lib/surface/api_trace.h" +#include "src/core/lib/surface/surface_trace.h" + +#include <grpc/grpc.h> +#include <grpc/impl/codegen/compression_types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*grpc_ioreq_completion_func)(grpc_exec_ctx *exec_ctx, + grpc_call *call, int success, + void *user_data); + +grpc_call *grpc_call_create(grpc_channel *channel, grpc_call *parent_call, + uint32_t propagation_mask, + grpc_completion_queue *cq, + const void *server_transport_data, + grpc_mdelem **add_initial_metadata, + size_t add_initial_metadata_count, + gpr_timespec send_deadline); + +void grpc_call_set_completion_queue(grpc_exec_ctx *exec_ctx, grpc_call *call, + grpc_completion_queue *cq); + +#ifdef GRPC_STREAM_REFCOUNT_DEBUG +void grpc_call_internal_ref(grpc_call *call, const char *reason); +void grpc_call_internal_unref(grpc_exec_ctx *exec_ctx, grpc_call *call, + const char *reason); +#define GRPC_CALL_INTERNAL_REF(call, reason) \ + grpc_call_internal_ref(call, reason) +#define GRPC_CALL_INTERNAL_UNREF(exec_ctx, call, reason) \ + grpc_call_internal_unref(exec_ctx, call, reason) +#else +void grpc_call_internal_ref(grpc_call *call); +void grpc_call_internal_unref(grpc_exec_ctx *exec_ctx, grpc_call *call); +#define GRPC_CALL_INTERNAL_REF(call, reason) grpc_call_internal_ref(call) +#define GRPC_CALL_INTERNAL_UNREF(exec_ctx, call, reason) \ + grpc_call_internal_unref(exec_ctx, call) +#endif + +grpc_call_stack *grpc_call_get_call_stack(grpc_call *call); + +grpc_call_error grpc_call_start_batch_and_execute(grpc_exec_ctx *exec_ctx, + grpc_call *call, + const grpc_op *ops, + size_t nops, + grpc_closure *closure); + +/* Given the top call_element, get the call object. */ +grpc_call *grpc_call_from_top_element(grpc_call_element *surface_element); + +void grpc_call_log_batch(char *file, int line, gpr_log_severity severity, + grpc_call *call, const grpc_op *ops, size_t nops, + void *tag); + +/* Set a context pointer. + No thread safety guarantees are made wrt this value. */ +void grpc_call_context_set(grpc_call *call, grpc_context_index elem, + void *value, void (*destroy)(void *value)); +/* Get a context pointer. */ +void *grpc_call_context_get(grpc_call *call, grpc_context_index elem); + +#define GRPC_CALL_LOG_BATCH(sev, call, ops, nops, tag) \ + if (grpc_api_trace) grpc_call_log_batch(sev, call, ops, nops, tag) + +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 + +#endif /* GRPC_CORE_LIB_SURFACE_CALL_H */ diff --git a/src/core/lib/surface/call_details.c b/src/core/lib/surface/call_details.c new file mode 100644 index 0000000000..08f606d84a --- /dev/null +++ b/src/core/lib/surface/call_details.c @@ -0,0 +1,50 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/grpc.h> +#include <grpc/support/alloc.h> + +#include <string.h> + +#include "src/core/lib/surface/api_trace.h" + +void grpc_call_details_init(grpc_call_details* cd) { + GRPC_API_TRACE("grpc_call_details_init(cd=%p)", 1, (cd)); + memset(cd, 0, sizeof(*cd)); +} + +void grpc_call_details_destroy(grpc_call_details* cd) { + GRPC_API_TRACE("grpc_call_details_destroy(cd=%p)", 1, (cd)); + gpr_free(cd->method); + gpr_free(cd->host); +} diff --git a/src/core/lib/surface/call_log_batch.c b/src/core/lib/surface/call_log_batch.c new file mode 100644 index 0000000000..bc5a2ffb65 --- /dev/null +++ b/src/core/lib/surface/call_log_batch.c @@ -0,0 +1,118 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/surface/call.h" + +#include <grpc/support/alloc.h> +#include <grpc/support/string_util.h> +#include "src/core/lib/support/string.h" + +static void add_metadata(gpr_strvec *b, const grpc_metadata *md, size_t count) { + size_t i; + for (i = 0; i < count; i++) { + gpr_strvec_add(b, gpr_strdup("\nkey=")); + gpr_strvec_add(b, gpr_strdup(md[i].key)); + + gpr_strvec_add(b, gpr_strdup(" value=")); + gpr_strvec_add(b, gpr_dump(md[i].value, md[i].value_length, + GPR_DUMP_HEX | GPR_DUMP_ASCII)); + } +} + +char *grpc_op_string(const grpc_op *op) { + char *tmp; + char *out; + + gpr_strvec b; + gpr_strvec_init(&b); + + switch (op->op) { + case GRPC_OP_SEND_INITIAL_METADATA: + gpr_strvec_add(&b, gpr_strdup("SEND_INITIAL_METADATA")); + add_metadata(&b, op->data.send_initial_metadata.metadata, + op->data.send_initial_metadata.count); + break; + case GRPC_OP_SEND_MESSAGE: + gpr_asprintf(&tmp, "SEND_MESSAGE ptr=%p", op->data.send_message); + gpr_strvec_add(&b, tmp); + break; + case GRPC_OP_SEND_CLOSE_FROM_CLIENT: + gpr_strvec_add(&b, gpr_strdup("SEND_CLOSE_FROM_CLIENT")); + break; + case GRPC_OP_SEND_STATUS_FROM_SERVER: + gpr_asprintf(&tmp, "SEND_STATUS_FROM_SERVER status=%d details=%s", + op->data.send_status_from_server.status, + op->data.send_status_from_server.status_details); + gpr_strvec_add(&b, tmp); + add_metadata(&b, op->data.send_status_from_server.trailing_metadata, + op->data.send_status_from_server.trailing_metadata_count); + break; + case GRPC_OP_RECV_INITIAL_METADATA: + gpr_asprintf(&tmp, "RECV_INITIAL_METADATA ptr=%p", + op->data.recv_initial_metadata); + gpr_strvec_add(&b, tmp); + break; + case GRPC_OP_RECV_MESSAGE: + gpr_asprintf(&tmp, "RECV_MESSAGE ptr=%p", op->data.recv_message); + gpr_strvec_add(&b, tmp); + break; + case GRPC_OP_RECV_STATUS_ON_CLIENT: + gpr_asprintf(&tmp, + "RECV_STATUS_ON_CLIENT metadata=%p status=%p details=%p", + op->data.recv_status_on_client.trailing_metadata, + op->data.recv_status_on_client.status, + op->data.recv_status_on_client.status_details); + gpr_strvec_add(&b, tmp); + break; + case GRPC_OP_RECV_CLOSE_ON_SERVER: + gpr_asprintf(&tmp, "RECV_CLOSE_ON_SERVER cancelled=%p", + op->data.recv_close_on_server.cancelled); + gpr_strvec_add(&b, tmp); + } + out = gpr_strvec_flatten(&b, NULL); + gpr_strvec_destroy(&b); + + return out; +} + +void grpc_call_log_batch(char *file, int line, gpr_log_severity severity, + grpc_call *call, const grpc_op *ops, size_t nops, + void *tag) { + char *tmp; + size_t i; + for (i = 0; i < nops; i++) { + tmp = grpc_op_string(&ops[i]); + gpr_log(file, line, severity, "ops[%d]: %s", i, tmp); + gpr_free(tmp); + } +} diff --git a/src/core/lib/surface/call_test_only.h b/src/core/lib/surface/call_test_only.h new file mode 100644 index 0000000000..400214189e --- /dev/null +++ b/src/core/lib/surface/call_test_only.h @@ -0,0 +1,64 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SURFACE_CALL_TEST_ONLY_H +#define GRPC_CORE_LIB_SURFACE_CALL_TEST_ONLY_H + +#include <grpc/grpc.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** Return the compression algorithm from \a call. + * + * \warning This function should \b only be used in test code. */ +grpc_compression_algorithm grpc_call_test_only_get_compression_algorithm( + grpc_call *call); + +/** Return the message flags from \a call. + * + * \warning This function should \b only be used in test code. */ +uint32_t grpc_call_test_only_get_message_flags(grpc_call *call); + +/** Returns a bitset for the encodings (compression algorithms) supported by \a + * call's peer. + * + * To be indexed by grpc_compression_algorithm enum values. */ +uint32_t grpc_call_test_only_get_encodings_accepted_by_peer(grpc_call *call); + +#ifdef __cplusplus +} +#endif + +#endif /* GRPC_CORE_LIB_SURFACE_CALL_TEST_ONLY_H */ diff --git a/src/core/lib/surface/channel.c b/src/core/lib/surface/channel.c new file mode 100644 index 0000000000..d815daa70c --- /dev/null +++ b/src/core/lib/surface/channel.c @@ -0,0 +1,324 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/surface/channel.h" + +#include <stdlib.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> + +#include "src/core/lib/client_config/resolver_registry.h" +#include "src/core/lib/iomgr/iomgr.h" +#include "src/core/lib/support/string.h" +#include "src/core/lib/surface/api_trace.h" +#include "src/core/lib/surface/call.h" +#include "src/core/lib/surface/channel_init.h" +#include "src/core/lib/surface/init.h" +#include "src/core/lib/transport/static_metadata.h" + +/** Cache grpc-status: X mdelems for X = 0..NUM_CACHED_STATUS_ELEMS. + * Avoids needing to take a metadata context lock for sending status + * if the status code is <= NUM_CACHED_STATUS_ELEMS. + * Sized to allow the most commonly used codes to fit in + * (OK, Cancelled, Unknown). */ +#define NUM_CACHED_STATUS_ELEMS 3 + +typedef struct registered_call { + grpc_mdelem *path; + grpc_mdelem *authority; + struct registered_call *next; +} registered_call; + +struct grpc_channel { + int is_client; + uint32_t max_message_length; + grpc_mdelem *default_authority; + + gpr_mu registered_call_mu; + registered_call *registered_calls; + char *target; +}; + +#define CHANNEL_STACK_FROM_CHANNEL(c) ((grpc_channel_stack *)((c) + 1)) +#define CHANNEL_FROM_CHANNEL_STACK(channel_stack) \ + (((grpc_channel *)(channel_stack)) - 1) +#define CHANNEL_FROM_TOP_ELEM(top_elem) \ + CHANNEL_FROM_CHANNEL_STACK(grpc_channel_stack_from_top_element(top_elem)) + +/* the protobuf library will (by default) start warning at 100megs */ +#define DEFAULT_MAX_MESSAGE_LENGTH (100 * 1024 * 1024) + +static void destroy_channel(grpc_exec_ctx *exec_ctx, void *arg, bool success); + +grpc_channel *grpc_channel_create(grpc_exec_ctx *exec_ctx, const char *target, + const grpc_channel_args *args, + grpc_channel_stack_type channel_stack_type, + grpc_transport *optional_transport) { + bool is_client = grpc_channel_stack_type_is_client(channel_stack_type); + + grpc_channel *channel = grpc_channel_init_create_stack( + exec_ctx, channel_stack_type, sizeof(grpc_channel), args, 1, + destroy_channel, NULL, optional_transport); + + memset(channel, 0, sizeof(*channel)); + channel->target = gpr_strdup(target); + channel->is_client = is_client; + gpr_mu_init(&channel->registered_call_mu); + channel->registered_calls = NULL; + + channel->max_message_length = DEFAULT_MAX_MESSAGE_LENGTH; + if (args) { + for (size_t i = 0; i < args->num_args; i++) { + if (0 == strcmp(args->args[i].key, GRPC_ARG_MAX_MESSAGE_LENGTH)) { + if (args->args[i].type != GRPC_ARG_INTEGER) { + gpr_log(GPR_ERROR, "%s ignored: it must be an integer", + GRPC_ARG_MAX_MESSAGE_LENGTH); + } else if (args->args[i].value.integer < 0) { + gpr_log(GPR_ERROR, "%s ignored: it must be >= 0", + GRPC_ARG_MAX_MESSAGE_LENGTH); + } else { + channel->max_message_length = (uint32_t)args->args[i].value.integer; + } + } else if (0 == strcmp(args->args[i].key, GRPC_ARG_DEFAULT_AUTHORITY)) { + if (args->args[i].type != GRPC_ARG_STRING) { + gpr_log(GPR_ERROR, "%s ignored: it must be a string", + GRPC_ARG_DEFAULT_AUTHORITY); + } else { + if (channel->default_authority) { + /* setting this takes precedence over anything else */ + GRPC_MDELEM_UNREF(channel->default_authority); + } + channel->default_authority = grpc_mdelem_from_strings( + ":authority", args->args[i].value.string); + } + } else if (0 == + strcmp(args->args[i].key, GRPC_SSL_TARGET_NAME_OVERRIDE_ARG)) { + if (args->args[i].type != GRPC_ARG_STRING) { + gpr_log(GPR_ERROR, "%s ignored: it must be a string", + GRPC_SSL_TARGET_NAME_OVERRIDE_ARG); + } else { + if (channel->default_authority) { + /* other ways of setting this (notably ssl) take precedence */ + gpr_log(GPR_ERROR, + "%s ignored: default host already set some other way", + GRPC_SSL_TARGET_NAME_OVERRIDE_ARG); + } else { + channel->default_authority = grpc_mdelem_from_strings( + ":authority", args->args[i].value.string); + } + } + } + } + } + + if (channel->is_client && channel->default_authority == NULL && + target != NULL) { + char *default_authority = grpc_get_default_authority(target); + if (default_authority) { + channel->default_authority = + grpc_mdelem_from_strings(":authority", default_authority); + } + gpr_free(default_authority); + } + + return channel; +} + +char *grpc_channel_get_target(grpc_channel *channel) { + GRPC_API_TRACE("grpc_channel_get_target(channel=%p)", 1, (channel)); + return gpr_strdup(channel->target); +} + +static grpc_call *grpc_channel_create_call_internal( + grpc_channel *channel, grpc_call *parent_call, uint32_t propagation_mask, + grpc_completion_queue *cq, grpc_mdelem *path_mdelem, + grpc_mdelem *authority_mdelem, gpr_timespec deadline) { + grpc_mdelem *send_metadata[2]; + size_t num_metadata = 0; + + GPR_ASSERT(channel->is_client); + + send_metadata[num_metadata++] = path_mdelem; + if (authority_mdelem != NULL) { + send_metadata[num_metadata++] = authority_mdelem; + } else if (channel->default_authority != NULL) { + send_metadata[num_metadata++] = GRPC_MDELEM_REF(channel->default_authority); + } + + return grpc_call_create(channel, parent_call, propagation_mask, cq, NULL, + send_metadata, num_metadata, deadline); +} + +grpc_call *grpc_channel_create_call(grpc_channel *channel, + grpc_call *parent_call, + uint32_t propagation_mask, + grpc_completion_queue *cq, + const char *method, const char *host, + gpr_timespec deadline, void *reserved) { + GRPC_API_TRACE( + "grpc_channel_create_call(" + "channel=%p, parent_call=%p, propagation_mask=%x, cq=%p, method=%s, " + "host=%s, " + "deadline=gpr_timespec { tv_sec: %lld, tv_nsec: %d, clock_type: %d }, " + "reserved=%p)", + 10, (channel, parent_call, (unsigned)propagation_mask, cq, method, host, + (long long)deadline.tv_sec, (int)deadline.tv_nsec, + (int)deadline.clock_type, reserved)); + GPR_ASSERT(!reserved); + return grpc_channel_create_call_internal( + channel, parent_call, propagation_mask, cq, + grpc_mdelem_from_metadata_strings(GRPC_MDSTR_PATH, + grpc_mdstr_from_string(method)), + host ? grpc_mdelem_from_metadata_strings(GRPC_MDSTR_AUTHORITY, + grpc_mdstr_from_string(host)) + : NULL, + deadline); +} + +void *grpc_channel_register_call(grpc_channel *channel, const char *method, + const char *host, void *reserved) { + registered_call *rc = gpr_malloc(sizeof(registered_call)); + GRPC_API_TRACE( + "grpc_channel_register_call(channel=%p, method=%s, host=%s, reserved=%p)", + 4, (channel, method, host, reserved)); + GPR_ASSERT(!reserved); + rc->path = grpc_mdelem_from_metadata_strings(GRPC_MDSTR_PATH, + grpc_mdstr_from_string(method)); + rc->authority = host ? grpc_mdelem_from_metadata_strings( + GRPC_MDSTR_AUTHORITY, grpc_mdstr_from_string(host)) + : NULL; + gpr_mu_lock(&channel->registered_call_mu); + rc->next = channel->registered_calls; + channel->registered_calls = rc; + gpr_mu_unlock(&channel->registered_call_mu); + return rc; +} + +grpc_call *grpc_channel_create_registered_call( + grpc_channel *channel, grpc_call *parent_call, uint32_t propagation_mask, + grpc_completion_queue *completion_queue, void *registered_call_handle, + gpr_timespec deadline, void *reserved) { + registered_call *rc = registered_call_handle; + GRPC_API_TRACE( + "grpc_channel_create_registered_call(" + "channel=%p, parent_call=%p, propagation_mask=%x, completion_queue=%p, " + "registered_call_handle=%p, " + "deadline=gpr_timespec { tv_sec: %lld, tv_nsec: %d, clock_type: %d }, " + "reserved=%p)", + 9, (channel, parent_call, (unsigned)propagation_mask, completion_queue, + registered_call_handle, (long long)deadline.tv_sec, + (int)deadline.tv_nsec, (int)deadline.clock_type, reserved)); + GPR_ASSERT(!reserved); + return grpc_channel_create_call_internal( + channel, parent_call, propagation_mask, completion_queue, + GRPC_MDELEM_REF(rc->path), + rc->authority ? GRPC_MDELEM_REF(rc->authority) : NULL, deadline); +} + +#ifdef GRPC_STREAM_REFCOUNT_DEBUG +#define REF_REASON reason +#define REF_ARG , const char *reason +#else +#define REF_REASON "" +#define REF_ARG +#endif +void grpc_channel_internal_ref(grpc_channel *c REF_ARG) { + GRPC_CHANNEL_STACK_REF(CHANNEL_STACK_FROM_CHANNEL(c), REF_REASON); +} + +void grpc_channel_internal_unref(grpc_exec_ctx *exec_ctx, + grpc_channel *c REF_ARG) { + GRPC_CHANNEL_STACK_UNREF(exec_ctx, CHANNEL_STACK_FROM_CHANNEL(c), REF_REASON); +} + +static void destroy_channel(grpc_exec_ctx *exec_ctx, void *arg, + bool iomgr_success) { + grpc_channel *channel = arg; + grpc_channel_stack_destroy(exec_ctx, CHANNEL_STACK_FROM_CHANNEL(channel)); + while (channel->registered_calls) { + registered_call *rc = channel->registered_calls; + channel->registered_calls = rc->next; + GRPC_MDELEM_UNREF(rc->path); + if (rc->authority) { + GRPC_MDELEM_UNREF(rc->authority); + } + gpr_free(rc); + } + if (channel->default_authority != NULL) { + GRPC_MDELEM_UNREF(channel->default_authority); + } + gpr_mu_destroy(&channel->registered_call_mu); + gpr_free(channel->target); + gpr_free(channel); +} + +void grpc_channel_destroy(grpc_channel *channel) { + grpc_transport_op op; + grpc_channel_element *elem; + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + GRPC_API_TRACE("grpc_channel_destroy(channel=%p)", 1, (channel)); + memset(&op, 0, sizeof(op)); + op.disconnect = 1; + elem = grpc_channel_stack_element(CHANNEL_STACK_FROM_CHANNEL(channel), 0); + elem->filter->start_transport_op(&exec_ctx, elem, &op); + + GRPC_CHANNEL_INTERNAL_UNREF(&exec_ctx, channel, "channel"); + + grpc_exec_ctx_finish(&exec_ctx); +} + +grpc_channel_stack *grpc_channel_get_channel_stack(grpc_channel *channel) { + return CHANNEL_STACK_FROM_CHANNEL(channel); +} + +grpc_mdelem *grpc_channel_get_reffed_status_elem(grpc_channel *channel, int i) { + char tmp[GPR_LTOA_MIN_BUFSIZE]; + switch (i) { + case 0: + return GRPC_MDELEM_GRPC_STATUS_0; + case 1: + return GRPC_MDELEM_GRPC_STATUS_1; + case 2: + return GRPC_MDELEM_GRPC_STATUS_2; + } + gpr_ltoa(i, tmp); + return grpc_mdelem_from_metadata_strings(GRPC_MDSTR_GRPC_STATUS, + grpc_mdstr_from_string(tmp)); +} + +uint32_t grpc_channel_get_max_message_length(grpc_channel *channel) { + return channel->max_message_length; +} diff --git a/src/core/lib/surface/channel.h b/src/core/lib/surface/channel.h new file mode 100644 index 0000000000..09de0fccc9 --- /dev/null +++ b/src/core/lib/surface/channel.h @@ -0,0 +1,75 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SURFACE_CHANNEL_H +#define GRPC_CORE_LIB_SURFACE_CHANNEL_H + +#include "src/core/lib/channel/channel_stack.h" +#include "src/core/lib/client_config/subchannel_factory.h" +#include "src/core/lib/surface/channel_stack_type.h" + +grpc_channel *grpc_channel_create(grpc_exec_ctx *exec_ctx, const char *target, + const grpc_channel_args *args, + grpc_channel_stack_type channel_stack_type, + grpc_transport *optional_transport); + +/** Get a (borrowed) pointer to this channels underlying channel stack */ +grpc_channel_stack *grpc_channel_get_channel_stack(grpc_channel *channel); + +/** Get a grpc_mdelem of grpc-status: X where X is the numeric value of + status_code. + + The returned elem is owned by the caller. */ +grpc_mdelem *grpc_channel_get_reffed_status_elem(grpc_channel *channel, + int status_code); +uint32_t grpc_channel_get_max_message_length(grpc_channel *channel); + +#ifdef GRPC_STREAM_REFCOUNT_DEBUG +void grpc_channel_internal_ref(grpc_channel *channel, const char *reason); +void grpc_channel_internal_unref(grpc_exec_ctx *exec_ctx, grpc_channel *channel, + const char *reason); +#define GRPC_CHANNEL_INTERNAL_REF(channel, reason) \ + grpc_channel_internal_ref(channel, reason) +#define GRPC_CHANNEL_INTERNAL_UNREF(exec_ctx, channel, reason) \ + grpc_channel_internal_unref(exec_ctx, channel, reason) +#else +void grpc_channel_internal_ref(grpc_channel *channel); +void grpc_channel_internal_unref(grpc_exec_ctx *exec_ctx, + grpc_channel *channel); +#define GRPC_CHANNEL_INTERNAL_REF(channel, reason) \ + grpc_channel_internal_ref(channel) +#define GRPC_CHANNEL_INTERNAL_UNREF(exec_ctx, channel, reason) \ + grpc_channel_internal_unref(exec_ctx, channel) +#endif + +#endif /* GRPC_CORE_LIB_SURFACE_CHANNEL_H */ diff --git a/src/core/lib/surface/channel_connectivity.c b/src/core/lib/surface/channel_connectivity.c new file mode 100644 index 0000000000..2f5d763e70 --- /dev/null +++ b/src/core/lib/surface/channel_connectivity.c @@ -0,0 +1,207 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/surface/channel.h" + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +#include "src/core/lib/channel/client_channel.h" +#include "src/core/lib/iomgr/timer.h" +#include "src/core/lib/surface/api_trace.h" +#include "src/core/lib/surface/completion_queue.h" + +grpc_connectivity_state grpc_channel_check_connectivity_state( + grpc_channel *channel, int try_to_connect) { + /* forward through to the underlying client channel */ + grpc_channel_element *client_channel_elem = + grpc_channel_stack_last_element(grpc_channel_get_channel_stack(channel)); + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + grpc_connectivity_state state; + GRPC_API_TRACE( + "grpc_channel_check_connectivity_state(channel=%p, try_to_connect=%d)", 2, + (channel, try_to_connect)); + if (client_channel_elem->filter == &grpc_client_channel_filter) { + state = grpc_client_channel_check_connectivity_state( + &exec_ctx, client_channel_elem, try_to_connect); + grpc_exec_ctx_finish(&exec_ctx); + return state; + } + gpr_log(GPR_ERROR, + "grpc_channel_check_connectivity_state called on something that is " + "not a (u)client channel, but '%s'", + client_channel_elem->filter->name); + grpc_exec_ctx_finish(&exec_ctx); + return GRPC_CHANNEL_FATAL_FAILURE; +} + +typedef enum { + WAITING, + CALLING_BACK, + CALLING_BACK_AND_FINISHED, + CALLED_BACK +} callback_phase; + +typedef struct { + gpr_mu mu; + callback_phase phase; + int success; + grpc_closure on_complete; + grpc_timer alarm; + grpc_connectivity_state state; + grpc_completion_queue *cq; + grpc_cq_completion completion_storage; + grpc_channel *channel; + void *tag; +} state_watcher; + +static void delete_state_watcher(grpc_exec_ctx *exec_ctx, state_watcher *w) { + grpc_channel_element *client_channel_elem = grpc_channel_stack_last_element( + grpc_channel_get_channel_stack(w->channel)); + if (client_channel_elem->filter == &grpc_client_channel_filter) { + GRPC_CHANNEL_INTERNAL_UNREF(exec_ctx, w->channel, + "watch_channel_connectivity"); + } else { + abort(); + } + gpr_mu_destroy(&w->mu); + gpr_free(w); +} + +static void finished_completion(grpc_exec_ctx *exec_ctx, void *pw, + grpc_cq_completion *ignored) { + int delete = 0; + state_watcher *w = pw; + gpr_mu_lock(&w->mu); + switch (w->phase) { + case WAITING: + case CALLED_BACK: + GPR_UNREACHABLE_CODE(return ); + case CALLING_BACK: + w->phase = CALLED_BACK; + break; + case CALLING_BACK_AND_FINISHED: + delete = 1; + break; + } + gpr_mu_unlock(&w->mu); + + if (delete) { + delete_state_watcher(exec_ctx, w); + } +} + +static void partly_done(grpc_exec_ctx *exec_ctx, state_watcher *w, + int due_to_completion) { + int delete = 0; + + if (due_to_completion) { + grpc_timer_cancel(exec_ctx, &w->alarm); + } + + gpr_mu_lock(&w->mu); + if (due_to_completion) { + w->success = 1; + } + switch (w->phase) { + case WAITING: + w->phase = CALLING_BACK; + grpc_cq_end_op(exec_ctx, w->cq, w->tag, w->success, finished_completion, + w, &w->completion_storage); + break; + case CALLING_BACK: + w->phase = CALLING_BACK_AND_FINISHED; + break; + case CALLING_BACK_AND_FINISHED: + GPR_UNREACHABLE_CODE(return ); + case CALLED_BACK: + delete = 1; + break; + } + gpr_mu_unlock(&w->mu); + + if (delete) { + delete_state_watcher(exec_ctx, w); + } +} + +static void watch_complete(grpc_exec_ctx *exec_ctx, void *pw, bool success) { + partly_done(exec_ctx, pw, 1); +} + +static void timeout_complete(grpc_exec_ctx *exec_ctx, void *pw, bool success) { + partly_done(exec_ctx, pw, 0); +} + +void grpc_channel_watch_connectivity_state( + grpc_channel *channel, grpc_connectivity_state last_observed_state, + gpr_timespec deadline, grpc_completion_queue *cq, void *tag) { + grpc_channel_element *client_channel_elem = + grpc_channel_stack_last_element(grpc_channel_get_channel_stack(channel)); + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + state_watcher *w = gpr_malloc(sizeof(*w)); + + GRPC_API_TRACE( + "grpc_channel_watch_connectivity_state(" + "channel=%p, last_observed_state=%d, " + "deadline=gpr_timespec { tv_sec: %lld, tv_nsec: %d, clock_type: %d }, " + "cq=%p, tag=%p)", + 7, (channel, (int)last_observed_state, (long long)deadline.tv_sec, + (int)deadline.tv_nsec, (int)deadline.clock_type, cq, tag)); + + grpc_cq_begin_op(cq, tag); + + gpr_mu_init(&w->mu); + grpc_closure_init(&w->on_complete, watch_complete, w); + w->phase = WAITING; + w->state = last_observed_state; + w->success = 0; + w->cq = cq; + w->tag = tag; + w->channel = channel; + + grpc_timer_init(&exec_ctx, &w->alarm, + gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC), + timeout_complete, w, gpr_now(GPR_CLOCK_MONOTONIC)); + + if (client_channel_elem->filter == &grpc_client_channel_filter) { + GRPC_CHANNEL_INTERNAL_REF(channel, "watch_channel_connectivity"); + grpc_client_channel_watch_connectivity_state(&exec_ctx, client_channel_elem, + grpc_cq_pollset(cq), &w->state, + &w->on_complete); + } else { + abort(); + } + + grpc_exec_ctx_finish(&exec_ctx); +} diff --git a/src/core/lib/surface/channel_create.c b/src/core/lib/surface/channel_create.c new file mode 100644 index 0000000000..e8777ce816 --- /dev/null +++ b/src/core/lib/surface/channel_create.c @@ -0,0 +1,223 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/grpc.h> + +#include <stdlib.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/slice.h> +#include <grpc/support/slice_buffer.h> + +#include "src/core/lib/census/grpc_filter.h" +#include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/channel/client_channel.h" +#include "src/core/lib/channel/compress_filter.h" +#include "src/core/lib/channel/http_client_filter.h" +#include "src/core/lib/client_config/resolver_registry.h" +#include "src/core/lib/iomgr/tcp_client.h" +#include "src/core/lib/surface/api_trace.h" +#include "src/core/lib/surface/channel.h" +#include "src/core/lib/transport/chttp2_transport.h" + +typedef struct { + grpc_connector base; + gpr_refcount refs; + + grpc_closure *notify; + grpc_connect_in_args args; + grpc_connect_out_args *result; + grpc_closure initial_string_sent; + gpr_slice_buffer initial_string_buffer; + + grpc_endpoint *tcp; + + grpc_closure connected; +} connector; + +static void connector_ref(grpc_connector *con) { + connector *c = (connector *)con; + gpr_ref(&c->refs); +} + +static void connector_unref(grpc_exec_ctx *exec_ctx, grpc_connector *con) { + connector *c = (connector *)con; + if (gpr_unref(&c->refs)) { + /* c->initial_string_buffer does not need to be destroyed */ + gpr_free(c); + } +} + +static void on_initial_connect_string_sent(grpc_exec_ctx *exec_ctx, void *arg, + bool success) { + connector_unref(exec_ctx, arg); +} + +static void connected(grpc_exec_ctx *exec_ctx, void *arg, bool success) { + connector *c = arg; + grpc_closure *notify; + grpc_endpoint *tcp = c->tcp; + if (tcp != NULL) { + if (!GPR_SLICE_IS_EMPTY(c->args.initial_connect_string)) { + grpc_closure_init(&c->initial_string_sent, on_initial_connect_string_sent, + c); + gpr_slice_buffer_init(&c->initial_string_buffer); + gpr_slice_buffer_add(&c->initial_string_buffer, + c->args.initial_connect_string); + connector_ref(arg); + grpc_endpoint_write(exec_ctx, tcp, &c->initial_string_buffer, + &c->initial_string_sent); + } + c->result->transport = + grpc_create_chttp2_transport(exec_ctx, c->args.channel_args, tcp, 1); + grpc_chttp2_transport_start_reading(exec_ctx, c->result->transport, NULL, + 0); + GPR_ASSERT(c->result->transport); + c->result->channel_args = c->args.channel_args; + } else { + memset(c->result, 0, sizeof(*c->result)); + } + notify = c->notify; + c->notify = NULL; + notify->cb(exec_ctx, notify->cb_arg, 1); +} + +static void connector_shutdown(grpc_exec_ctx *exec_ctx, grpc_connector *con) {} + +static void connector_connect(grpc_exec_ctx *exec_ctx, grpc_connector *con, + const grpc_connect_in_args *args, + grpc_connect_out_args *result, + grpc_closure *notify) { + connector *c = (connector *)con; + GPR_ASSERT(c->notify == NULL); + GPR_ASSERT(notify->cb); + c->notify = notify; + c->args = *args; + c->result = result; + c->tcp = NULL; + grpc_closure_init(&c->connected, connected, c); + grpc_tcp_client_connect(exec_ctx, &c->connected, &c->tcp, + args->interested_parties, args->addr, args->addr_len, + args->deadline); +} + +static const grpc_connector_vtable connector_vtable = { + connector_ref, connector_unref, connector_shutdown, connector_connect}; + +typedef struct { + grpc_subchannel_factory base; + gpr_refcount refs; + grpc_channel_args *merge_args; + grpc_channel *master; +} subchannel_factory; + +static void subchannel_factory_ref(grpc_subchannel_factory *scf) { + subchannel_factory *f = (subchannel_factory *)scf; + gpr_ref(&f->refs); +} + +static void subchannel_factory_unref(grpc_exec_ctx *exec_ctx, + grpc_subchannel_factory *scf) { + subchannel_factory *f = (subchannel_factory *)scf; + if (gpr_unref(&f->refs)) { + GRPC_CHANNEL_INTERNAL_UNREF(exec_ctx, f->master, "subchannel_factory"); + grpc_channel_args_destroy(f->merge_args); + gpr_free(f); + } +} + +static grpc_subchannel *subchannel_factory_create_subchannel( + grpc_exec_ctx *exec_ctx, grpc_subchannel_factory *scf, + grpc_subchannel_args *args) { + subchannel_factory *f = (subchannel_factory *)scf; + connector *c = gpr_malloc(sizeof(*c)); + grpc_channel_args *final_args = + grpc_channel_args_merge(args->args, f->merge_args); + grpc_subchannel *s; + memset(c, 0, sizeof(*c)); + c->base.vtable = &connector_vtable; + gpr_ref_init(&c->refs, 1); + args->args = final_args; + s = grpc_subchannel_create(exec_ctx, &c->base, args); + grpc_connector_unref(exec_ctx, &c->base); + grpc_channel_args_destroy(final_args); + return s; +} + +static const grpc_subchannel_factory_vtable subchannel_factory_vtable = { + subchannel_factory_ref, subchannel_factory_unref, + subchannel_factory_create_subchannel}; + +/* Create a client channel: + Asynchronously: - resolve target + - connect to it (trying alternatives as presented) + - perform handshakes */ +grpc_channel *grpc_insecure_channel_create(const char *target, + const grpc_channel_args *args, + void *reserved) { + grpc_channel *channel = NULL; + grpc_resolver *resolver; + subchannel_factory *f; + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + GRPC_API_TRACE( + "grpc_insecure_channel_create(target=%p, args=%p, reserved=%p)", 3, + (target, args, reserved)); + GPR_ASSERT(!reserved); + + channel = + grpc_channel_create(&exec_ctx, target, args, GRPC_CLIENT_CHANNEL, NULL); + + f = gpr_malloc(sizeof(*f)); + f->base.vtable = &subchannel_factory_vtable; + gpr_ref_init(&f->refs, 1); + f->merge_args = grpc_channel_args_copy(args); + f->master = channel; + GRPC_CHANNEL_INTERNAL_REF(f->master, "subchannel_factory"); + resolver = grpc_resolver_create(target, &f->base); + if (!resolver) { + GRPC_CHANNEL_INTERNAL_UNREF(&exec_ctx, f->master, "subchannel_factory"); + grpc_subchannel_factory_unref(&exec_ctx, &f->base); + grpc_exec_ctx_finish(&exec_ctx); + return NULL; + } + + grpc_client_channel_set_resolver( + &exec_ctx, grpc_channel_get_channel_stack(channel), resolver); + GRPC_RESOLVER_UNREF(&exec_ctx, resolver, "create"); + grpc_subchannel_factory_unref(&exec_ctx, &f->base); + + grpc_exec_ctx_finish(&exec_ctx); + + return channel; +} diff --git a/src/core/lib/surface/channel_init.c b/src/core/lib/surface/channel_init.c new file mode 100644 index 0000000000..fc69f61f77 --- /dev/null +++ b/src/core/lib/surface/channel_init.c @@ -0,0 +1,146 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/surface/channel_init.h" + +#include <grpc/support/alloc.h> +#include <grpc/support/useful.h> + +typedef struct stage_slot { + grpc_channel_init_stage fn; + void *arg; + int priority; + size_t insertion_order; +} stage_slot; + +typedef struct stage_slots { + stage_slot *slots; + size_t num_slots; + size_t cap_slots; +} stage_slots; + +static stage_slots g_slots[GRPC_NUM_CHANNEL_STACK_TYPES]; +static bool g_finalized; + +void grpc_channel_init_init(void) { + for (int i = 0; i < GRPC_NUM_CHANNEL_STACK_TYPES; i++) { + g_slots[i].slots = NULL; + g_slots[i].num_slots = 0; + g_slots[i].cap_slots = 0; + } + g_finalized = false; +} + +void grpc_channel_init_register_stage(grpc_channel_stack_type type, + int priority, + grpc_channel_init_stage stage, + void *stage_arg) { + GPR_ASSERT(!g_finalized); + if (g_slots[type].cap_slots == g_slots[type].num_slots) { + g_slots[type].cap_slots = GPR_MAX(8, 3 * g_slots[type].cap_slots / 2); + g_slots[type].slots = + gpr_realloc(g_slots[type].slots, + g_slots[type].cap_slots * sizeof(*g_slots[type].slots)); + } + stage_slot *s = &g_slots[type].slots[g_slots[type].num_slots++]; + s->insertion_order = g_slots[type].num_slots; + s->priority = priority; + s->fn = stage; + s->arg = stage_arg; +} + +static int compare_slots(const void *a, const void *b) { + const stage_slot *sa = a; + const stage_slot *sb = b; + + int c = GPR_ICMP(sa->priority, sb->priority); + if (c != 0) return c; + return GPR_ICMP(sa->insertion_order, sb->insertion_order); +} + +void grpc_channel_init_finalize(void) { + GPR_ASSERT(!g_finalized); + for (int i = 0; i < GRPC_NUM_CHANNEL_STACK_TYPES; i++) { + qsort(g_slots[i].slots, g_slots[i].num_slots, sizeof(*g_slots[i].slots), + compare_slots); + } + g_finalized = true; +} + +void grpc_channel_init_shutdown(void) { + for (int i = 0; i < GRPC_NUM_CHANNEL_STACK_TYPES; i++) { + gpr_free(g_slots[i].slots); + g_slots[i].slots = (void *)(uintptr_t)0xdeadbeef; + } +} + +static const char *name_for_type(grpc_channel_stack_type type) { + switch (type) { + case GRPC_CLIENT_CHANNEL: + return "CLIENT_CHANNEL"; + case GRPC_CLIENT_SUBCHANNEL: + return "CLIENT_SUBCHANNEL"; + case GRPC_SERVER_CHANNEL: + return "SERVER_CHANNEL"; + case GRPC_CLIENT_LAME_CHANNEL: + return "CLIENT_LAME_CHANNEL"; + case GRPC_CLIENT_DIRECT_CHANNEL: + return "CLIENT_DIRECT_CHANNEL"; + case GRPC_NUM_CHANNEL_STACK_TYPES: + break; + } + GPR_UNREACHABLE_CODE(return "UNKNOWN"); +} + +void *grpc_channel_init_create_stack( + grpc_exec_ctx *exec_ctx, grpc_channel_stack_type type, size_t prefix_bytes, + const grpc_channel_args *args, int initial_refs, grpc_iomgr_cb_func destroy, + void *destroy_arg, grpc_transport *transport) { + GPR_ASSERT(g_finalized); + + grpc_channel_stack_builder *builder = grpc_channel_stack_builder_create(); + grpc_channel_stack_builder_set_name(builder, name_for_type(type)); + grpc_channel_stack_builder_set_channel_arguments(builder, args); + grpc_channel_stack_builder_set_transport(builder, transport); + + for (size_t i = 0; i < g_slots[type].num_slots; i++) { + const stage_slot *slot = &g_slots[type].slots[i]; + if (!slot->fn(builder, slot->arg)) { + grpc_channel_stack_builder_destroy(builder); + return NULL; + } + } + + return grpc_channel_stack_builder_finish(exec_ctx, builder, prefix_bytes, + initial_refs, destroy, destroy_arg); +} diff --git a/src/core/lib/surface/channel_init.h b/src/core/lib/surface/channel_init.h new file mode 100644 index 0000000000..a4d8271ca6 --- /dev/null +++ b/src/core/lib/surface/channel_init.h @@ -0,0 +1,86 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SURFACE_CHANNEL_INIT_H +#define GRPC_CORE_LIB_SURFACE_CHANNEL_INIT_H + +#include "src/core/lib/channel/channel_stack_builder.h" +#include "src/core/lib/surface/channel_stack_type.h" +#include "src/core/lib/transport/transport.h" + +/// This module provides a way for plugins (and the grpc core library itself) +/// to register mutators for channel stacks. +/// It also provides a universal entry path to run those mutators to build +/// a channel stack for various subsystems. + +/// One stage of mutation: call functions against \a builder to influence the +/// finally constructed channel stack +typedef bool (*grpc_channel_init_stage)(grpc_channel_stack_builder *builder, + void *arg); + +/// Global initialization of the system +void grpc_channel_init_init(void); + +/// Register one stage of mutators. +/// Stages are run in priority order (lowest to highest), and then in +/// registration order (in the case of a tie). +/// Stages are registered against one of the pre-determined channel stack +/// types. +void grpc_channel_init_register_stage(grpc_channel_stack_type type, + int priority, + grpc_channel_init_stage stage_fn, + void *stage_arg); + +/// Finalize registration. No more calls to grpc_channel_init_register_stage are +/// allowed. +void grpc_channel_init_finalize(void); +/// Shutdown the channel init system +void grpc_channel_init_shutdown(void); + +/// Construct a channel stack of some sort: see channel_stack.h for details +/// \a type is the type of channel stack to create +/// \a prefix_bytes is the number of bytes before the channel stack to allocate +/// \a args are configuration arguments for the channel stack +/// \a initial_refs is the initial refcount to give the channel stack +/// \a destroy and \a destroy_arg specify how to destroy the channel stack +/// if destroy_arg is NULL, the returned value from this function will be +/// substituted +/// \a optional_transport is either NULL or a constructed transport object +/// Returns a pointer to the base of the memory allocated (the actual channel +/// stack object will be prefix_bytes past that pointer) +void *grpc_channel_init_create_stack( + grpc_exec_ctx *exec_ctx, grpc_channel_stack_type type, size_t prefix_bytes, + const grpc_channel_args *args, int initial_refs, grpc_iomgr_cb_func destroy, + void *destroy_arg, grpc_transport *optional_transport); + +#endif /* GRPC_CORE_LIB_SURFACE_CHANNEL_INIT_H */ diff --git a/src/core/lib/surface/channel_ping.c b/src/core/lib/surface/channel_ping.c new file mode 100644 index 0000000000..dd862cdadd --- /dev/null +++ b/src/core/lib/surface/channel_ping.c @@ -0,0 +1,79 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/surface/channel.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +#include "src/core/lib/surface/api_trace.h" +#include "src/core/lib/surface/completion_queue.h" + +typedef struct { + grpc_closure closure; + void *tag; + grpc_completion_queue *cq; + grpc_cq_completion completion_storage; +} ping_result; + +static void ping_destroy(grpc_exec_ctx *exec_ctx, void *arg, + grpc_cq_completion *storage) { + gpr_free(arg); +} + +static void ping_done(grpc_exec_ctx *exec_ctx, void *arg, bool success) { + ping_result *pr = arg; + grpc_cq_end_op(exec_ctx, pr->cq, pr->tag, success, ping_destroy, pr, + &pr->completion_storage); +} + +void grpc_channel_ping(grpc_channel *channel, grpc_completion_queue *cq, + void *tag, void *reserved) { + grpc_transport_op op; + ping_result *pr = gpr_malloc(sizeof(*pr)); + grpc_channel_element *top_elem = + grpc_channel_stack_element(grpc_channel_get_channel_stack(channel), 0); + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + GPR_ASSERT(reserved == NULL); + memset(&op, 0, sizeof(op)); + pr->tag = tag; + pr->cq = cq; + grpc_closure_init(&pr->closure, ping_done, pr); + op.send_ping = &pr->closure; + op.bind_pollset = grpc_cq_pollset(cq); + grpc_cq_begin_op(cq, tag); + top_elem->filter->start_transport_op(&exec_ctx, top_elem, &op); + grpc_exec_ctx_finish(&exec_ctx); +} diff --git a/src/core/lib/surface/channel_stack_type.c b/src/core/lib/surface/channel_stack_type.c new file mode 100644 index 0000000000..c35d603ca3 --- /dev/null +++ b/src/core/lib/surface/channel_stack_type.c @@ -0,0 +1,54 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/surface/channel_stack_type.h" +#include <grpc/support/log.h> +#include <grpc/support/port_platform.h> + +bool grpc_channel_stack_type_is_client(grpc_channel_stack_type type) { + switch (type) { + case GRPC_CLIENT_CHANNEL: + return true; + case GRPC_CLIENT_SUBCHANNEL: + return true; + case GRPC_CLIENT_LAME_CHANNEL: + return true; + case GRPC_CLIENT_DIRECT_CHANNEL: + return true; + case GRPC_SERVER_CHANNEL: + return false; + case GRPC_NUM_CHANNEL_STACK_TYPES: + break; + } + GPR_UNREACHABLE_CODE(return true;); +} diff --git a/src/core/lib/surface/channel_stack_type.h b/src/core/lib/surface/channel_stack_type.h new file mode 100644 index 0000000000..16608fa386 --- /dev/null +++ b/src/core/lib/surface/channel_stack_type.h @@ -0,0 +1,58 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SURFACE_CHANNEL_STACK_TYPE_H +#define GRPC_CORE_LIB_SURFACE_CHANNEL_STACK_TYPE_H + +#include <stdbool.h> + +typedef enum { + // normal top-half client channel with load-balancing, connection management + GRPC_CLIENT_CHANNEL, + // bottom-half of a client channel: everything that happens post-load + // balancing (bound to a specific transport) + GRPC_CLIENT_SUBCHANNEL, + // a permanently broken client channel + GRPC_CLIENT_LAME_CHANNEL, + // a directly connected client channel (without load-balancing, directly talks + // to a transport) + GRPC_CLIENT_DIRECT_CHANNEL, + // server side channel + GRPC_SERVER_CHANNEL, + // must be last + GRPC_NUM_CHANNEL_STACK_TYPES +} grpc_channel_stack_type; + +bool grpc_channel_stack_type_is_client(grpc_channel_stack_type type); + +#endif /* GRPC_CORE_LIB_SURFACE_CHANNEL_STACK_TYPE_H */ diff --git a/src/core/lib/surface/completion_queue.c b/src/core/lib/surface/completion_queue.c new file mode 100644 index 0000000000..a0d7002053 --- /dev/null +++ b/src/core/lib/surface/completion_queue.c @@ -0,0 +1,512 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/surface/completion_queue.h" + +#include <stdio.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/atm.h> +#include <grpc/support/log.h> +#include <grpc/support/time.h> + +#include "src/core/lib/iomgr/pollset.h" +#include "src/core/lib/iomgr/timer.h" +#include "src/core/lib/profiling/timers.h" +#include "src/core/lib/support/string.h" +#include "src/core/lib/surface/api_trace.h" +#include "src/core/lib/surface/call.h" +#include "src/core/lib/surface/event_string.h" +#include "src/core/lib/surface/surface_trace.h" + +typedef struct { + grpc_pollset_worker **worker; + void *tag; +} plucker; + +/* Completion queue structure */ +struct grpc_completion_queue { + /** owned by pollset */ + gpr_mu *mu; + /** completed events */ + grpc_cq_completion completed_head; + grpc_cq_completion *completed_tail; + /** Number of pending events (+1 if we're not shutdown) */ + gpr_refcount pending_events; + /** Once owning_refs drops to zero, we will destroy the cq */ + gpr_refcount owning_refs; + /** 0 initially, 1 once we've begun shutting down */ + int shutdown; + int shutdown_called; + int is_server_cq; + int num_pluckers; + plucker pluckers[GRPC_MAX_COMPLETION_QUEUE_PLUCKERS]; + grpc_closure pollset_shutdown_done; + +#ifndef NDEBUG + void **outstanding_tags; + size_t outstanding_tag_count; + size_t outstanding_tag_capacity; +#endif + + grpc_completion_queue *next_free; +}; + +#define POLLSET_FROM_CQ(cq) ((grpc_pollset *)(cq + 1)) + +static gpr_mu g_freelist_mu; +static grpc_completion_queue *g_freelist; + +static void on_pollset_shutdown_done(grpc_exec_ctx *exec_ctx, void *cc, + bool success); + +void grpc_cq_global_init(void) { gpr_mu_init(&g_freelist_mu); } + +void grpc_cq_global_shutdown(void) { + gpr_mu_destroy(&g_freelist_mu); + while (g_freelist) { + grpc_completion_queue *next = g_freelist->next_free; + grpc_pollset_destroy(POLLSET_FROM_CQ(g_freelist)); +#ifndef NDEBUG + gpr_free(g_freelist->outstanding_tags); +#endif + gpr_free(g_freelist); + g_freelist = next; + } +} + +struct grpc_cq_alarm { + grpc_timer alarm; + grpc_cq_completion completion; + /** completion queue where events about this alarm will be posted */ + grpc_completion_queue *cq; + /** user supplied tag */ + void *tag; +}; + +grpc_completion_queue *grpc_completion_queue_create(void *reserved) { + grpc_completion_queue *cc; + GPR_ASSERT(!reserved); + + GPR_TIMER_BEGIN("grpc_completion_queue_create", 0); + + GRPC_API_TRACE("grpc_completion_queue_create(reserved=%p)", 1, (reserved)); + + gpr_mu_lock(&g_freelist_mu); + if (g_freelist == NULL) { + gpr_mu_unlock(&g_freelist_mu); + + cc = gpr_malloc(sizeof(grpc_completion_queue) + grpc_pollset_size()); + grpc_pollset_init(POLLSET_FROM_CQ(cc), &cc->mu); +#ifndef NDEBUG + cc->outstanding_tags = NULL; + cc->outstanding_tag_capacity = 0; +#endif + } else { + cc = g_freelist; + g_freelist = g_freelist->next_free; + gpr_mu_unlock(&g_freelist_mu); + /* pollset already initialized */ + } + + /* Initial ref is dropped by grpc_completion_queue_shutdown */ + gpr_ref_init(&cc->pending_events, 1); + /* One for destroy(), one for pollset_shutdown */ + gpr_ref_init(&cc->owning_refs, 2); + cc->completed_tail = &cc->completed_head; + cc->completed_head.next = (uintptr_t)cc->completed_tail; + cc->shutdown = 0; + cc->shutdown_called = 0; + cc->is_server_cq = 0; + cc->num_pluckers = 0; +#ifndef NDEBUG + cc->outstanding_tag_count = 0; +#endif + grpc_closure_init(&cc->pollset_shutdown_done, on_pollset_shutdown_done, cc); + + GPR_TIMER_END("grpc_completion_queue_create", 0); + + return cc; +} + +#ifdef GRPC_CQ_REF_COUNT_DEBUG +void grpc_cq_internal_ref(grpc_completion_queue *cc, const char *reason, + const char *file, int line) { + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, "CQ:%p ref %d -> %d %s", cc, + (int)cc->owning_refs.count, (int)cc->owning_refs.count + 1, reason); +#else +void grpc_cq_internal_ref(grpc_completion_queue *cc) { +#endif + gpr_ref(&cc->owning_refs); +} + +static void on_pollset_shutdown_done(grpc_exec_ctx *exec_ctx, void *arg, + bool success) { + grpc_completion_queue *cc = arg; + GRPC_CQ_INTERNAL_UNREF(cc, "pollset_destroy"); +} + +#ifdef GRPC_CQ_REF_COUNT_DEBUG +void grpc_cq_internal_unref(grpc_completion_queue *cc, const char *reason, + const char *file, int line) { + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, "CQ:%p unref %d -> %d %s", cc, + (int)cc->owning_refs.count, (int)cc->owning_refs.count - 1, reason); +#else +void grpc_cq_internal_unref(grpc_completion_queue *cc) { +#endif + if (gpr_unref(&cc->owning_refs)) { + GPR_ASSERT(cc->completed_head.next == (uintptr_t)&cc->completed_head); + grpc_pollset_reset(POLLSET_FROM_CQ(cc)); + gpr_mu_lock(&g_freelist_mu); + cc->next_free = g_freelist; + g_freelist = cc; + gpr_mu_unlock(&g_freelist_mu); + } +} + +void grpc_cq_begin_op(grpc_completion_queue *cc, void *tag) { +#ifndef NDEBUG + gpr_mu_lock(cc->mu); + GPR_ASSERT(!cc->shutdown_called); + if (cc->outstanding_tag_count == cc->outstanding_tag_capacity) { + cc->outstanding_tag_capacity = GPR_MAX(4, 2 * cc->outstanding_tag_capacity); + cc->outstanding_tags = + gpr_realloc(cc->outstanding_tags, sizeof(*cc->outstanding_tags) * + cc->outstanding_tag_capacity); + } + cc->outstanding_tags[cc->outstanding_tag_count++] = tag; + gpr_mu_unlock(cc->mu); +#endif + gpr_ref(&cc->pending_events); +} + +/* Signal the end of an operation - if this is the last waiting-to-be-queued + event, then enter shutdown mode */ +/* Queue a GRPC_OP_COMPLETED operation */ +void grpc_cq_end_op(grpc_exec_ctx *exec_ctx, grpc_completion_queue *cc, + void *tag, int success, + void (*done)(grpc_exec_ctx *exec_ctx, void *done_arg, + grpc_cq_completion *storage), + void *done_arg, grpc_cq_completion *storage) { + int shutdown; + int i; + grpc_pollset_worker *pluck_worker; +#ifndef NDEBUG + int found = 0; +#endif + + GPR_TIMER_BEGIN("grpc_cq_end_op", 0); + + storage->tag = tag; + storage->done = done; + storage->done_arg = done_arg; + storage->next = + ((uintptr_t)&cc->completed_head) | ((uintptr_t)(success != 0)); + + gpr_mu_lock(cc->mu); +#ifndef NDEBUG + for (i = 0; i < (int)cc->outstanding_tag_count; i++) { + if (cc->outstanding_tags[i] == tag) { + cc->outstanding_tag_count--; + GPR_SWAP(void *, cc->outstanding_tags[i], + cc->outstanding_tags[cc->outstanding_tag_count]); + found = 1; + break; + } + } + GPR_ASSERT(found); +#endif + shutdown = gpr_unref(&cc->pending_events); + if (!shutdown) { + cc->completed_tail->next = + ((uintptr_t)storage) | (1u & (uintptr_t)cc->completed_tail->next); + cc->completed_tail = storage; + pluck_worker = NULL; + for (i = 0; i < cc->num_pluckers; i++) { + if (cc->pluckers[i].tag == tag) { + pluck_worker = *cc->pluckers[i].worker; + break; + } + } + grpc_pollset_kick(POLLSET_FROM_CQ(cc), pluck_worker); + gpr_mu_unlock(cc->mu); + } else { + cc->completed_tail->next = + ((uintptr_t)storage) | (1u & (uintptr_t)cc->completed_tail->next); + cc->completed_tail = storage; + GPR_ASSERT(!cc->shutdown); + GPR_ASSERT(cc->shutdown_called); + cc->shutdown = 1; + grpc_pollset_shutdown(exec_ctx, POLLSET_FROM_CQ(cc), + &cc->pollset_shutdown_done); + gpr_mu_unlock(cc->mu); + } + + GPR_TIMER_END("grpc_cq_end_op", 0); +} + +grpc_event grpc_completion_queue_next(grpc_completion_queue *cc, + gpr_timespec deadline, void *reserved) { + grpc_event ret; + grpc_pollset_worker *worker = NULL; + int first_loop = 1; + gpr_timespec now; + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + + GPR_TIMER_BEGIN("grpc_completion_queue_next", 0); + + GRPC_API_TRACE( + "grpc_completion_queue_next(" + "cc=%p, " + "deadline=gpr_timespec { tv_sec: %lld, tv_nsec: %d, clock_type: %d }, " + "reserved=%p)", + 5, (cc, (long long)deadline.tv_sec, (int)deadline.tv_nsec, + (int)deadline.clock_type, reserved)); + GPR_ASSERT(!reserved); + + deadline = gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC); + + GRPC_CQ_INTERNAL_REF(cc, "next"); + gpr_mu_lock(cc->mu); + for (;;) { + if (cc->completed_tail != &cc->completed_head) { + grpc_cq_completion *c = (grpc_cq_completion *)cc->completed_head.next; + cc->completed_head.next = c->next & ~(uintptr_t)1; + if (c == cc->completed_tail) { + cc->completed_tail = &cc->completed_head; + } + gpr_mu_unlock(cc->mu); + ret.type = GRPC_OP_COMPLETE; + ret.success = c->next & 1u; + ret.tag = c->tag; + c->done(&exec_ctx, c->done_arg, c); + break; + } + if (cc->shutdown) { + gpr_mu_unlock(cc->mu); + memset(&ret, 0, sizeof(ret)); + ret.type = GRPC_QUEUE_SHUTDOWN; + break; + } + now = gpr_now(GPR_CLOCK_MONOTONIC); + if (!first_loop && gpr_time_cmp(now, deadline) >= 0) { + gpr_mu_unlock(cc->mu); + memset(&ret, 0, sizeof(ret)); + ret.type = GRPC_QUEUE_TIMEOUT; + break; + } + first_loop = 0; + /* Check alarms - these are a global resource so we just ping + each time through on every pollset. + May update deadline to ensure timely wakeups. + TODO(ctiller): can this work be localized? */ + gpr_timespec iteration_deadline = deadline; + if (grpc_timer_check(&exec_ctx, now, &iteration_deadline)) { + GPR_TIMER_MARK("alarm_triggered", 0); + gpr_mu_unlock(cc->mu); + grpc_exec_ctx_flush(&exec_ctx); + gpr_mu_lock(cc->mu); + continue; + } else { + grpc_pollset_work(&exec_ctx, POLLSET_FROM_CQ(cc), &worker, now, + iteration_deadline); + } + } + GRPC_SURFACE_TRACE_RETURNED_EVENT(cc, &ret); + GRPC_CQ_INTERNAL_UNREF(cc, "next"); + grpc_exec_ctx_finish(&exec_ctx); + + GPR_TIMER_END("grpc_completion_queue_next", 0); + + return ret; +} + +static int add_plucker(grpc_completion_queue *cc, void *tag, + grpc_pollset_worker **worker) { + if (cc->num_pluckers == GRPC_MAX_COMPLETION_QUEUE_PLUCKERS) { + return 0; + } + cc->pluckers[cc->num_pluckers].tag = tag; + cc->pluckers[cc->num_pluckers].worker = worker; + cc->num_pluckers++; + return 1; +} + +static void del_plucker(grpc_completion_queue *cc, void *tag, + grpc_pollset_worker **worker) { + int i; + for (i = 0; i < cc->num_pluckers; i++) { + if (cc->pluckers[i].tag == tag && cc->pluckers[i].worker == worker) { + cc->num_pluckers--; + GPR_SWAP(plucker, cc->pluckers[i], cc->pluckers[cc->num_pluckers]); + return; + } + } + GPR_UNREACHABLE_CODE(return ); +} + +grpc_event grpc_completion_queue_pluck(grpc_completion_queue *cc, void *tag, + gpr_timespec deadline, void *reserved) { + grpc_event ret; + grpc_cq_completion *c; + grpc_cq_completion *prev; + grpc_pollset_worker *worker = NULL; + gpr_timespec now; + int first_loop = 1; + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + + GPR_TIMER_BEGIN("grpc_completion_queue_pluck", 0); + + GRPC_API_TRACE( + "grpc_completion_queue_pluck(" + "cc=%p, tag=%p, " + "deadline=gpr_timespec { tv_sec: %lld, tv_nsec: %d, clock_type: %d }, " + "reserved=%p)", + 6, (cc, tag, (long long)deadline.tv_sec, (int)deadline.tv_nsec, + (int)deadline.clock_type, reserved)); + GPR_ASSERT(!reserved); + + deadline = gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC); + + GRPC_CQ_INTERNAL_REF(cc, "pluck"); + gpr_mu_lock(cc->mu); + for (;;) { + prev = &cc->completed_head; + while ((c = (grpc_cq_completion *)(prev->next & ~(uintptr_t)1)) != + &cc->completed_head) { + if (c->tag == tag) { + prev->next = (prev->next & (uintptr_t)1) | (c->next & ~(uintptr_t)1); + if (c == cc->completed_tail) { + cc->completed_tail = prev; + } + gpr_mu_unlock(cc->mu); + ret.type = GRPC_OP_COMPLETE; + ret.success = c->next & 1u; + ret.tag = c->tag; + c->done(&exec_ctx, c->done_arg, c); + goto done; + } + prev = c; + } + if (cc->shutdown) { + gpr_mu_unlock(cc->mu); + memset(&ret, 0, sizeof(ret)); + ret.type = GRPC_QUEUE_SHUTDOWN; + break; + } + if (!add_plucker(cc, tag, &worker)) { + gpr_log(GPR_DEBUG, + "Too many outstanding grpc_completion_queue_pluck calls: maximum " + "is %d", + GRPC_MAX_COMPLETION_QUEUE_PLUCKERS); + gpr_mu_unlock(cc->mu); + memset(&ret, 0, sizeof(ret)); + /* TODO(ctiller): should we use a different result here */ + ret.type = GRPC_QUEUE_TIMEOUT; + break; + } + now = gpr_now(GPR_CLOCK_MONOTONIC); + if (!first_loop && gpr_time_cmp(now, deadline) >= 0) { + del_plucker(cc, tag, &worker); + gpr_mu_unlock(cc->mu); + memset(&ret, 0, sizeof(ret)); + ret.type = GRPC_QUEUE_TIMEOUT; + break; + } + first_loop = 0; + /* Check alarms - these are a global resource so we just ping + each time through on every pollset. + May update deadline to ensure timely wakeups. + TODO(ctiller): can this work be localized? */ + gpr_timespec iteration_deadline = deadline; + if (grpc_timer_check(&exec_ctx, now, &iteration_deadline)) { + GPR_TIMER_MARK("alarm_triggered", 0); + gpr_mu_unlock(cc->mu); + grpc_exec_ctx_flush(&exec_ctx); + gpr_mu_lock(cc->mu); + } else { + grpc_pollset_work(&exec_ctx, POLLSET_FROM_CQ(cc), &worker, now, + iteration_deadline); + } + del_plucker(cc, tag, &worker); + } +done: + GRPC_SURFACE_TRACE_RETURNED_EVENT(cc, &ret); + GRPC_CQ_INTERNAL_UNREF(cc, "pluck"); + grpc_exec_ctx_finish(&exec_ctx); + + GPR_TIMER_END("grpc_completion_queue_pluck", 0); + + return ret; +} + +/* Shutdown simply drops a ref that we reserved at creation time; if we drop + to zero here, then enter shutdown mode and wake up any waiters */ +void grpc_completion_queue_shutdown(grpc_completion_queue *cc) { + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + GPR_TIMER_BEGIN("grpc_completion_queue_shutdown", 0); + GRPC_API_TRACE("grpc_completion_queue_shutdown(cc=%p)", 1, (cc)); + gpr_mu_lock(cc->mu); + if (cc->shutdown_called) { + gpr_mu_unlock(cc->mu); + GPR_TIMER_END("grpc_completion_queue_shutdown", 0); + return; + } + cc->shutdown_called = 1; + if (gpr_unref(&cc->pending_events)) { + GPR_ASSERT(!cc->shutdown); + cc->shutdown = 1; + grpc_pollset_shutdown(&exec_ctx, POLLSET_FROM_CQ(cc), + &cc->pollset_shutdown_done); + } + gpr_mu_unlock(cc->mu); + grpc_exec_ctx_finish(&exec_ctx); + GPR_TIMER_END("grpc_completion_queue_shutdown", 0); +} + +void grpc_completion_queue_destroy(grpc_completion_queue *cc) { + GRPC_API_TRACE("grpc_completion_queue_destroy(cc=%p)", 1, (cc)); + GPR_TIMER_BEGIN("grpc_completion_queue_destroy", 0); + grpc_completion_queue_shutdown(cc); + GRPC_CQ_INTERNAL_UNREF(cc, "destroy"); + GPR_TIMER_END("grpc_completion_queue_destroy", 0); +} + +grpc_pollset *grpc_cq_pollset(grpc_completion_queue *cc) { + return POLLSET_FROM_CQ(cc); +} + +void grpc_cq_mark_server_cq(grpc_completion_queue *cc) { cc->is_server_cq = 1; } + +int grpc_cq_is_server_cq(grpc_completion_queue *cc) { return cc->is_server_cq; } diff --git a/src/core/lib/surface/completion_queue.h b/src/core/lib/surface/completion_queue.h new file mode 100644 index 0000000000..35591cb6f4 --- /dev/null +++ b/src/core/lib/surface/completion_queue.h @@ -0,0 +1,91 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SURFACE_COMPLETION_QUEUE_H +#define GRPC_CORE_LIB_SURFACE_COMPLETION_QUEUE_H + +/* Internal API for completion queues */ + +#include <grpc/grpc.h> +#include "src/core/lib/iomgr/pollset.h" + +typedef struct grpc_cq_completion { + /** user supplied tag */ + void *tag; + /** done callback - called when this queue element is no longer + needed by the completion queue */ + void (*done)(grpc_exec_ctx *exec_ctx, void *done_arg, + struct grpc_cq_completion *c); + void *done_arg; + /** next pointer; low bit is used to indicate success or not */ + uintptr_t next; +} grpc_cq_completion; + +#ifdef GRPC_CQ_REF_COUNT_DEBUG +void grpc_cq_internal_ref(grpc_completion_queue *cc, const char *reason, + const char *file, int line); +void grpc_cq_internal_unref(grpc_completion_queue *cc, const char *reason, + const char *file, int line); +#define GRPC_CQ_INTERNAL_REF(cc, reason) \ + grpc_cq_internal_ref(cc, reason, __FILE__, __LINE__) +#define GRPC_CQ_INTERNAL_UNREF(cc, reason) \ + grpc_cq_internal_unref(cc, reason, __FILE__, __LINE__) +#else +void grpc_cq_internal_ref(grpc_completion_queue *cc); +void grpc_cq_internal_unref(grpc_completion_queue *cc); +#define GRPC_CQ_INTERNAL_REF(cc, reason) grpc_cq_internal_ref(cc) +#define GRPC_CQ_INTERNAL_UNREF(cc, reason) grpc_cq_internal_unref(cc) +#endif + +/* Flag that an operation is beginning: the completion channel will not finish + shutdown until a corrensponding grpc_cq_end_* call is made. + \a tag is currently used only in debug builds. */ +void grpc_cq_begin_op(grpc_completion_queue *cc, void *tag); + +/* Queue a GRPC_OP_COMPLETED operation; tag must correspond to the tag passed to + grpc_cq_begin_op */ +void grpc_cq_end_op(grpc_exec_ctx *exec_ctx, grpc_completion_queue *cc, + void *tag, int success, + void (*done)(grpc_exec_ctx *exec_ctx, void *done_arg, + grpc_cq_completion *storage), + void *done_arg, grpc_cq_completion *storage); + +grpc_pollset *grpc_cq_pollset(grpc_completion_queue *cc); + +void grpc_cq_mark_server_cq(grpc_completion_queue *cc); +int grpc_cq_is_server_cq(grpc_completion_queue *cc); + +void grpc_cq_global_init(void); +void grpc_cq_global_shutdown(void); + +#endif /* GRPC_CORE_LIB_SURFACE_COMPLETION_QUEUE_H */ diff --git a/src/core/lib/surface/event_string.c b/src/core/lib/surface/event_string.c new file mode 100644 index 0000000000..360c718a17 --- /dev/null +++ b/src/core/lib/surface/event_string.c @@ -0,0 +1,81 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/surface/event_string.h" + +#include <stdio.h> + +#include <grpc/byte_buffer.h> +#include <grpc/support/string_util.h> +#include "src/core/lib/support/string.h" + +static void addhdr(gpr_strvec *buf, grpc_event *ev) { + char *tmp; + gpr_asprintf(&tmp, "tag:%p", ev->tag); + gpr_strvec_add(buf, tmp); +} + +static const char *errstr(int success) { return success ? "OK" : "ERROR"; } + +static void adderr(gpr_strvec *buf, int success) { + char *tmp; + gpr_asprintf(&tmp, " %s", errstr(success)); + gpr_strvec_add(buf, tmp); +} + +char *grpc_event_string(grpc_event *ev) { + char *out; + gpr_strvec buf; + + if (ev == NULL) return gpr_strdup("null"); + + gpr_strvec_init(&buf); + + switch (ev->type) { + case GRPC_QUEUE_TIMEOUT: + gpr_strvec_add(&buf, gpr_strdup("QUEUE_TIMEOUT")); + break; + case GRPC_QUEUE_SHUTDOWN: + gpr_strvec_add(&buf, gpr_strdup("QUEUE_SHUTDOWN")); + break; + case GRPC_OP_COMPLETE: + gpr_strvec_add(&buf, gpr_strdup("OP_COMPLETE: ")); + addhdr(&buf, ev); + adderr(&buf, ev->success); + break; + } + + out = gpr_strvec_flatten(&buf, NULL); + gpr_strvec_destroy(&buf); + return out; +} diff --git a/src/core/lib/surface/event_string.h b/src/core/lib/surface/event_string.h new file mode 100644 index 0000000000..577e9c718f --- /dev/null +++ b/src/core/lib/surface/event_string.h @@ -0,0 +1,42 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SURFACE_EVENT_STRING_H +#define GRPC_CORE_LIB_SURFACE_EVENT_STRING_H + +#include <grpc/grpc.h> + +/* Returns a string describing an event. Must be later freed with gpr_free() */ +char *grpc_event_string(grpc_event *ev); + +#endif /* GRPC_CORE_LIB_SURFACE_EVENT_STRING_H */ diff --git a/src/core/lib/surface/init.c b/src/core/lib/surface/init.c new file mode 100644 index 0000000000..dcb9c62d36 --- /dev/null +++ b/src/core/lib/surface/init.c @@ -0,0 +1,239 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/support/port_platform.h> + +#include <limits.h> +#include <memory.h> + +#include <grpc/grpc.h> +#include <grpc/support/alloc.h> +#include <grpc/support/time.h> +/* TODO(ctiller): find another way? - better not to include census here */ +#include "src/core/lib/census/grpc_plugin.h" +#include "src/core/lib/channel/channel_stack.h" +#include "src/core/lib/channel/client_channel.h" +#include "src/core/lib/channel/compress_filter.h" +#include "src/core/lib/channel/connected_channel.h" +#include "src/core/lib/channel/http_client_filter.h" +#include "src/core/lib/channel/http_server_filter.h" +#include "src/core/lib/client_config/lb_policies/pick_first.h" +#include "src/core/lib/client_config/lb_policies/round_robin.h" +#include "src/core/lib/client_config/lb_policy_registry.h" +#include "src/core/lib/client_config/resolver_registry.h" +#include "src/core/lib/client_config/resolvers/dns_resolver.h" +#include "src/core/lib/client_config/resolvers/sockaddr_resolver.h" +#include "src/core/lib/client_config/subchannel.h" +#include "src/core/lib/client_config/subchannel_index.h" +#include "src/core/lib/debug/trace.h" +#include "src/core/lib/iomgr/executor.h" +#include "src/core/lib/iomgr/iomgr.h" +#include "src/core/lib/profiling/timers.h" +#include "src/core/lib/surface/api_trace.h" +#include "src/core/lib/surface/call.h" +#include "src/core/lib/surface/channel_init.h" +#include "src/core/lib/surface/completion_queue.h" +#include "src/core/lib/surface/init.h" +#include "src/core/lib/surface/lame_client.h" +#include "src/core/lib/surface/server.h" +#include "src/core/lib/surface/surface_trace.h" +#include "src/core/lib/transport/chttp2_transport.h" +#include "src/core/lib/transport/connectivity_state.h" +#include "src/core/lib/transport/transport_impl.h" + +#ifndef GRPC_DEFAULT_NAME_PREFIX +#define GRPC_DEFAULT_NAME_PREFIX "dns:///" +#endif + +#define MAX_PLUGINS 128 + +static gpr_once g_basic_init = GPR_ONCE_INIT; +static gpr_mu g_init_mu; +static int g_initializations; + +static void do_basic_init(void) { + gpr_mu_init(&g_init_mu); + /* TODO(ctiller): ideally remove this strict linkage */ + grpc_register_plugin(census_grpc_plugin_init, census_grpc_plugin_destroy); + g_initializations = 0; +} + +static bool append_filter(grpc_channel_stack_builder *builder, void *arg) { + return grpc_channel_stack_builder_append_filter( + builder, (const grpc_channel_filter *)arg, NULL, NULL); +} + +static bool prepend_filter(grpc_channel_stack_builder *builder, void *arg) { + return grpc_channel_stack_builder_prepend_filter( + builder, (const grpc_channel_filter *)arg, NULL, NULL); +} + +static bool maybe_add_http_filter(grpc_channel_stack_builder *builder, + void *arg) { + grpc_transport *t = grpc_channel_stack_builder_get_transport(builder); + if (t && strstr(t->vtable->name, "http")) { + return grpc_channel_stack_builder_prepend_filter( + builder, (const grpc_channel_filter *)arg, NULL, NULL); + } + return true; +} + +static void register_builtin_channel_init() { + grpc_channel_init_register_stage(GRPC_CLIENT_CHANNEL, INT_MAX, prepend_filter, + (void *)&grpc_compress_filter); + grpc_channel_init_register_stage(GRPC_CLIENT_DIRECT_CHANNEL, INT_MAX, + prepend_filter, + (void *)&grpc_compress_filter); + grpc_channel_init_register_stage(GRPC_SERVER_CHANNEL, INT_MAX, prepend_filter, + (void *)&grpc_compress_filter); + grpc_channel_init_register_stage(GRPC_CLIENT_SUBCHANNEL, INT_MAX, + maybe_add_http_filter, + (void *)&grpc_http_client_filter); + grpc_channel_init_register_stage(GRPC_CLIENT_SUBCHANNEL, INT_MAX, + grpc_add_connected_filter, NULL); + grpc_channel_init_register_stage(GRPC_CLIENT_DIRECT_CHANNEL, INT_MAX, + maybe_add_http_filter, + (void *)&grpc_http_client_filter); + grpc_channel_init_register_stage(GRPC_CLIENT_DIRECT_CHANNEL, INT_MAX, + grpc_add_connected_filter, NULL); + grpc_channel_init_register_stage(GRPC_SERVER_CHANNEL, INT_MAX, + maybe_add_http_filter, + (void *)&grpc_http_server_filter); + grpc_channel_init_register_stage(GRPC_SERVER_CHANNEL, INT_MAX, + grpc_add_connected_filter, NULL); + grpc_channel_init_register_stage(GRPC_CLIENT_CHANNEL, INT_MAX, append_filter, + (void *)&grpc_client_channel_filter); + grpc_channel_init_register_stage(GRPC_CLIENT_LAME_CHANNEL, INT_MAX, + append_filter, (void *)&grpc_lame_filter); + grpc_channel_init_register_stage(GRPC_SERVER_CHANNEL, INT_MAX, prepend_filter, + (void *)&grpc_server_top_filter); +} + +typedef struct grpc_plugin { + void (*init)(); + void (*destroy)(); +} grpc_plugin; + +static grpc_plugin g_all_of_the_plugins[MAX_PLUGINS]; +static int g_number_of_plugins = 0; + +void grpc_register_plugin(void (*init)(void), void (*destroy)(void)) { + GRPC_API_TRACE("grpc_register_plugin(init=%p, destroy=%p)", 2, + ((void *)(intptr_t)init, (void *)(intptr_t)destroy)); + GPR_ASSERT(g_number_of_plugins != MAX_PLUGINS); + g_all_of_the_plugins[g_number_of_plugins].init = init; + g_all_of_the_plugins[g_number_of_plugins].destroy = destroy; + g_number_of_plugins++; +} + +void grpc_init(void) { + int i; + gpr_once_init(&g_basic_init, do_basic_init); + + gpr_mu_lock(&g_init_mu); + if (++g_initializations == 1) { + gpr_time_init(); + grpc_mdctx_global_init(); + grpc_channel_init_init(); + grpc_lb_policy_registry_init(grpc_pick_first_lb_factory_create()); + grpc_register_lb_policy(grpc_pick_first_lb_factory_create()); + grpc_register_lb_policy(grpc_round_robin_lb_factory_create()); + grpc_resolver_registry_init(GRPC_DEFAULT_NAME_PREFIX); + grpc_register_resolver_type(grpc_dns_resolver_factory_create()); + grpc_register_resolver_type(grpc_ipv4_resolver_factory_create()); + grpc_register_resolver_type(grpc_ipv6_resolver_factory_create()); +#ifdef GPR_POSIX_SOCKET + grpc_register_resolver_type(grpc_unix_resolver_factory_create()); +#endif + grpc_register_tracer("api", &grpc_api_trace); + grpc_register_tracer("channel", &grpc_trace_channel); + grpc_register_tracer("http", &grpc_http_trace); + grpc_register_tracer("flowctl", &grpc_flowctl_trace); + grpc_register_tracer("connectivity_state", &grpc_connectivity_state_trace); + grpc_register_tracer("channel_stack_builder", + &grpc_trace_channel_stack_builder); + grpc_security_pre_init(); + grpc_iomgr_init(); + grpc_executor_init(); + grpc_tracer_init("GRPC_TRACE"); + gpr_timers_global_init(); + grpc_cq_global_init(); + grpc_subchannel_index_init(); + for (i = 0; i < g_number_of_plugins; i++) { + if (g_all_of_the_plugins[i].init != NULL) { + g_all_of_the_plugins[i].init(); + } + } + /* register channel finalization AFTER all plugins, to ensure that it's run + * at the appropriate time */ + grpc_register_security_filters(); + register_builtin_channel_init(); + /* no more changes to channel init pipelines */ + grpc_channel_init_finalize(); + } + gpr_mu_unlock(&g_init_mu); + GRPC_API_TRACE("grpc_init(void)", 0, ()); +} + +void grpc_shutdown(void) { + int i; + GRPC_API_TRACE("grpc_shutdown(void)", 0, ()); + gpr_mu_lock(&g_init_mu); + if (--g_initializations == 0) { + grpc_executor_shutdown(); + grpc_cq_global_shutdown(); + grpc_iomgr_shutdown(); + grpc_subchannel_index_shutdown(); + gpr_timers_global_destroy(); + grpc_tracer_shutdown(); + grpc_resolver_registry_shutdown(); + grpc_lb_policy_registry_shutdown(); + for (i = 0; i < g_number_of_plugins; i++) { + if (g_all_of_the_plugins[i].destroy != NULL) { + g_all_of_the_plugins[i].destroy(); + } + } + grpc_channel_init_shutdown(); + grpc_mdctx_global_shutdown(); + } + gpr_mu_unlock(&g_init_mu); +} + +int grpc_is_initialized(void) { + int r; + gpr_once_init(&g_basic_init, do_basic_init); + gpr_mu_lock(&g_init_mu); + r = g_initializations > 0; + gpr_mu_unlock(&g_init_mu); + return r; +} diff --git a/src/core/lib/surface/init.h b/src/core/lib/surface/init.h new file mode 100644 index 0000000000..10e2a5896e --- /dev/null +++ b/src/core/lib/surface/init.h @@ -0,0 +1,41 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SURFACE_INIT_H +#define GRPC_CORE_LIB_SURFACE_INIT_H + +void grpc_register_security_filters(void); +void grpc_security_pre_init(void); +int grpc_is_initialized(void); + +#endif /* GRPC_CORE_LIB_SURFACE_INIT_H */ diff --git a/src/core/lib/surface/init_secure.c b/src/core/lib/surface/init_secure.c new file mode 100644 index 0000000000..d3c2f645a7 --- /dev/null +++ b/src/core/lib/surface/init_secure.c @@ -0,0 +1,89 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/surface/init.h" + +#include <limits.h> +#include <string.h> + +#include "src/core/lib/debug/trace.h" +#include "src/core/lib/security/auth_filters.h" +#include "src/core/lib/security/credentials.h" +#include "src/core/lib/security/secure_endpoint.h" +#include "src/core/lib/security/security_connector.h" +#include "src/core/lib/surface/channel_init.h" +#include "src/core/lib/tsi/transport_security_interface.h" + +void grpc_security_pre_init(void) { + grpc_register_tracer("secure_endpoint", &grpc_trace_secure_endpoint); + grpc_register_tracer("transport_security", &tsi_tracing_enabled); +} + +static bool maybe_prepend_client_auth_filter( + grpc_channel_stack_builder *builder, void *arg) { + const grpc_channel_args *args = + grpc_channel_stack_builder_get_channel_arguments(builder); + if (args) { + for (size_t i = 0; i < args->num_args; i++) { + if (0 == strcmp(GRPC_SECURITY_CONNECTOR_ARG, args->args[i].key)) { + return grpc_channel_stack_builder_prepend_filter( + builder, &grpc_client_auth_filter, NULL, NULL); + } + } + } + return true; +} + +static bool maybe_prepend_server_auth_filter( + grpc_channel_stack_builder *builder, void *arg) { + const grpc_channel_args *args = + grpc_channel_stack_builder_get_channel_arguments(builder); + if (args) { + for (size_t i = 0; i < args->num_args; i++) { + if (0 == strcmp(GRPC_SERVER_CREDENTIALS_ARG, args->args[i].key)) { + return grpc_channel_stack_builder_prepend_filter( + builder, &grpc_server_auth_filter, NULL, NULL); + } + } + } + return true; +} + +void grpc_register_security_filters(void) { + grpc_channel_init_register_stage(GRPC_CLIENT_SUBCHANNEL, INT_MAX, + maybe_prepend_client_auth_filter, NULL); + grpc_channel_init_register_stage(GRPC_CLIENT_DIRECT_CHANNEL, INT_MAX, + maybe_prepend_client_auth_filter, NULL); + grpc_channel_init_register_stage(GRPC_SERVER_CHANNEL, INT_MAX, + maybe_prepend_server_auth_filter, NULL); +} diff --git a/src/core/lib/surface/init_unsecure.c b/src/core/lib/surface/init_unsecure.c new file mode 100644 index 0000000000..243c005d86 --- /dev/null +++ b/src/core/lib/surface/init_unsecure.c @@ -0,0 +1,38 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/surface/init.h" + +void grpc_security_pre_init(void) {} + +void grpc_register_security_filters(void) {} diff --git a/src/core/lib/surface/lame_client.c b/src/core/lib/surface/lame_client.c new file mode 100644 index 0000000000..95ec4b06c3 --- /dev/null +++ b/src/core/lib/surface/lame_client.c @@ -0,0 +1,155 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/surface/lame_client.h" + +#include <grpc/grpc.h> + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include "src/core/lib/channel/channel_stack.h" +#include "src/core/lib/support/string.h" +#include "src/core/lib/surface/api_trace.h" +#include "src/core/lib/surface/call.h" +#include "src/core/lib/surface/channel.h" + +typedef struct { + grpc_linked_mdelem status; + grpc_linked_mdelem details; +} call_data; + +typedef struct { + grpc_status_code error_code; + const char *error_message; +} channel_data; + +static void fill_metadata(grpc_call_element *elem, grpc_metadata_batch *mdb) { + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + char tmp[GPR_LTOA_MIN_BUFSIZE]; + gpr_ltoa(chand->error_code, tmp); + calld->status.md = grpc_mdelem_from_strings("grpc-status", tmp); + calld->details.md = + grpc_mdelem_from_strings("grpc-message", chand->error_message); + calld->status.prev = calld->details.next = NULL; + calld->status.next = &calld->details; + calld->details.prev = &calld->status; + mdb->list.head = &calld->status; + mdb->list.tail = &calld->details; + mdb->deadline = gpr_inf_future(GPR_CLOCK_REALTIME); +} + +static void lame_start_transport_stream_op(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem, + grpc_transport_stream_op *op) { + GRPC_CALL_LOG_OP(GPR_INFO, elem, op); + if (op->recv_initial_metadata != NULL) { + fill_metadata(elem, op->recv_initial_metadata); + } else if (op->recv_trailing_metadata != NULL) { + fill_metadata(elem, op->recv_trailing_metadata); + } + grpc_transport_stream_op_finish_with_failure(exec_ctx, op); +} + +static char *lame_get_peer(grpc_exec_ctx *exec_ctx, grpc_call_element *elem) { + return NULL; +} + +static void lame_start_transport_op(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem, + grpc_transport_op *op) { + if (op->on_connectivity_state_change) { + GPR_ASSERT(*op->connectivity_state != GRPC_CHANNEL_FATAL_FAILURE); + *op->connectivity_state = GRPC_CHANNEL_FATAL_FAILURE; + op->on_connectivity_state_change->cb( + exec_ctx, op->on_connectivity_state_change->cb_arg, 1); + } + if (op->on_consumed != NULL) { + op->on_consumed->cb(exec_ctx, op->on_consumed->cb_arg, 1); + } +} + +static void init_call_elem(grpc_exec_ctx *exec_ctx, grpc_call_element *elem, + grpc_call_element_args *args) {} + +static void destroy_call_elem(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem) {} + +static void init_channel_elem(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem, + grpc_channel_element_args *args) { + GPR_ASSERT(args->is_first); + GPR_ASSERT(args->is_last); +} + +static void destroy_channel_elem(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem) {} + +const grpc_channel_filter grpc_lame_filter = { + lame_start_transport_stream_op, + lame_start_transport_op, + sizeof(call_data), + init_call_elem, + grpc_call_stack_ignore_set_pollset, + destroy_call_elem, + sizeof(channel_data), + init_channel_elem, + destroy_channel_elem, + lame_get_peer, + "lame-client", +}; + +#define CHANNEL_STACK_FROM_CHANNEL(c) ((grpc_channel_stack *)((c) + 1)) + +grpc_channel *grpc_lame_client_channel_create(const char *target, + grpc_status_code error_code, + const char *error_message) { + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + grpc_channel_element *elem; + channel_data *chand; + grpc_channel *channel = grpc_channel_create(&exec_ctx, target, NULL, + GRPC_CLIENT_LAME_CHANNEL, NULL); + elem = grpc_channel_stack_element(grpc_channel_get_channel_stack(channel), 0); + GRPC_API_TRACE( + "grpc_lame_client_channel_create(target=%s, error_code=%d, " + "error_message=%s)", + 3, (target, (int)error_code, error_message)); + GPR_ASSERT(elem->filter == &grpc_lame_filter); + chand = (channel_data *)elem->channel_data; + chand->error_code = error_code; + chand->error_message = error_message; + grpc_exec_ctx_finish(&exec_ctx); + return channel; +} diff --git a/src/core/lib/surface/lame_client.h b/src/core/lib/surface/lame_client.h new file mode 100644 index 0000000000..5f6ea34d4b --- /dev/null +++ b/src/core/lib/surface/lame_client.h @@ -0,0 +1,41 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SURFACE_LAME_CLIENT_H +#define GRPC_CORE_LIB_SURFACE_LAME_CLIENT_H + +#include "src/core/lib/channel/channel_stack.h" + +extern const grpc_channel_filter grpc_lame_filter; + +#endif /* GRPC_CORE_LIB_SURFACE_LAME_CLIENT_H */ diff --git a/src/core/lib/surface/metadata_array.c b/src/core/lib/surface/metadata_array.c new file mode 100644 index 0000000000..4436f2da87 --- /dev/null +++ b/src/core/lib/surface/metadata_array.c @@ -0,0 +1,49 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/grpc.h> +#include <grpc/support/alloc.h> + +#include <string.h> + +#include "src/core/lib/surface/api_trace.h" + +void grpc_metadata_array_init(grpc_metadata_array* array) { + GRPC_API_TRACE("grpc_metadata_array_init(array=%p)", 1, (array)); + memset(array, 0, sizeof(*array)); +} + +void grpc_metadata_array_destroy(grpc_metadata_array* array) { + GRPC_API_TRACE("grpc_metadata_array_destroy(array=%p)", 1, (array)); + gpr_free(array->metadata); +} diff --git a/src/core/lib/surface/secure_channel_create.c b/src/core/lib/surface/secure_channel_create.c new file mode 100644 index 0000000000..dcb367023e --- /dev/null +++ b/src/core/lib/surface/secure_channel_create.c @@ -0,0 +1,319 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/grpc.h> + +#include <stdlib.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/slice.h> +#include <grpc/support/slice_buffer.h> + +#include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/channel/client_channel.h" +#include "src/core/lib/client_config/resolver_registry.h" +#include "src/core/lib/iomgr/tcp_client.h" +#include "src/core/lib/security/auth_filters.h" +#include "src/core/lib/security/credentials.h" +#include "src/core/lib/security/security_context.h" +#include "src/core/lib/surface/api_trace.h" +#include "src/core/lib/surface/channel.h" +#include "src/core/lib/transport/chttp2_transport.h" +#include "src/core/lib/tsi/transport_security_interface.h" + +typedef struct { + grpc_connector base; + gpr_refcount refs; + + grpc_channel_security_connector *security_connector; + + grpc_closure *notify; + grpc_connect_in_args args; + grpc_connect_out_args *result; + grpc_closure initial_string_sent; + gpr_slice_buffer initial_string_buffer; + + gpr_mu mu; + grpc_endpoint *connecting_endpoint; + grpc_endpoint *newly_connecting_endpoint; + + grpc_closure connected_closure; +} connector; + +static void connector_ref(grpc_connector *con) { + connector *c = (connector *)con; + gpr_ref(&c->refs); +} + +static void connector_unref(grpc_exec_ctx *exec_ctx, grpc_connector *con) { + connector *c = (connector *)con; + if (gpr_unref(&c->refs)) { + /* c->initial_string_buffer does not need to be destroyed */ + gpr_free(c); + } +} + +static void on_secure_handshake_done(grpc_exec_ctx *exec_ctx, void *arg, + grpc_security_status status, + grpc_endpoint *secure_endpoint, + grpc_auth_context *auth_context) { + connector *c = arg; + grpc_closure *notify; + grpc_channel_args *args_copy = NULL; + gpr_mu_lock(&c->mu); + if (c->connecting_endpoint == NULL) { + memset(c->result, 0, sizeof(*c->result)); + gpr_mu_unlock(&c->mu); + } else if (status != GRPC_SECURITY_OK) { + gpr_log(GPR_ERROR, "Secure handshake failed with error %d.", status); + memset(c->result, 0, sizeof(*c->result)); + c->connecting_endpoint = NULL; + gpr_mu_unlock(&c->mu); + } else { + grpc_arg auth_context_arg; + c->connecting_endpoint = NULL; + gpr_mu_unlock(&c->mu); + c->result->transport = grpc_create_chttp2_transport( + exec_ctx, c->args.channel_args, secure_endpoint, 1); + grpc_chttp2_transport_start_reading(exec_ctx, c->result->transport, NULL, + 0); + auth_context_arg = grpc_auth_context_to_arg(auth_context); + args_copy = grpc_channel_args_copy_and_add(c->args.channel_args, + &auth_context_arg, 1); + c->result->channel_args = args_copy; + } + notify = c->notify; + c->notify = NULL; + /* look at c->args which are connector args. */ + notify->cb(exec_ctx, notify->cb_arg, 1); + if (args_copy != NULL) grpc_channel_args_destroy(args_copy); +} + +static void on_initial_connect_string_sent(grpc_exec_ctx *exec_ctx, void *arg, + bool success) { + connector *c = arg; + grpc_channel_security_connector_do_handshake(exec_ctx, c->security_connector, + c->connecting_endpoint, + on_secure_handshake_done, c); +} + +static void connected(grpc_exec_ctx *exec_ctx, void *arg, bool success) { + connector *c = arg; + grpc_closure *notify; + grpc_endpoint *tcp = c->newly_connecting_endpoint; + if (tcp != NULL) { + gpr_mu_lock(&c->mu); + GPR_ASSERT(c->connecting_endpoint == NULL); + c->connecting_endpoint = tcp; + gpr_mu_unlock(&c->mu); + if (!GPR_SLICE_IS_EMPTY(c->args.initial_connect_string)) { + grpc_closure_init(&c->initial_string_sent, on_initial_connect_string_sent, + c); + gpr_slice_buffer_init(&c->initial_string_buffer); + gpr_slice_buffer_add(&c->initial_string_buffer, + c->args.initial_connect_string); + grpc_endpoint_write(exec_ctx, tcp, &c->initial_string_buffer, + &c->initial_string_sent); + } else { + grpc_channel_security_connector_do_handshake( + exec_ctx, c->security_connector, tcp, on_secure_handshake_done, c); + } + } else { + memset(c->result, 0, sizeof(*c->result)); + notify = c->notify; + c->notify = NULL; + notify->cb(exec_ctx, notify->cb_arg, 1); + } +} + +static void connector_shutdown(grpc_exec_ctx *exec_ctx, grpc_connector *con) { + connector *c = (connector *)con; + grpc_endpoint *ep; + gpr_mu_lock(&c->mu); + ep = c->connecting_endpoint; + c->connecting_endpoint = NULL; + gpr_mu_unlock(&c->mu); + if (ep) { + grpc_endpoint_shutdown(exec_ctx, ep); + } +} + +static void connector_connect(grpc_exec_ctx *exec_ctx, grpc_connector *con, + const grpc_connect_in_args *args, + grpc_connect_out_args *result, + grpc_closure *notify) { + connector *c = (connector *)con; + GPR_ASSERT(c->notify == NULL); + GPR_ASSERT(notify->cb); + c->notify = notify; + c->args = *args; + c->result = result; + gpr_mu_lock(&c->mu); + GPR_ASSERT(c->connecting_endpoint == NULL); + gpr_mu_unlock(&c->mu); + grpc_closure_init(&c->connected_closure, connected, c); + grpc_tcp_client_connect( + exec_ctx, &c->connected_closure, &c->newly_connecting_endpoint, + args->interested_parties, args->addr, args->addr_len, args->deadline); +} + +static const grpc_connector_vtable connector_vtable = { + connector_ref, connector_unref, connector_shutdown, connector_connect}; + +typedef struct { + grpc_subchannel_factory base; + gpr_refcount refs; + grpc_channel_args *merge_args; + grpc_channel_security_connector *security_connector; + grpc_channel *master; +} subchannel_factory; + +static void subchannel_factory_ref(grpc_subchannel_factory *scf) { + subchannel_factory *f = (subchannel_factory *)scf; + gpr_ref(&f->refs); +} + +static void subchannel_factory_unref(grpc_exec_ctx *exec_ctx, + grpc_subchannel_factory *scf) { + subchannel_factory *f = (subchannel_factory *)scf; + if (gpr_unref(&f->refs)) { + GRPC_SECURITY_CONNECTOR_UNREF(&f->security_connector->base, + "subchannel_factory"); + GRPC_CHANNEL_INTERNAL_UNREF(exec_ctx, f->master, "subchannel_factory"); + grpc_channel_args_destroy(f->merge_args); + gpr_free(f); + } +} + +static grpc_subchannel *subchannel_factory_create_subchannel( + grpc_exec_ctx *exec_ctx, grpc_subchannel_factory *scf, + grpc_subchannel_args *args) { + subchannel_factory *f = (subchannel_factory *)scf; + connector *c = gpr_malloc(sizeof(*c)); + grpc_channel_args *final_args = + grpc_channel_args_merge(args->args, f->merge_args); + grpc_subchannel *s; + memset(c, 0, sizeof(*c)); + c->base.vtable = &connector_vtable; + c->security_connector = f->security_connector; + gpr_mu_init(&c->mu); + gpr_ref_init(&c->refs, 1); + args->args = final_args; + s = grpc_subchannel_create(exec_ctx, &c->base, args); + grpc_connector_unref(exec_ctx, &c->base); + grpc_channel_args_destroy(final_args); + return s; +} + +static const grpc_subchannel_factory_vtable subchannel_factory_vtable = { + subchannel_factory_ref, subchannel_factory_unref, + subchannel_factory_create_subchannel}; + +/* Create a secure client channel: + Asynchronously: - resolve target + - connect to it (trying alternatives as presented) + - perform handshakes */ +grpc_channel *grpc_secure_channel_create(grpc_channel_credentials *creds, + const char *target, + const grpc_channel_args *args, + void *reserved) { + grpc_channel *channel; + grpc_arg connector_arg; + grpc_channel_args *args_copy; + grpc_channel_args *new_args_from_connector; + grpc_channel_security_connector *security_connector; + grpc_resolver *resolver; + subchannel_factory *f; + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + + GRPC_API_TRACE( + "grpc_secure_channel_create(creds=%p, target=%s, args=%p, " + "reserved=%p)", + 4, (creds, target, args, reserved)); + GPR_ASSERT(reserved == NULL); + + if (grpc_find_security_connector_in_args(args) != NULL) { + gpr_log(GPR_ERROR, "Cannot set security context in channel args."); + grpc_exec_ctx_finish(&exec_ctx); + return grpc_lame_client_channel_create( + target, GRPC_STATUS_INVALID_ARGUMENT, + "Security connector exists in channel args."); + } + + if (grpc_channel_credentials_create_security_connector( + creds, target, args, &security_connector, &new_args_from_connector) != + GRPC_SECURITY_OK) { + grpc_exec_ctx_finish(&exec_ctx); + return grpc_lame_client_channel_create( + target, GRPC_STATUS_INVALID_ARGUMENT, + "Failed to create security connector."); + } + + connector_arg = grpc_security_connector_to_arg(&security_connector->base); + args_copy = grpc_channel_args_copy_and_add( + new_args_from_connector != NULL ? new_args_from_connector : args, + &connector_arg, 1); + + channel = grpc_channel_create(&exec_ctx, target, args_copy, + GRPC_CLIENT_CHANNEL, NULL); + + f = gpr_malloc(sizeof(*f)); + f->base.vtable = &subchannel_factory_vtable; + gpr_ref_init(&f->refs, 1); + GRPC_SECURITY_CONNECTOR_REF(&security_connector->base, "subchannel_factory"); + f->security_connector = security_connector; + f->merge_args = grpc_channel_args_copy(args_copy); + f->master = channel; + GRPC_CHANNEL_INTERNAL_REF(channel, "subchannel_factory"); + resolver = grpc_resolver_create(target, &f->base); + if (resolver) { + grpc_client_channel_set_resolver( + &exec_ctx, grpc_channel_get_channel_stack(channel), resolver); + GRPC_RESOLVER_UNREF(&exec_ctx, resolver, "create"); + } + grpc_subchannel_factory_unref(&exec_ctx, &f->base); + GRPC_SECURITY_CONNECTOR_UNREF(&security_connector->base, "channel_create"); + grpc_channel_args_destroy(args_copy); + if (new_args_from_connector != NULL) { + grpc_channel_args_destroy(new_args_from_connector); + } + + if (!resolver) { + GRPC_CHANNEL_INTERNAL_UNREF(&exec_ctx, channel, "subchannel_factory"); + channel = NULL; + } + grpc_exec_ctx_finish(&exec_ctx); + + return channel; +} diff --git a/src/core/lib/surface/server.c b/src/core/lib/surface/server.c new file mode 100644 index 0000000000..080734e9d5 --- /dev/null +++ b/src/core/lib/surface/server.c @@ -0,0 +1,1319 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/surface/server.h" + +#include <limits.h> +#include <stdlib.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> +#include <grpc/support/useful.h> + +#include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/channel/connected_channel.h" +#include "src/core/lib/iomgr/iomgr.h" +#include "src/core/lib/support/stack_lockfree.h" +#include "src/core/lib/support/string.h" +#include "src/core/lib/surface/api_trace.h" +#include "src/core/lib/surface/call.h" +#include "src/core/lib/surface/channel.h" +#include "src/core/lib/surface/completion_queue.h" +#include "src/core/lib/surface/init.h" +#include "src/core/lib/transport/metadata.h" +#include "src/core/lib/transport/static_metadata.h" + +typedef struct listener { + void *arg; + void (*start)(grpc_exec_ctx *exec_ctx, grpc_server *server, void *arg, + grpc_pollset **pollsets, size_t pollset_count); + void (*destroy)(grpc_exec_ctx *exec_ctx, grpc_server *server, void *arg, + grpc_closure *closure); + struct listener *next; + grpc_closure destroy_done; +} listener; + +typedef struct call_data call_data; +typedef struct channel_data channel_data; +typedef struct registered_method registered_method; + +typedef struct { + call_data *next; + call_data *prev; +} call_link; + +typedef enum { BATCH_CALL, REGISTERED_CALL } requested_call_type; + +typedef struct requested_call { + requested_call_type type; + void *tag; + grpc_server *server; + grpc_completion_queue *cq_bound_to_call; + grpc_completion_queue *cq_for_notification; + grpc_call **call; + grpc_cq_completion completion; + grpc_metadata_array *initial_metadata; + union { + struct { + grpc_call_details *details; + } batch; + struct { + registered_method *registered_method; + gpr_timespec *deadline; + grpc_byte_buffer **optional_payload; + } registered; + } data; + grpc_closure publish; +} requested_call; + +typedef struct channel_registered_method { + registered_method *server_registered_method; + grpc_mdstr *method; + grpc_mdstr *host; +} channel_registered_method; + +struct channel_data { + grpc_server *server; + grpc_connectivity_state connectivity_state; + grpc_channel *channel; + /* linked list of all channels on a server */ + channel_data *next; + channel_data *prev; + channel_registered_method *registered_methods; + uint32_t registered_method_slots; + uint32_t registered_method_max_probes; + grpc_closure finish_destroy_channel_closure; + grpc_closure channel_connectivity_changed; +}; + +typedef struct shutdown_tag { + void *tag; + grpc_completion_queue *cq; + grpc_cq_completion completion; +} shutdown_tag; + +typedef enum { + /* waiting for metadata */ + NOT_STARTED, + /* inital metadata read, not flow controlled in yet */ + PENDING, + /* flow controlled in, on completion queue */ + ACTIVATED, + /* cancelled before being queued */ + ZOMBIED +} call_state; + +typedef struct request_matcher request_matcher; + +struct call_data { + grpc_call *call; + + /** protects state */ + gpr_mu mu_state; + /** the current state of a call - see call_state */ + call_state state; + + grpc_mdstr *path; + grpc_mdstr *host; + gpr_timespec deadline; + + grpc_completion_queue *cq_new; + + grpc_metadata_batch *recv_initial_metadata; + grpc_metadata_array initial_metadata; + + grpc_closure got_initial_metadata; + grpc_closure server_on_recv_initial_metadata; + grpc_closure kill_zombie_closure; + grpc_closure *on_done_recv_initial_metadata; + + call_data *pending_next; +}; + +struct request_matcher { + call_data *pending_head; + call_data *pending_tail; + gpr_stack_lockfree *requests; +}; + +struct registered_method { + char *method; + char *host; + request_matcher request_matcher; + registered_method *next; +}; + +typedef struct { + grpc_channel **channels; + size_t num_channels; +} channel_broadcaster; + +struct grpc_server { + grpc_channel_args *channel_args; + + grpc_completion_queue **cqs; + grpc_pollset **pollsets; + size_t cq_count; + + /* The two following mutexes control access to server-state + mu_global controls access to non-call-related state (e.g., channel state) + mu_call controls access to call-related state (e.g., the call lists) + + If they are ever required to be nested, you must lock mu_global + before mu_call. This is currently used in shutdown processing + (grpc_server_shutdown_and_notify and maybe_finish_shutdown) */ + gpr_mu mu_global; /* mutex for server and channel state */ + gpr_mu mu_call; /* mutex for call-specific state */ + + registered_method *registered_methods; + request_matcher unregistered_request_matcher; + /** free list of available requested_calls indices */ + gpr_stack_lockfree *request_freelist; + /** requested call backing data */ + requested_call *requested_calls; + size_t max_requested_calls; + + gpr_atm shutdown_flag; + uint8_t shutdown_published; + size_t num_shutdown_tags; + shutdown_tag *shutdown_tags; + + channel_data root_channel_data; + + listener *listeners; + int listeners_destroyed; + gpr_refcount internal_refcount; + + /** when did we print the last shutdown progress message */ + gpr_timespec last_shutdown_message_time; +}; + +#define SERVER_FROM_CALL_ELEM(elem) \ + (((channel_data *)(elem)->channel_data)->server) + +static void begin_call(grpc_exec_ctx *exec_ctx, grpc_server *server, + call_data *calld, requested_call *rc); +static void fail_call(grpc_exec_ctx *exec_ctx, grpc_server *server, + requested_call *rc); +/* Before calling maybe_finish_shutdown, we must hold mu_global and not + hold mu_call */ +static void maybe_finish_shutdown(grpc_exec_ctx *exec_ctx, grpc_server *server); + +/* + * channel broadcaster + */ + +/* assumes server locked */ +static void channel_broadcaster_init(grpc_server *s, channel_broadcaster *cb) { + channel_data *c; + size_t count = 0; + for (c = s->root_channel_data.next; c != &s->root_channel_data; c = c->next) { + count++; + } + cb->num_channels = count; + cb->channels = gpr_malloc(sizeof(*cb->channels) * cb->num_channels); + count = 0; + for (c = s->root_channel_data.next; c != &s->root_channel_data; c = c->next) { + cb->channels[count++] = c->channel; + GRPC_CHANNEL_INTERNAL_REF(c->channel, "broadcast"); + } +} + +struct shutdown_cleanup_args { + grpc_closure closure; + gpr_slice slice; +}; + +static void shutdown_cleanup(grpc_exec_ctx *exec_ctx, void *arg, + bool iomgr_status_ignored) { + struct shutdown_cleanup_args *a = arg; + gpr_slice_unref(a->slice); + gpr_free(a); +} + +static void send_shutdown(grpc_exec_ctx *exec_ctx, grpc_channel *channel, + int send_goaway, int send_disconnect) { + grpc_transport_op op; + struct shutdown_cleanup_args *sc; + grpc_channel_element *elem; + + memset(&op, 0, sizeof(op)); + op.send_goaway = send_goaway; + sc = gpr_malloc(sizeof(*sc)); + sc->slice = gpr_slice_from_copied_string("Server shutdown"); + op.goaway_message = &sc->slice; + op.goaway_status = GRPC_STATUS_OK; + op.disconnect = send_disconnect; + grpc_closure_init(&sc->closure, shutdown_cleanup, sc); + op.on_consumed = &sc->closure; + + elem = grpc_channel_stack_element(grpc_channel_get_channel_stack(channel), 0); + elem->filter->start_transport_op(exec_ctx, elem, &op); +} + +static void channel_broadcaster_shutdown(grpc_exec_ctx *exec_ctx, + channel_broadcaster *cb, + int send_goaway, + int force_disconnect) { + size_t i; + + for (i = 0; i < cb->num_channels; i++) { + send_shutdown(exec_ctx, cb->channels[i], send_goaway, force_disconnect); + GRPC_CHANNEL_INTERNAL_UNREF(exec_ctx, cb->channels[i], "broadcast"); + } + gpr_free(cb->channels); +} + +/* + * request_matcher + */ + +static void request_matcher_init(request_matcher *rm, size_t entries) { + memset(rm, 0, sizeof(*rm)); + rm->requests = gpr_stack_lockfree_create(entries); +} + +static void request_matcher_destroy(request_matcher *rm) { + GPR_ASSERT(gpr_stack_lockfree_pop(rm->requests) == -1); + gpr_stack_lockfree_destroy(rm->requests); +} + +static void kill_zombie(grpc_exec_ctx *exec_ctx, void *elem, bool success) { + grpc_call_destroy(grpc_call_from_top_element(elem)); +} + +static void request_matcher_zombify_all_pending_calls(grpc_exec_ctx *exec_ctx, + request_matcher *rm) { + while (rm->pending_head) { + call_data *calld = rm->pending_head; + rm->pending_head = calld->pending_next; + gpr_mu_lock(&calld->mu_state); + calld->state = ZOMBIED; + gpr_mu_unlock(&calld->mu_state); + grpc_closure_init( + &calld->kill_zombie_closure, kill_zombie, + grpc_call_stack_element(grpc_call_get_call_stack(calld->call), 0)); + grpc_exec_ctx_enqueue(exec_ctx, &calld->kill_zombie_closure, true, NULL); + } +} + +static void request_matcher_kill_requests(grpc_exec_ctx *exec_ctx, + grpc_server *server, + request_matcher *rm) { + int request_id; + while ((request_id = gpr_stack_lockfree_pop(rm->requests)) != -1) { + fail_call(exec_ctx, server, &server->requested_calls[request_id]); + } +} + +/* + * server proper + */ + +static void server_ref(grpc_server *server) { + gpr_ref(&server->internal_refcount); +} + +static void server_delete(grpc_exec_ctx *exec_ctx, grpc_server *server) { + registered_method *rm; + size_t i; + grpc_channel_args_destroy(server->channel_args); + gpr_mu_destroy(&server->mu_global); + gpr_mu_destroy(&server->mu_call); + while ((rm = server->registered_methods) != NULL) { + server->registered_methods = rm->next; + request_matcher_destroy(&rm->request_matcher); + gpr_free(rm->method); + gpr_free(rm->host); + gpr_free(rm); + } + for (i = 0; i < server->cq_count; i++) { + GRPC_CQ_INTERNAL_UNREF(server->cqs[i], "server"); + } + request_matcher_destroy(&server->unregistered_request_matcher); + gpr_stack_lockfree_destroy(server->request_freelist); + gpr_free(server->cqs); + gpr_free(server->pollsets); + gpr_free(server->shutdown_tags); + gpr_free(server->requested_calls); + gpr_free(server); +} + +static void server_unref(grpc_exec_ctx *exec_ctx, grpc_server *server) { + if (gpr_unref(&server->internal_refcount)) { + server_delete(exec_ctx, server); + } +} + +static int is_channel_orphaned(channel_data *chand) { + return chand->next == chand; +} + +static void orphan_channel(channel_data *chand) { + chand->next->prev = chand->prev; + chand->prev->next = chand->next; + chand->next = chand->prev = chand; +} + +static void finish_destroy_channel(grpc_exec_ctx *exec_ctx, void *cd, + bool success) { + channel_data *chand = cd; + grpc_server *server = chand->server; + GRPC_CHANNEL_INTERNAL_UNREF(exec_ctx, chand->channel, "server"); + server_unref(exec_ctx, server); +} + +static void destroy_channel(grpc_exec_ctx *exec_ctx, channel_data *chand) { + if (is_channel_orphaned(chand)) return; + GPR_ASSERT(chand->server != NULL); + orphan_channel(chand); + server_ref(chand->server); + maybe_finish_shutdown(exec_ctx, chand->server); + chand->finish_destroy_channel_closure.cb = finish_destroy_channel; + chand->finish_destroy_channel_closure.cb_arg = chand; + + grpc_transport_op op; + memset(&op, 0, sizeof(op)); + op.set_accept_stream = true; + op.on_consumed = &chand->finish_destroy_channel_closure; + grpc_channel_next_op(exec_ctx, + grpc_channel_stack_element( + grpc_channel_get_channel_stack(chand->channel), 0), + &op); +} + +static void finish_start_new_rpc(grpc_exec_ctx *exec_ctx, grpc_server *server, + grpc_call_element *elem, request_matcher *rm) { + call_data *calld = elem->call_data; + int request_id; + + if (gpr_atm_acq_load(&server->shutdown_flag)) { + gpr_mu_lock(&calld->mu_state); + calld->state = ZOMBIED; + gpr_mu_unlock(&calld->mu_state); + grpc_closure_init(&calld->kill_zombie_closure, kill_zombie, elem); + grpc_exec_ctx_enqueue(exec_ctx, &calld->kill_zombie_closure, true, NULL); + return; + } + + request_id = gpr_stack_lockfree_pop(rm->requests); + if (request_id == -1) { + gpr_mu_lock(&server->mu_call); + gpr_mu_lock(&calld->mu_state); + calld->state = PENDING; + gpr_mu_unlock(&calld->mu_state); + if (rm->pending_head == NULL) { + rm->pending_tail = rm->pending_head = calld; + } else { + rm->pending_tail->pending_next = calld; + rm->pending_tail = calld; + } + calld->pending_next = NULL; + gpr_mu_unlock(&server->mu_call); + } else { + gpr_mu_lock(&calld->mu_state); + calld->state = ACTIVATED; + gpr_mu_unlock(&calld->mu_state); + begin_call(exec_ctx, server, calld, &server->requested_calls[request_id]); + } +} + +static void start_new_rpc(grpc_exec_ctx *exec_ctx, grpc_call_element *elem) { + channel_data *chand = elem->channel_data; + call_data *calld = elem->call_data; + grpc_server *server = chand->server; + uint32_t i; + uint32_t hash; + channel_registered_method *rm; + + if (chand->registered_methods && calld->path && calld->host) { + /* TODO(ctiller): unify these two searches */ + /* check for an exact match with host */ + hash = GRPC_MDSTR_KV_HASH(calld->host->hash, calld->path->hash); + for (i = 0; i <= chand->registered_method_max_probes; i++) { + rm = &chand->registered_methods[(hash + i) % + chand->registered_method_slots]; + if (!rm) break; + if (rm->host != calld->host) continue; + if (rm->method != calld->path) continue; + finish_start_new_rpc(exec_ctx, server, elem, + &rm->server_registered_method->request_matcher); + return; + } + /* check for a wildcard method definition (no host set) */ + hash = GRPC_MDSTR_KV_HASH(0, calld->path->hash); + for (i = 0; i <= chand->registered_method_max_probes; i++) { + rm = &chand->registered_methods[(hash + i) % + chand->registered_method_slots]; + if (!rm) break; + if (rm->host != NULL) continue; + if (rm->method != calld->path) continue; + finish_start_new_rpc(exec_ctx, server, elem, + &rm->server_registered_method->request_matcher); + return; + } + } + finish_start_new_rpc(exec_ctx, server, elem, + &server->unregistered_request_matcher); +} + +static int num_listeners(grpc_server *server) { + listener *l; + int n = 0; + for (l = server->listeners; l; l = l->next) { + n++; + } + return n; +} + +static void done_shutdown_event(grpc_exec_ctx *exec_ctx, void *server, + grpc_cq_completion *completion) { + server_unref(exec_ctx, server); +} + +static int num_channels(grpc_server *server) { + channel_data *chand; + int n = 0; + for (chand = server->root_channel_data.next; + chand != &server->root_channel_data; chand = chand->next) { + n++; + } + return n; +} + +static void kill_pending_work_locked(grpc_exec_ctx *exec_ctx, + grpc_server *server) { + registered_method *rm; + request_matcher_kill_requests(exec_ctx, server, + &server->unregistered_request_matcher); + request_matcher_zombify_all_pending_calls( + exec_ctx, &server->unregistered_request_matcher); + for (rm = server->registered_methods; rm; rm = rm->next) { + request_matcher_kill_requests(exec_ctx, server, &rm->request_matcher); + request_matcher_zombify_all_pending_calls(exec_ctx, &rm->request_matcher); + } +} + +static void maybe_finish_shutdown(grpc_exec_ctx *exec_ctx, + grpc_server *server) { + size_t i; + if (!gpr_atm_acq_load(&server->shutdown_flag) || server->shutdown_published) { + return; + } + + kill_pending_work_locked(exec_ctx, server); + + if (server->root_channel_data.next != &server->root_channel_data || + server->listeners_destroyed < num_listeners(server)) { + if (gpr_time_cmp(gpr_time_sub(gpr_now(GPR_CLOCK_REALTIME), + server->last_shutdown_message_time), + gpr_time_from_seconds(1, GPR_TIMESPAN)) >= 0) { + server->last_shutdown_message_time = gpr_now(GPR_CLOCK_REALTIME); + gpr_log(GPR_DEBUG, + "Waiting for %d channels and %d/%d listeners to be destroyed" + " before shutting down server", + num_channels(server), + num_listeners(server) - server->listeners_destroyed, + num_listeners(server)); + } + return; + } + server->shutdown_published = 1; + for (i = 0; i < server->num_shutdown_tags; i++) { + server_ref(server); + grpc_cq_end_op(exec_ctx, server->shutdown_tags[i].cq, + server->shutdown_tags[i].tag, 1, done_shutdown_event, server, + &server->shutdown_tags[i].completion); + } +} + +static grpc_mdelem *server_filter(void *user_data, grpc_mdelem *md) { + grpc_call_element *elem = user_data; + call_data *calld = elem->call_data; + if (md->key == GRPC_MDSTR_PATH) { + calld->path = GRPC_MDSTR_REF(md->value); + return NULL; + } else if (md->key == GRPC_MDSTR_AUTHORITY) { + calld->host = GRPC_MDSTR_REF(md->value); + return NULL; + } + return md; +} + +static void server_on_recv_initial_metadata(grpc_exec_ctx *exec_ctx, void *ptr, + bool success) { + grpc_call_element *elem = ptr; + call_data *calld = elem->call_data; + gpr_timespec op_deadline; + + grpc_metadata_batch_filter(calld->recv_initial_metadata, server_filter, elem); + op_deadline = calld->recv_initial_metadata->deadline; + if (0 != gpr_time_cmp(op_deadline, gpr_inf_future(op_deadline.clock_type))) { + calld->deadline = op_deadline; + } + if (calld->host && calld->path) { + /* do nothing */ + } else { + success = 0; + } + + calld->on_done_recv_initial_metadata->cb( + exec_ctx, calld->on_done_recv_initial_metadata->cb_arg, success); +} + +static void server_mutate_op(grpc_call_element *elem, + grpc_transport_stream_op *op) { + call_data *calld = elem->call_data; + + if (op->recv_initial_metadata != NULL) { + calld->recv_initial_metadata = op->recv_initial_metadata; + calld->on_done_recv_initial_metadata = op->recv_initial_metadata_ready; + op->recv_initial_metadata_ready = &calld->server_on_recv_initial_metadata; + } +} + +static void server_start_transport_stream_op(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem, + grpc_transport_stream_op *op) { + GRPC_CALL_LOG_OP(GPR_INFO, elem, op); + server_mutate_op(elem, op); + grpc_call_next_op(exec_ctx, elem, op); +} + +static void got_initial_metadata(grpc_exec_ctx *exec_ctx, void *ptr, + bool success) { + grpc_call_element *elem = ptr; + call_data *calld = elem->call_data; + if (success) { + start_new_rpc(exec_ctx, elem); + } else { + gpr_mu_lock(&calld->mu_state); + if (calld->state == NOT_STARTED) { + calld->state = ZOMBIED; + gpr_mu_unlock(&calld->mu_state); + grpc_closure_init(&calld->kill_zombie_closure, kill_zombie, elem); + grpc_exec_ctx_enqueue(exec_ctx, &calld->kill_zombie_closure, true, NULL); + } else if (calld->state == PENDING) { + calld->state = ZOMBIED; + gpr_mu_unlock(&calld->mu_state); + /* zombied call will be destroyed when it's removed from the pending + queue... later */ + } else { + gpr_mu_unlock(&calld->mu_state); + } + } +} + +static void accept_stream(grpc_exec_ctx *exec_ctx, void *cd, + grpc_transport *transport, + const void *transport_server_data) { + channel_data *chand = cd; + /* create a call */ + grpc_call *call = + grpc_call_create(chand->channel, NULL, 0, NULL, transport_server_data, + NULL, 0, gpr_inf_future(GPR_CLOCK_MONOTONIC)); + grpc_call_element *elem = + grpc_call_stack_element(grpc_call_get_call_stack(call), 0); + call_data *calld = elem->call_data; + grpc_op op; + memset(&op, 0, sizeof(op)); + op.op = GRPC_OP_RECV_INITIAL_METADATA; + op.data.recv_initial_metadata = &calld->initial_metadata; + grpc_closure_init(&calld->got_initial_metadata, got_initial_metadata, elem); + grpc_call_start_batch_and_execute(exec_ctx, call, &op, 1, + &calld->got_initial_metadata); +} + +static void channel_connectivity_changed(grpc_exec_ctx *exec_ctx, void *cd, + bool iomgr_status_ignored) { + channel_data *chand = cd; + grpc_server *server = chand->server; + if (chand->connectivity_state != GRPC_CHANNEL_FATAL_FAILURE) { + grpc_transport_op op; + memset(&op, 0, sizeof(op)); + op.on_connectivity_state_change = &chand->channel_connectivity_changed, + op.connectivity_state = &chand->connectivity_state; + grpc_channel_next_op(exec_ctx, + grpc_channel_stack_element( + grpc_channel_get_channel_stack(chand->channel), 0), + &op); + } else { + gpr_mu_lock(&server->mu_global); + destroy_channel(exec_ctx, chand); + gpr_mu_unlock(&server->mu_global); + GRPC_CHANNEL_INTERNAL_UNREF(exec_ctx, chand->channel, "connectivity"); + } +} + +static void init_call_elem(grpc_exec_ctx *exec_ctx, grpc_call_element *elem, + grpc_call_element_args *args) { + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + memset(calld, 0, sizeof(call_data)); + calld->deadline = gpr_inf_future(GPR_CLOCK_REALTIME); + calld->call = grpc_call_from_top_element(elem); + gpr_mu_init(&calld->mu_state); + + grpc_closure_init(&calld->server_on_recv_initial_metadata, + server_on_recv_initial_metadata, elem); + + server_ref(chand->server); +} + +static void destroy_call_elem(grpc_exec_ctx *exec_ctx, + grpc_call_element *elem) { + channel_data *chand = elem->channel_data; + call_data *calld = elem->call_data; + + GPR_ASSERT(calld->state != PENDING); + + if (calld->host) { + GRPC_MDSTR_UNREF(calld->host); + } + if (calld->path) { + GRPC_MDSTR_UNREF(calld->path); + } + grpc_metadata_array_destroy(&calld->initial_metadata); + + gpr_mu_destroy(&calld->mu_state); + + server_unref(exec_ctx, chand->server); +} + +static void init_channel_elem(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem, + grpc_channel_element_args *args) { + channel_data *chand = elem->channel_data; + GPR_ASSERT(args->is_first); + GPR_ASSERT(!args->is_last); + chand->server = NULL; + chand->channel = NULL; + chand->next = chand->prev = chand; + chand->registered_methods = NULL; + chand->connectivity_state = GRPC_CHANNEL_IDLE; + grpc_closure_init(&chand->channel_connectivity_changed, + channel_connectivity_changed, chand); +} + +static void destroy_channel_elem(grpc_exec_ctx *exec_ctx, + grpc_channel_element *elem) { + size_t i; + channel_data *chand = elem->channel_data; + if (chand->registered_methods) { + for (i = 0; i < chand->registered_method_slots; i++) { + if (chand->registered_methods[i].method) { + GRPC_MDSTR_UNREF(chand->registered_methods[i].method); + } + if (chand->registered_methods[i].host) { + GRPC_MDSTR_UNREF(chand->registered_methods[i].host); + } + } + gpr_free(chand->registered_methods); + } + if (chand->server) { + gpr_mu_lock(&chand->server->mu_global); + chand->next->prev = chand->prev; + chand->prev->next = chand->next; + chand->next = chand->prev = chand; + maybe_finish_shutdown(exec_ctx, chand->server); + gpr_mu_unlock(&chand->server->mu_global); + server_unref(exec_ctx, chand->server); + } +} + +const grpc_channel_filter grpc_server_top_filter = { + server_start_transport_stream_op, + grpc_channel_next_op, + sizeof(call_data), + init_call_elem, + grpc_call_stack_ignore_set_pollset, + destroy_call_elem, + sizeof(channel_data), + init_channel_elem, + destroy_channel_elem, + grpc_call_next_get_peer, + "server", +}; + +void grpc_server_register_completion_queue(grpc_server *server, + grpc_completion_queue *cq, + void *reserved) { + size_t i, n; + GRPC_API_TRACE( + "grpc_server_register_completion_queue(server=%p, cq=%p, reserved=%p)", 3, + (server, cq, reserved)); + GPR_ASSERT(!reserved); + for (i = 0; i < server->cq_count; i++) { + if (server->cqs[i] == cq) return; + } + GRPC_CQ_INTERNAL_REF(cq, "server"); + grpc_cq_mark_server_cq(cq); + n = server->cq_count++; + server->cqs = gpr_realloc(server->cqs, + server->cq_count * sizeof(grpc_completion_queue *)); + server->cqs[n] = cq; +} + +grpc_server *grpc_server_create(const grpc_channel_args *args, void *reserved) { + size_t i; + + GRPC_API_TRACE("grpc_server_create(%p, %p)", 2, (args, reserved)); + + grpc_server *server = gpr_malloc(sizeof(grpc_server)); + + GPR_ASSERT(grpc_is_initialized() && "call grpc_init()"); + + memset(server, 0, sizeof(grpc_server)); + + gpr_mu_init(&server->mu_global); + gpr_mu_init(&server->mu_call); + + /* decremented by grpc_server_destroy */ + gpr_ref_init(&server->internal_refcount, 1); + server->root_channel_data.next = server->root_channel_data.prev = + &server->root_channel_data; + + /* TODO(ctiller): expose a channel_arg for this */ + server->max_requested_calls = 32768; + server->request_freelist = + gpr_stack_lockfree_create(server->max_requested_calls); + for (i = 0; i < (size_t)server->max_requested_calls; i++) { + gpr_stack_lockfree_push(server->request_freelist, (int)i); + } + request_matcher_init(&server->unregistered_request_matcher, + server->max_requested_calls); + server->requested_calls = gpr_malloc(server->max_requested_calls * + sizeof(*server->requested_calls)); + + server->channel_args = grpc_channel_args_copy(args); + + return server; +} + +static int streq(const char *a, const char *b) { + if (a == NULL && b == NULL) return 1; + if (a == NULL) return 0; + if (b == NULL) return 0; + return 0 == strcmp(a, b); +} + +void *grpc_server_register_method(grpc_server *server, const char *method, + const char *host) { + registered_method *m; + GRPC_API_TRACE("grpc_server_register_method(server=%p, method=%s, host=%s)", + 3, (server, method, host)); + if (!method) { + gpr_log(GPR_ERROR, + "grpc_server_register_method method string cannot be NULL"); + return NULL; + } + for (m = server->registered_methods; m; m = m->next) { + if (streq(m->method, method) && streq(m->host, host)) { + gpr_log(GPR_ERROR, "duplicate registration for %s@%s", method, + host ? host : "*"); + return NULL; + } + } + m = gpr_malloc(sizeof(registered_method)); + memset(m, 0, sizeof(*m)); + request_matcher_init(&m->request_matcher, server->max_requested_calls); + m->method = gpr_strdup(method); + m->host = gpr_strdup(host); + m->next = server->registered_methods; + server->registered_methods = m; + return m; +} + +void grpc_server_start(grpc_server *server) { + listener *l; + size_t i; + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + + GRPC_API_TRACE("grpc_server_start(server=%p)", 1, (server)); + + server->pollsets = gpr_malloc(sizeof(grpc_pollset *) * server->cq_count); + for (i = 0; i < server->cq_count; i++) { + server->pollsets[i] = grpc_cq_pollset(server->cqs[i]); + } + + for (l = server->listeners; l; l = l->next) { + l->start(&exec_ctx, server, l->arg, server->pollsets, server->cq_count); + } + + grpc_exec_ctx_finish(&exec_ctx); +} + +void grpc_server_setup_transport(grpc_exec_ctx *exec_ctx, grpc_server *s, + grpc_transport *transport, + const grpc_channel_args *args) { + size_t i; + size_t num_registered_methods; + size_t alloc; + registered_method *rm; + channel_registered_method *crm; + grpc_channel *channel; + channel_data *chand; + grpc_mdstr *host; + grpc_mdstr *method; + uint32_t hash; + size_t slots; + uint32_t probes; + uint32_t max_probes = 0; + grpc_transport_op op; + + for (i = 0; i < s->cq_count; i++) { + memset(&op, 0, sizeof(op)); + op.bind_pollset = grpc_cq_pollset(s->cqs[i]); + grpc_transport_perform_op(exec_ctx, transport, &op); + } + + channel = + grpc_channel_create(exec_ctx, NULL, args, GRPC_SERVER_CHANNEL, transport); + chand = (channel_data *)grpc_channel_stack_element( + grpc_channel_get_channel_stack(channel), 0) + ->channel_data; + chand->server = s; + server_ref(s); + chand->channel = channel; + + num_registered_methods = 0; + for (rm = s->registered_methods; rm; rm = rm->next) { + num_registered_methods++; + } + /* build a lookup table phrased in terms of mdstr's in this channels context + to quickly find registered methods */ + if (num_registered_methods > 0) { + slots = 2 * num_registered_methods; + alloc = sizeof(channel_registered_method) * slots; + chand->registered_methods = gpr_malloc(alloc); + memset(chand->registered_methods, 0, alloc); + for (rm = s->registered_methods; rm; rm = rm->next) { + host = rm->host ? grpc_mdstr_from_string(rm->host) : NULL; + method = grpc_mdstr_from_string(rm->method); + hash = GRPC_MDSTR_KV_HASH(host ? host->hash : 0, method->hash); + for (probes = 0; chand->registered_methods[(hash + probes) % slots] + .server_registered_method != NULL; + probes++) + ; + if (probes > max_probes) max_probes = probes; + crm = &chand->registered_methods[(hash + probes) % slots]; + crm->server_registered_method = rm; + crm->host = host; + crm->method = method; + } + GPR_ASSERT(slots <= UINT32_MAX); + chand->registered_method_slots = (uint32_t)slots; + chand->registered_method_max_probes = max_probes; + } + + gpr_mu_lock(&s->mu_global); + chand->next = &s->root_channel_data; + chand->prev = chand->next->prev; + chand->next->prev = chand->prev->next = chand; + gpr_mu_unlock(&s->mu_global); + + GRPC_CHANNEL_INTERNAL_REF(channel, "connectivity"); + memset(&op, 0, sizeof(op)); + op.set_accept_stream = true; + op.set_accept_stream_fn = accept_stream; + op.set_accept_stream_user_data = chand; + op.on_connectivity_state_change = &chand->channel_connectivity_changed; + op.connectivity_state = &chand->connectivity_state; + op.disconnect = gpr_atm_acq_load(&s->shutdown_flag) != 0; + grpc_transport_perform_op(exec_ctx, transport, &op); +} + +void done_published_shutdown(grpc_exec_ctx *exec_ctx, void *done_arg, + grpc_cq_completion *storage) { + (void)done_arg; + gpr_free(storage); +} + +static void listener_destroy_done(grpc_exec_ctx *exec_ctx, void *s, + bool success) { + grpc_server *server = s; + gpr_mu_lock(&server->mu_global); + server->listeners_destroyed++; + maybe_finish_shutdown(exec_ctx, server); + gpr_mu_unlock(&server->mu_global); +} + +void grpc_server_shutdown_and_notify(grpc_server *server, + grpc_completion_queue *cq, void *tag) { + listener *l; + shutdown_tag *sdt; + channel_broadcaster broadcaster; + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + + GRPC_API_TRACE("grpc_server_shutdown_and_notify(server=%p, cq=%p, tag=%p)", 3, + (server, cq, tag)); + + /* lock, and gather up some stuff to do */ + gpr_mu_lock(&server->mu_global); + grpc_cq_begin_op(cq, tag); + if (server->shutdown_published) { + grpc_cq_end_op(&exec_ctx, cq, tag, 1, done_published_shutdown, NULL, + gpr_malloc(sizeof(grpc_cq_completion))); + gpr_mu_unlock(&server->mu_global); + goto done; + } + server->shutdown_tags = + gpr_realloc(server->shutdown_tags, + sizeof(shutdown_tag) * (server->num_shutdown_tags + 1)); + sdt = &server->shutdown_tags[server->num_shutdown_tags++]; + sdt->tag = tag; + sdt->cq = cq; + if (gpr_atm_acq_load(&server->shutdown_flag)) { + gpr_mu_unlock(&server->mu_global); + goto done; + } + + server->last_shutdown_message_time = gpr_now(GPR_CLOCK_REALTIME); + + channel_broadcaster_init(server, &broadcaster); + + gpr_atm_rel_store(&server->shutdown_flag, 1); + + /* collect all unregistered then registered calls */ + gpr_mu_lock(&server->mu_call); + kill_pending_work_locked(&exec_ctx, server); + gpr_mu_unlock(&server->mu_call); + + maybe_finish_shutdown(&exec_ctx, server); + gpr_mu_unlock(&server->mu_global); + + /* Shutdown listeners */ + for (l = server->listeners; l; l = l->next) { + grpc_closure_init(&l->destroy_done, listener_destroy_done, server); + l->destroy(&exec_ctx, server, l->arg, &l->destroy_done); + } + + channel_broadcaster_shutdown(&exec_ctx, &broadcaster, 1, 0); + +done: + grpc_exec_ctx_finish(&exec_ctx); +} + +void grpc_server_cancel_all_calls(grpc_server *server) { + channel_broadcaster broadcaster; + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + + GRPC_API_TRACE("grpc_server_cancel_all_calls(server=%p)", 1, (server)); + + gpr_mu_lock(&server->mu_global); + channel_broadcaster_init(server, &broadcaster); + gpr_mu_unlock(&server->mu_global); + + channel_broadcaster_shutdown(&exec_ctx, &broadcaster, 0, 1); + grpc_exec_ctx_finish(&exec_ctx); +} + +void grpc_server_destroy(grpc_server *server) { + listener *l; + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + + GRPC_API_TRACE("grpc_server_destroy(server=%p)", 1, (server)); + + gpr_mu_lock(&server->mu_global); + GPR_ASSERT(gpr_atm_acq_load(&server->shutdown_flag) || !server->listeners); + GPR_ASSERT(server->listeners_destroyed == num_listeners(server)); + + while (server->listeners) { + l = server->listeners; + server->listeners = l->next; + gpr_free(l); + } + + gpr_mu_unlock(&server->mu_global); + + server_unref(&exec_ctx, server); + grpc_exec_ctx_finish(&exec_ctx); +} + +void grpc_server_add_listener( + grpc_exec_ctx *exec_ctx, grpc_server *server, void *arg, + void (*start)(grpc_exec_ctx *exec_ctx, grpc_server *server, void *arg, + grpc_pollset **pollsets, size_t pollset_count), + void (*destroy)(grpc_exec_ctx *exec_ctx, grpc_server *server, void *arg, + grpc_closure *on_done)) { + listener *l = gpr_malloc(sizeof(listener)); + l->arg = arg; + l->start = start; + l->destroy = destroy; + l->next = server->listeners; + server->listeners = l; +} + +static grpc_call_error queue_call_request(grpc_exec_ctx *exec_ctx, + grpc_server *server, + requested_call *rc) { + call_data *calld = NULL; + request_matcher *rm = NULL; + int request_id; + if (gpr_atm_acq_load(&server->shutdown_flag)) { + fail_call(exec_ctx, server, rc); + return GRPC_CALL_OK; + } + request_id = gpr_stack_lockfree_pop(server->request_freelist); + if (request_id == -1) { + /* out of request ids: just fail this one */ + fail_call(exec_ctx, server, rc); + return GRPC_CALL_OK; + } + switch (rc->type) { + case BATCH_CALL: + rm = &server->unregistered_request_matcher; + break; + case REGISTERED_CALL: + rm = &rc->data.registered.registered_method->request_matcher; + break; + } + server->requested_calls[request_id] = *rc; + gpr_free(rc); + if (gpr_stack_lockfree_push(rm->requests, request_id)) { + /* this was the first queued request: we need to lock and start + matching calls */ + gpr_mu_lock(&server->mu_call); + while ((calld = rm->pending_head) != NULL) { + request_id = gpr_stack_lockfree_pop(rm->requests); + if (request_id == -1) break; + rm->pending_head = calld->pending_next; + gpr_mu_unlock(&server->mu_call); + gpr_mu_lock(&calld->mu_state); + if (calld->state == ZOMBIED) { + gpr_mu_unlock(&calld->mu_state); + grpc_closure_init( + &calld->kill_zombie_closure, kill_zombie, + grpc_call_stack_element(grpc_call_get_call_stack(calld->call), 0)); + grpc_exec_ctx_enqueue(exec_ctx, &calld->kill_zombie_closure, true, + NULL); + } else { + GPR_ASSERT(calld->state == PENDING); + calld->state = ACTIVATED; + gpr_mu_unlock(&calld->mu_state); + begin_call(exec_ctx, server, calld, + &server->requested_calls[request_id]); + } + gpr_mu_lock(&server->mu_call); + } + gpr_mu_unlock(&server->mu_call); + } + return GRPC_CALL_OK; +} + +grpc_call_error grpc_server_request_call( + grpc_server *server, grpc_call **call, grpc_call_details *details, + grpc_metadata_array *initial_metadata, + grpc_completion_queue *cq_bound_to_call, + grpc_completion_queue *cq_for_notification, void *tag) { + grpc_call_error error; + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + requested_call *rc = gpr_malloc(sizeof(*rc)); + GRPC_API_TRACE( + "grpc_server_request_call(" + "server=%p, call=%p, details=%p, initial_metadata=%p, " + "cq_bound_to_call=%p, cq_for_notification=%p, tag=%p)", + 7, (server, call, details, initial_metadata, cq_bound_to_call, + cq_for_notification, tag)); + if (!grpc_cq_is_server_cq(cq_for_notification)) { + gpr_free(rc); + error = GRPC_CALL_ERROR_NOT_SERVER_COMPLETION_QUEUE; + goto done; + } + grpc_cq_begin_op(cq_for_notification, tag); + details->reserved = NULL; + rc->type = BATCH_CALL; + rc->server = server; + rc->tag = tag; + rc->cq_bound_to_call = cq_bound_to_call; + rc->cq_for_notification = cq_for_notification; + rc->call = call; + rc->data.batch.details = details; + rc->initial_metadata = initial_metadata; + error = queue_call_request(&exec_ctx, server, rc); +done: + grpc_exec_ctx_finish(&exec_ctx); + return error; +} + +grpc_call_error grpc_server_request_registered_call( + grpc_server *server, void *rmp, grpc_call **call, gpr_timespec *deadline, + grpc_metadata_array *initial_metadata, grpc_byte_buffer **optional_payload, + grpc_completion_queue *cq_bound_to_call, + grpc_completion_queue *cq_for_notification, void *tag) { + grpc_call_error error; + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + requested_call *rc = gpr_malloc(sizeof(*rc)); + registered_method *rm = rmp; + GRPC_API_TRACE( + "grpc_server_request_registered_call(" + "server=%p, rmp=%p, call=%p, deadline=%p, initial_metadata=%p, " + "optional_payload=%p, cq_bound_to_call=%p, cq_for_notification=%p, " + "tag=%p)", + 9, (server, rmp, call, deadline, initial_metadata, optional_payload, + cq_bound_to_call, cq_for_notification, tag)); + if (!grpc_cq_is_server_cq(cq_for_notification)) { + gpr_free(rc); + error = GRPC_CALL_ERROR_NOT_SERVER_COMPLETION_QUEUE; + goto done; + } + grpc_cq_begin_op(cq_for_notification, tag); + rc->type = REGISTERED_CALL; + rc->server = server; + rc->tag = tag; + rc->cq_bound_to_call = cq_bound_to_call; + rc->cq_for_notification = cq_for_notification; + rc->call = call; + rc->data.registered.registered_method = rm; + rc->data.registered.deadline = deadline; + rc->initial_metadata = initial_metadata; + rc->data.registered.optional_payload = optional_payload; + error = queue_call_request(&exec_ctx, server, rc); +done: + grpc_exec_ctx_finish(&exec_ctx); + return error; +} + +static void publish_registered_or_batch(grpc_exec_ctx *exec_ctx, + void *user_data, bool success); + +static void cpstr(char **dest, size_t *capacity, grpc_mdstr *value) { + gpr_slice slice = value->slice; + size_t len = GPR_SLICE_LENGTH(slice); + + if (len + 1 > *capacity) { + *capacity = GPR_MAX(len + 1, *capacity * 2); + *dest = gpr_realloc(*dest, *capacity); + } + memcpy(*dest, grpc_mdstr_as_c_string(value), len + 1); +} + +static void begin_call(grpc_exec_ctx *exec_ctx, grpc_server *server, + call_data *calld, requested_call *rc) { + grpc_op ops[1]; + grpc_op *op = ops; + + memset(ops, 0, sizeof(ops)); + + /* called once initial metadata has been read by the call, but BEFORE + the ioreq to fetch it out of the call has been executed. + This means metadata related fields can be relied on in calld, but to + fill in the metadata array passed by the client, we need to perform + an ioreq op, that should complete immediately. */ + + grpc_call_set_completion_queue(exec_ctx, calld->call, rc->cq_bound_to_call); + grpc_closure_init(&rc->publish, publish_registered_or_batch, rc); + *rc->call = calld->call; + calld->cq_new = rc->cq_for_notification; + GPR_SWAP(grpc_metadata_array, *rc->initial_metadata, calld->initial_metadata); + switch (rc->type) { + case BATCH_CALL: + GPR_ASSERT(calld->host != NULL); + GPR_ASSERT(calld->path != NULL); + cpstr(&rc->data.batch.details->host, + &rc->data.batch.details->host_capacity, calld->host); + cpstr(&rc->data.batch.details->method, + &rc->data.batch.details->method_capacity, calld->path); + rc->data.batch.details->deadline = calld->deadline; + break; + case REGISTERED_CALL: + *rc->data.registered.deadline = calld->deadline; + if (rc->data.registered.optional_payload) { + op->op = GRPC_OP_RECV_MESSAGE; + op->data.recv_message = rc->data.registered.optional_payload; + op++; + } + break; + default: + GPR_UNREACHABLE_CODE(return ); + } + + GRPC_CALL_INTERNAL_REF(calld->call, "server"); + grpc_call_start_batch_and_execute(exec_ctx, calld->call, ops, + (size_t)(op - ops), &rc->publish); +} + +static void done_request_event(grpc_exec_ctx *exec_ctx, void *req, + grpc_cq_completion *c) { + requested_call *rc = req; + grpc_server *server = rc->server; + + if (rc >= server->requested_calls && + rc < server->requested_calls + server->max_requested_calls) { + GPR_ASSERT(rc - server->requested_calls <= INT_MAX); + gpr_stack_lockfree_push(server->request_freelist, + (int)(rc - server->requested_calls)); + } else { + gpr_free(req); + } + + server_unref(exec_ctx, server); +} + +static void fail_call(grpc_exec_ctx *exec_ctx, grpc_server *server, + requested_call *rc) { + *rc->call = NULL; + rc->initial_metadata->count = 0; + + server_ref(server); + grpc_cq_end_op(exec_ctx, rc->cq_for_notification, rc->tag, 0, + done_request_event, rc, &rc->completion); +} + +static void publish_registered_or_batch(grpc_exec_ctx *exec_ctx, void *prc, + bool success) { + requested_call *rc = prc; + grpc_call *call = *rc->call; + grpc_call_element *elem = + grpc_call_stack_element(grpc_call_get_call_stack(call), 0); + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + server_ref(chand->server); + grpc_cq_end_op(exec_ctx, calld->cq_new, rc->tag, success, done_request_event, + rc, &rc->completion); + GRPC_CALL_INTERNAL_UNREF(exec_ctx, call, "server"); +} + +const grpc_channel_args *grpc_server_get_channel_args(grpc_server *server) { + return server->channel_args; +} + +int grpc_server_has_open_connections(grpc_server *server) { + int r; + gpr_mu_lock(&server->mu_global); + r = server->root_channel_data.next != &server->root_channel_data; + gpr_mu_unlock(&server->mu_global); + return r; +} diff --git a/src/core/lib/surface/server.h b/src/core/lib/surface/server.h new file mode 100644 index 0000000000..3845eb2981 --- /dev/null +++ b/src/core/lib/surface/server.h @@ -0,0 +1,62 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SURFACE_SERVER_H +#define GRPC_CORE_LIB_SURFACE_SERVER_H + +#include <grpc/grpc.h> +#include "src/core/lib/channel/channel_stack.h" +#include "src/core/lib/transport/transport.h" + +extern const grpc_channel_filter grpc_server_top_filter; + +/* Add a listener to the server: when the server starts, it will call start, + and when it shuts down, it will call destroy */ +void grpc_server_add_listener( + grpc_exec_ctx *exec_ctx, grpc_server *server, void *listener, + void (*start)(grpc_exec_ctx *exec_ctx, grpc_server *server, void *arg, + grpc_pollset **pollsets, size_t npollsets), + void (*destroy)(grpc_exec_ctx *exec_ctx, grpc_server *server, void *arg, + grpc_closure *on_done)); + +/* Setup a transport - creates a channel stack, binds the transport to the + server */ +void grpc_server_setup_transport(grpc_exec_ctx *exec_ctx, grpc_server *server, + grpc_transport *transport, + const grpc_channel_args *args); + +const grpc_channel_args *grpc_server_get_channel_args(grpc_server *server); + +int grpc_server_has_open_connections(grpc_server *server); + +#endif /* GRPC_CORE_LIB_SURFACE_SERVER_H */ diff --git a/src/core/lib/surface/server_chttp2.c b/src/core/lib/surface/server_chttp2.c new file mode 100644 index 0000000000..f0c2ee5153 --- /dev/null +++ b/src/core/lib/surface/server_chttp2.c @@ -0,0 +1,146 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <grpc/grpc.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/useful.h> +#include "src/core/lib/channel/http_server_filter.h" +#include "src/core/lib/iomgr/resolve_address.h" +#include "src/core/lib/iomgr/tcp_server.h" +#include "src/core/lib/surface/api_trace.h" +#include "src/core/lib/surface/server.h" +#include "src/core/lib/transport/chttp2_transport.h" + +static void setup_transport(grpc_exec_ctx *exec_ctx, void *server, + grpc_transport *transport) { + grpc_server_setup_transport(exec_ctx, server, transport, + grpc_server_get_channel_args(server)); +} + +static void new_transport(grpc_exec_ctx *exec_ctx, void *server, + grpc_endpoint *tcp, + grpc_tcp_server_acceptor *acceptor) { + /* + * Beware that the call to grpc_create_chttp2_transport() has to happen before + * grpc_tcp_server_destroy(). This is fine here, but similar code + * asynchronously doing a handshake instead of calling grpc_tcp_server_start() + * (as in server_secure_chttp2.c) needs to add synchronization to avoid this + * case. + */ + grpc_transport *transport = grpc_create_chttp2_transport( + exec_ctx, grpc_server_get_channel_args(server), tcp, 0); + setup_transport(exec_ctx, server, transport); + grpc_chttp2_transport_start_reading(exec_ctx, transport, NULL, 0); +} + +/* Server callback: start listening on our ports */ +static void start(grpc_exec_ctx *exec_ctx, grpc_server *server, void *tcpp, + grpc_pollset **pollsets, size_t pollset_count) { + grpc_tcp_server *tcp = tcpp; + grpc_tcp_server_start(exec_ctx, tcp, pollsets, pollset_count, new_transport, + server); +} + +/* Server callback: destroy the tcp listener (so we don't generate further + callbacks) */ +static void destroy(grpc_exec_ctx *exec_ctx, grpc_server *server, void *tcpp, + grpc_closure *destroy_done) { + grpc_tcp_server *tcp = tcpp; + grpc_tcp_server_unref(exec_ctx, tcp); + grpc_exec_ctx_enqueue(exec_ctx, destroy_done, true, NULL); +} + +int grpc_server_add_insecure_http2_port(grpc_server *server, const char *addr) { + grpc_resolved_addresses *resolved = NULL; + grpc_tcp_server *tcp = NULL; + size_t i; + unsigned count = 0; + int port_num = -1; + int port_temp; + grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT; + + GRPC_API_TRACE("grpc_server_add_insecure_http2_port(server=%p, addr=%s)", 2, + (server, addr)); + + resolved = grpc_blocking_resolve_address(addr, "http"); + if (!resolved) { + goto error; + } + + tcp = grpc_tcp_server_create(NULL); + GPR_ASSERT(tcp); + + for (i = 0; i < resolved->naddrs; i++) { + port_temp = grpc_tcp_server_add_port( + tcp, (struct sockaddr *)&resolved->addrs[i].addr, + resolved->addrs[i].len); + if (port_temp > 0) { + if (port_num == -1) { + port_num = port_temp; + } else { + GPR_ASSERT(port_num == port_temp); + } + count++; + } + } + if (count == 0) { + gpr_log(GPR_ERROR, "No address added out of total %d resolved", + resolved->naddrs); + goto error; + } + if (count != resolved->naddrs) { + gpr_log(GPR_ERROR, "Only %d addresses added out of total %d resolved", + count, resolved->naddrs); + } + grpc_resolved_addresses_destroy(resolved); + + /* Register with the server only upon success */ + grpc_server_add_listener(&exec_ctx, server, tcp, start, destroy); + goto done; + +/* Error path: cleanup and return */ +error: + if (resolved) { + grpc_resolved_addresses_destroy(resolved); + } + if (tcp) { + grpc_tcp_server_unref(&exec_ctx, tcp); + } + port_num = 0; + +done: + grpc_exec_ctx_finish(&exec_ctx); + return port_num; +} diff --git a/src/core/lib/surface/surface_trace.h b/src/core/lib/surface/surface_trace.h new file mode 100644 index 0000000000..6b3f673924 --- /dev/null +++ b/src/core/lib/surface/surface_trace.h @@ -0,0 +1,48 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_SURFACE_SURFACE_TRACE_H +#define GRPC_CORE_LIB_SURFACE_SURFACE_TRACE_H + +#include <grpc/support/log.h> +#include "src/core/lib/debug/trace.h" +#include "src/core/lib/surface/api_trace.h" + +#define GRPC_SURFACE_TRACE_RETURNED_EVENT(cq, event) \ + if (grpc_api_trace) { \ + char *_ev = grpc_event_string(event); \ + gpr_log(GPR_INFO, "RETURN_EVENT[%p]: %s", cq, _ev); \ + gpr_free(_ev); \ + } + +#endif /* GRPC_CORE_LIB_SURFACE_SURFACE_TRACE_H */ diff --git a/src/core/lib/surface/validate_metadata.c b/src/core/lib/surface/validate_metadata.c new file mode 100644 index 0000000000..bf4126867f --- /dev/null +++ b/src/core/lib/surface/validate_metadata.c @@ -0,0 +1,73 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <stdlib.h> +#include <string.h> + +#include <grpc/support/port_platform.h> + +static int conforms_to(const char *s, size_t len, const uint8_t *legal_bits) { + const char *p = s; + const char *e = s + len; + for (; p != e; p++) { + int idx = *p; + int byte = idx / 8; + int bit = idx % 8; + if ((legal_bits[byte] & (1 << bit)) == 0) return 0; + } + return 1; +} + +int grpc_header_key_is_legal(const char *key, size_t length) { + static const uint8_t legal_header_bits[256 / 8] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xff, 0x03, 0x00, 0x00, 0x00, + 0x80, 0xfe, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + if (length == 0) { + return 0; + } + return conforms_to(key, length, legal_header_bits); +} + +int grpc_header_nonbin_value_is_legal(const char *value, size_t length) { + static const uint8_t legal_header_bits[256 / 8] = { + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + return conforms_to(value, length, legal_header_bits); +} + +int grpc_is_binary_header(const char *key, size_t length) { + if (length < 5) return 0; + return 0 == memcmp(key + length - 4, "-bin", 4); +} diff --git a/src/core/lib/surface/version.c b/src/core/lib/surface/version.c new file mode 100644 index 0000000000..7723f39401 --- /dev/null +++ b/src/core/lib/surface/version.c @@ -0,0 +1,39 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* This file is autogenerated from: + templates/src/core/surface/version.c.template */ + +#include <grpc/grpc.h> + +const char *grpc_version_string(void) { return "0.14.0-dev"; } diff --git a/src/core/lib/transport/byte_stream.c b/src/core/lib/transport/byte_stream.c new file mode 100644 index 0000000000..79981aa154 --- /dev/null +++ b/src/core/lib/transport/byte_stream.c @@ -0,0 +1,78 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/byte_stream.h" + +#include <stdlib.h> + +#include <grpc/support/log.h> + +int grpc_byte_stream_next(grpc_exec_ctx *exec_ctx, + grpc_byte_stream *byte_stream, gpr_slice *slice, + size_t max_size_hint, grpc_closure *on_complete) { + return byte_stream->next(exec_ctx, byte_stream, slice, max_size_hint, + on_complete); +} + +void grpc_byte_stream_destroy(grpc_exec_ctx *exec_ctx, + grpc_byte_stream *byte_stream) { + byte_stream->destroy(exec_ctx, byte_stream); +} + +/* slice_buffer_stream */ + +static int slice_buffer_stream_next(grpc_exec_ctx *exec_ctx, + grpc_byte_stream *byte_stream, + gpr_slice *slice, size_t max_size_hint, + grpc_closure *on_complete) { + grpc_slice_buffer_stream *stream = (grpc_slice_buffer_stream *)byte_stream; + GPR_ASSERT(stream->cursor < stream->backing_buffer->count); + *slice = gpr_slice_ref(stream->backing_buffer->slices[stream->cursor]); + stream->cursor++; + return 1; +} + +static void slice_buffer_stream_destroy(grpc_exec_ctx *exec_ctx, + grpc_byte_stream *byte_stream) {} + +void grpc_slice_buffer_stream_init(grpc_slice_buffer_stream *stream, + gpr_slice_buffer *slice_buffer, + uint32_t flags) { + GPR_ASSERT(slice_buffer->length <= UINT32_MAX); + stream->base.length = (uint32_t)slice_buffer->length; + stream->base.flags = flags; + stream->base.next = slice_buffer_stream_next; + stream->base.destroy = slice_buffer_stream_destroy; + stream->backing_buffer = slice_buffer; + stream->cursor = 0; +} diff --git a/src/core/lib/transport/byte_stream.h b/src/core/lib/transport/byte_stream.h new file mode 100644 index 0000000000..e7346dafc3 --- /dev/null +++ b/src/core/lib/transport/byte_stream.h @@ -0,0 +1,89 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_BYTE_STREAM_H +#define GRPC_CORE_LIB_TRANSPORT_BYTE_STREAM_H + +#include <grpc/support/slice_buffer.h> +#include "src/core/lib/iomgr/exec_ctx.h" + +/** Internal bit flag for grpc_begin_message's \a flags signaling the use of + * compression for the message */ +#define GRPC_WRITE_INTERNAL_COMPRESS (0x80000000u) +/** Mask of all valid internal flags. */ +#define GRPC_WRITE_INTERNAL_USED_MASK (GRPC_WRITE_INTERNAL_COMPRESS) + +struct grpc_byte_stream; +typedef struct grpc_byte_stream grpc_byte_stream; + +struct grpc_byte_stream { + uint32_t length; + uint32_t flags; + int (*next)(grpc_exec_ctx *exec_ctx, grpc_byte_stream *byte_stream, + gpr_slice *slice, size_t max_size_hint, + grpc_closure *on_complete); + void (*destroy)(grpc_exec_ctx *exec_ctx, grpc_byte_stream *byte_stream); +}; + +/* returns 1 if the bytes are available immediately (in which case + * on_complete will not be called), 0 if the bytes will be available + * asynchronously. + * + * on entry, *remaining can be set as a hint as to the maximum number + * of bytes that would be acceptable to read. + * + * fills *buffer, *length, *remaining with the bytes, length of bytes + * and length of data remaining to be read before either returning 1 + * or calling on_complete. + * + * once a slice is returned into *slice, it is owned by the caller. + */ +int grpc_byte_stream_next(grpc_exec_ctx *exec_ctx, + grpc_byte_stream *byte_stream, gpr_slice *slice, + size_t max_size_hint, grpc_closure *on_complete); + +void grpc_byte_stream_destroy(grpc_exec_ctx *exec_ctx, + grpc_byte_stream *byte_stream); + +/* grpc_byte_stream that wraps a slice buffer */ +typedef struct grpc_slice_buffer_stream { + grpc_byte_stream base; + gpr_slice_buffer *backing_buffer; + size_t cursor; +} grpc_slice_buffer_stream; + +void grpc_slice_buffer_stream_init(grpc_slice_buffer_stream *stream, + gpr_slice_buffer *slice_buffer, + uint32_t flags); + +#endif /* GRPC_CORE_LIB_TRANSPORT_BYTE_STREAM_H */ diff --git a/src/core/lib/transport/chttp2/alpn.c b/src/core/lib/transport/chttp2/alpn.c new file mode 100644 index 0000000000..befe319180 --- /dev/null +++ b/src/core/lib/transport/chttp2/alpn.c @@ -0,0 +1,56 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/chttp2/alpn.h" +#include <grpc/support/log.h> +#include <grpc/support/useful.h> + +/* in order of preference */ +static const char *const supported_versions[] = {"h2"}; + +int grpc_chttp2_is_alpn_version_supported(const char *version, size_t size) { + size_t i; + for (i = 0; i < GPR_ARRAY_SIZE(supported_versions); i++) { + if (!strncmp(version, supported_versions[i], size)) return 1; + } + return 0; +} + +size_t grpc_chttp2_num_alpn_versions(void) { + return GPR_ARRAY_SIZE(supported_versions); +} + +const char *grpc_chttp2_get_alpn_version_index(size_t i) { + GPR_ASSERT(i < GPR_ARRAY_SIZE(supported_versions)); + return supported_versions[i]; +} diff --git a/src/core/lib/transport/chttp2/alpn.h b/src/core/lib/transport/chttp2/alpn.h new file mode 100644 index 0000000000..a9184e63a4 --- /dev/null +++ b/src/core/lib/transport/chttp2/alpn.h @@ -0,0 +1,49 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CHTTP2_ALPN_H +#define GRPC_CORE_LIB_TRANSPORT_CHTTP2_ALPN_H + +#include <string.h> + +/* Retuns 1 if the version is supported, 0 otherwise. */ +int grpc_chttp2_is_alpn_version_supported(const char *version, size_t size); + +/* Returns the number of protocol versions to advertise */ +size_t grpc_chttp2_num_alpn_versions(void); + +/* Returns the protocol version at index i (0 <= i < + * grpc_chttp2_num_alpn_versions()) */ +const char *grpc_chttp2_get_alpn_version_index(size_t i); + +#endif /* GRPC_CORE_LIB_TRANSPORT_CHTTP2_ALPN_H */ diff --git a/src/core/lib/transport/chttp2/bin_encoder.c b/src/core/lib/transport/chttp2/bin_encoder.c new file mode 100644 index 0000000000..79d0aa3d6f --- /dev/null +++ b/src/core/lib/transport/chttp2/bin_encoder.c @@ -0,0 +1,233 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/chttp2/bin_encoder.h" + +#include <string.h> + +#include <grpc/support/log.h> +#include "src/core/lib/transport/chttp2/huffsyms.h" + +static const char alphabet[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +typedef struct { + uint16_t bits; + uint8_t length; +} b64_huff_sym; + +static const b64_huff_sym huff_alphabet[64] = { + {0x21, 6}, {0x5d, 7}, {0x5e, 7}, {0x5f, 7}, {0x60, 7}, {0x61, 7}, + {0x62, 7}, {0x63, 7}, {0x64, 7}, {0x65, 7}, {0x66, 7}, {0x67, 7}, + {0x68, 7}, {0x69, 7}, {0x6a, 7}, {0x6b, 7}, {0x6c, 7}, {0x6d, 7}, + {0x6e, 7}, {0x6f, 7}, {0x70, 7}, {0x71, 7}, {0x72, 7}, {0xfc, 8}, + {0x73, 7}, {0xfd, 8}, {0x3, 5}, {0x23, 6}, {0x4, 5}, {0x24, 6}, + {0x5, 5}, {0x25, 6}, {0x26, 6}, {0x27, 6}, {0x6, 5}, {0x74, 7}, + {0x75, 7}, {0x28, 6}, {0x29, 6}, {0x2a, 6}, {0x7, 5}, {0x2b, 6}, + {0x76, 7}, {0x2c, 6}, {0x8, 5}, {0x9, 5}, {0x2d, 6}, {0x77, 7}, + {0x78, 7}, {0x79, 7}, {0x7a, 7}, {0x7b, 7}, {0x0, 5}, {0x1, 5}, + {0x2, 5}, {0x19, 6}, {0x1a, 6}, {0x1b, 6}, {0x1c, 6}, {0x1d, 6}, + {0x1e, 6}, {0x1f, 6}, {0x7fb, 11}, {0x18, 6}}; + +static const uint8_t tail_xtra[3] = {0, 2, 3}; + +gpr_slice grpc_chttp2_base64_encode(gpr_slice input) { + size_t input_length = GPR_SLICE_LENGTH(input); + size_t input_triplets = input_length / 3; + size_t tail_case = input_length % 3; + size_t output_length = input_triplets * 4 + tail_xtra[tail_case]; + gpr_slice output = gpr_slice_malloc(output_length); + uint8_t *in = GPR_SLICE_START_PTR(input); + char *out = (char *)GPR_SLICE_START_PTR(output); + size_t i; + + /* encode full triplets */ + for (i = 0; i < input_triplets; i++) { + out[0] = alphabet[in[0] >> 2]; + out[1] = alphabet[((in[0] & 0x3) << 4) | (in[1] >> 4)]; + out[2] = alphabet[((in[1] & 0xf) << 2) | (in[2] >> 6)]; + out[3] = alphabet[in[2] & 0x3f]; + out += 4; + in += 3; + } + + /* encode the remaining bytes */ + switch (tail_case) { + case 0: + break; + case 1: + out[0] = alphabet[in[0] >> 2]; + out[1] = alphabet[(in[0] & 0x3) << 4]; + out += 2; + in += 1; + break; + case 2: + out[0] = alphabet[in[0] >> 2]; + out[1] = alphabet[((in[0] & 0x3) << 4) | (in[1] >> 4)]; + out[2] = alphabet[(in[1] & 0xf) << 2]; + out += 3; + in += 2; + break; + } + + GPR_ASSERT(out == (char *)GPR_SLICE_END_PTR(output)); + GPR_ASSERT(in == GPR_SLICE_END_PTR(input)); + return output; +} + +gpr_slice grpc_chttp2_huffman_compress(gpr_slice input) { + size_t nbits; + uint8_t *in; + uint8_t *out; + gpr_slice output; + uint32_t temp = 0; + uint32_t temp_length = 0; + + nbits = 0; + for (in = GPR_SLICE_START_PTR(input); in != GPR_SLICE_END_PTR(input); ++in) { + nbits += grpc_chttp2_huffsyms[*in].length; + } + + output = gpr_slice_malloc(nbits / 8 + (nbits % 8 != 0)); + out = GPR_SLICE_START_PTR(output); + for (in = GPR_SLICE_START_PTR(input); in != GPR_SLICE_END_PTR(input); ++in) { + int sym = *in; + temp <<= grpc_chttp2_huffsyms[sym].length; + temp |= grpc_chttp2_huffsyms[sym].bits; + temp_length += grpc_chttp2_huffsyms[sym].length; + + while (temp_length > 8) { + temp_length -= 8; + *out++ = (uint8_t)(temp >> temp_length); + } + } + + if (temp_length) { + /* NB: the following integer arithmetic operation needs to be in its + * expanded form due to the "integral promotion" performed (see section + * 3.2.1.1 of the C89 draft standard). A cast to the smaller container type + * is then required to avoid the compiler warning */ + *out++ = (uint8_t)((uint8_t)(temp << (8u - temp_length)) | + (uint8_t)(0xffu >> temp_length)); + } + + GPR_ASSERT(out == GPR_SLICE_END_PTR(output)); + + return output; +} + +typedef struct { + uint32_t temp; + uint32_t temp_length; + uint8_t *out; +} huff_out; + +static void enc_flush_some(huff_out *out) { + while (out->temp_length > 8) { + out->temp_length -= 8; + *out->out++ = (uint8_t)(out->temp >> out->temp_length); + } +} + +static void enc_add2(huff_out *out, uint8_t a, uint8_t b) { + b64_huff_sym sa = huff_alphabet[a]; + b64_huff_sym sb = huff_alphabet[b]; + out->temp = (out->temp << (sa.length + sb.length)) | + ((uint32_t)sa.bits << sb.length) | sb.bits; + out->temp_length += (uint32_t)sa.length + (uint32_t)sb.length; + enc_flush_some(out); +} + +static void enc_add1(huff_out *out, uint8_t a) { + b64_huff_sym sa = huff_alphabet[a]; + out->temp = (out->temp << sa.length) | sa.bits; + out->temp_length += sa.length; + enc_flush_some(out); +} + +gpr_slice grpc_chttp2_base64_encode_and_huffman_compress(gpr_slice input) { + size_t input_length = GPR_SLICE_LENGTH(input); + size_t input_triplets = input_length / 3; + size_t tail_case = input_length % 3; + size_t output_syms = input_triplets * 4 + tail_xtra[tail_case]; + size_t max_output_bits = 11 * output_syms; + size_t max_output_length = max_output_bits / 8 + (max_output_bits % 8 != 0); + gpr_slice output = gpr_slice_malloc(max_output_length); + uint8_t *in = GPR_SLICE_START_PTR(input); + uint8_t *start_out = GPR_SLICE_START_PTR(output); + huff_out out; + size_t i; + + out.temp = 0; + out.temp_length = 0; + out.out = start_out; + + /* encode full triplets */ + for (i = 0; i < input_triplets; i++) { + enc_add2(&out, in[0] >> 2, (uint8_t)((in[0] & 0x3) << 4) | (in[1] >> 4)); + enc_add2(&out, (uint8_t)((in[1] & 0xf) << 2) | (in[2] >> 6), + (uint8_t)(in[2] & 0x3f)); + in += 3; + } + + /* encode the remaining bytes */ + switch (tail_case) { + case 0: + break; + case 1: + enc_add2(&out, in[0] >> 2, (uint8_t)((in[0] & 0x3) << 4)); + in += 1; + break; + case 2: + enc_add2(&out, in[0] >> 2, + (uint8_t)((in[0] & 0x3) << 4) | (uint8_t)(in[1] >> 4)); + enc_add1(&out, (uint8_t)((in[1] & 0xf) << 2)); + in += 2; + break; + } + + if (out.temp_length) { + /* NB: the following integer arithmetic operation needs to be in its + * expanded form due to the "integral promotion" performed (see section + * 3.2.1.1 of the C89 draft standard). A cast to the smaller container type + * is then required to avoid the compiler warning */ + *out.out++ = (uint8_t)((uint8_t)(out.temp << (8u - out.temp_length)) | + (uint8_t)(0xffu >> out.temp_length)); + } + + GPR_ASSERT(out.out <= GPR_SLICE_END_PTR(output)); + GPR_SLICE_SET_LENGTH(output, out.out - start_out); + + GPR_ASSERT(in == GPR_SLICE_END_PTR(input)); + return output; +} diff --git a/src/core/lib/transport/chttp2/bin_encoder.h b/src/core/lib/transport/chttp2/bin_encoder.h new file mode 100644 index 0000000000..1c5cd1e1c6 --- /dev/null +++ b/src/core/lib/transport/chttp2/bin_encoder.h @@ -0,0 +1,54 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CHTTP2_BIN_ENCODER_H +#define GRPC_CORE_LIB_TRANSPORT_CHTTP2_BIN_ENCODER_H + +#include <grpc/support/slice.h> + +/* base64 encode a slice. Returns a new slice, does not take ownership of the + input */ +gpr_slice grpc_chttp2_base64_encode(gpr_slice input); + +/* Compress a slice with the static huffman encoder detailed in the hpack + standard. Returns a new slice, does not take ownership of the input */ +gpr_slice grpc_chttp2_huffman_compress(gpr_slice input); + +/* equivalent to: + gpr_slice x = grpc_chttp2_base64_encode(input); + gpr_slice y = grpc_chttp2_huffman_compress(x); + gpr_slice_unref(x); + return y; */ +gpr_slice grpc_chttp2_base64_encode_and_huffman_compress(gpr_slice input); + +#endif /* GRPC_CORE_LIB_TRANSPORT_CHTTP2_BIN_ENCODER_H */ diff --git a/src/core/lib/transport/chttp2/frame.h b/src/core/lib/transport/chttp2/frame.h new file mode 100644 index 0000000000..4674bc9703 --- /dev/null +++ b/src/core/lib/transport/chttp2/frame.h @@ -0,0 +1,69 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CHTTP2_FRAME_H +#define GRPC_CORE_LIB_TRANSPORT_CHTTP2_FRAME_H + +#include <grpc/support/port_platform.h> +#include <grpc/support/slice.h> + +/* Common definitions for frame handling in the chttp2 transport */ + +typedef enum { + GRPC_CHTTP2_PARSE_OK, + GRPC_CHTTP2_STREAM_ERROR, + GRPC_CHTTP2_CONNECTION_ERROR +} grpc_chttp2_parse_error; + +/* defined in internal.h */ +typedef struct grpc_chttp2_stream_parsing grpc_chttp2_stream_parsing; +typedef struct grpc_chttp2_transport_parsing grpc_chttp2_transport_parsing; + +#define GRPC_CHTTP2_FRAME_DATA 0 +#define GRPC_CHTTP2_FRAME_HEADER 1 +#define GRPC_CHTTP2_FRAME_CONTINUATION 9 +#define GRPC_CHTTP2_FRAME_RST_STREAM 3 +#define GRPC_CHTTP2_FRAME_SETTINGS 4 +#define GRPC_CHTTP2_FRAME_PING 6 +#define GRPC_CHTTP2_FRAME_GOAWAY 7 +#define GRPC_CHTTP2_FRAME_WINDOW_UPDATE 8 + +#define GRPC_CHTTP2_MAX_PAYLOAD_LENGTH ((1 << 14) - 1) + +#define GRPC_CHTTP2_DATA_FLAG_END_STREAM 1 +#define GRPC_CHTTP2_FLAG_ACK 1 +#define GRPC_CHTTP2_DATA_FLAG_END_HEADERS 4 +#define GRPC_CHTTP2_DATA_FLAG_PADDED 8 +#define GRPC_CHTTP2_FLAG_HAS_PRIORITY 0x20 + +#endif /* GRPC_CORE_LIB_TRANSPORT_CHTTP2_FRAME_H */ diff --git a/src/core/lib/transport/chttp2/frame_data.c b/src/core/lib/transport/chttp2/frame_data.c new file mode 100644 index 0000000000..cf25c3ccc1 --- /dev/null +++ b/src/core/lib/transport/chttp2/frame_data.c @@ -0,0 +1,248 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/chttp2/frame_data.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/useful.h> +#include "src/core/lib/support/string.h" +#include "src/core/lib/transport/chttp2/internal.h" +#include "src/core/lib/transport/transport.h" + +grpc_chttp2_parse_error grpc_chttp2_data_parser_init( + grpc_chttp2_data_parser *parser) { + parser->state = GRPC_CHTTP2_DATA_FH_0; + parser->parsing_frame = NULL; + return GRPC_CHTTP2_PARSE_OK; +} + +void grpc_chttp2_data_parser_destroy(grpc_exec_ctx *exec_ctx, + grpc_chttp2_data_parser *parser) { + grpc_byte_stream *bs; + if (parser->parsing_frame) { + grpc_chttp2_incoming_byte_stream_finished(exec_ctx, parser->parsing_frame, + 0, 1); + } + while ( + (bs = grpc_chttp2_incoming_frame_queue_pop(&parser->incoming_frames))) { + grpc_byte_stream_destroy(exec_ctx, bs); + } +} + +grpc_chttp2_parse_error grpc_chttp2_data_parser_begin_frame( + grpc_chttp2_data_parser *parser, uint8_t flags) { + if (flags & ~GRPC_CHTTP2_DATA_FLAG_END_STREAM) { + gpr_log(GPR_ERROR, "unsupported data flags: 0x%02x", flags); + return GRPC_CHTTP2_STREAM_ERROR; + } + + if (flags & GRPC_CHTTP2_DATA_FLAG_END_STREAM) { + parser->is_last_frame = 1; + } else { + parser->is_last_frame = 0; + } + + return GRPC_CHTTP2_PARSE_OK; +} + +void grpc_chttp2_incoming_frame_queue_merge( + grpc_chttp2_incoming_frame_queue *head_dst, + grpc_chttp2_incoming_frame_queue *tail_src) { + if (tail_src->head == NULL) { + return; + } + + if (head_dst->head == NULL) { + *head_dst = *tail_src; + memset(tail_src, 0, sizeof(*tail_src)); + return; + } + + head_dst->tail->next_message = tail_src->head; + head_dst->tail = tail_src->tail; + memset(tail_src, 0, sizeof(*tail_src)); +} + +grpc_byte_stream *grpc_chttp2_incoming_frame_queue_pop( + grpc_chttp2_incoming_frame_queue *q) { + grpc_byte_stream *out; + if (q->head == NULL) { + return NULL; + } + out = &q->head->base; + if (q->head == q->tail) { + memset(q, 0, sizeof(*q)); + } else { + q->head = q->head->next_message; + } + return out; +} + +void grpc_chttp2_encode_data(uint32_t id, gpr_slice_buffer *inbuf, + uint32_t write_bytes, int is_eof, + gpr_slice_buffer *outbuf) { + gpr_slice hdr; + uint8_t *p; + + hdr = gpr_slice_malloc(9); + p = GPR_SLICE_START_PTR(hdr); + GPR_ASSERT(write_bytes < (1 << 24)); + *p++ = (uint8_t)(write_bytes >> 16); + *p++ = (uint8_t)(write_bytes >> 8); + *p++ = (uint8_t)(write_bytes); + *p++ = GRPC_CHTTP2_FRAME_DATA; + *p++ = is_eof ? GRPC_CHTTP2_DATA_FLAG_END_STREAM : 0; + *p++ = (uint8_t)(id >> 24); + *p++ = (uint8_t)(id >> 16); + *p++ = (uint8_t)(id >> 8); + *p++ = (uint8_t)(id); + gpr_slice_buffer_add(outbuf, hdr); + + gpr_slice_buffer_move_first(inbuf, write_bytes, outbuf); +} + +grpc_chttp2_parse_error grpc_chttp2_data_parser_parse( + grpc_exec_ctx *exec_ctx, void *parser, + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) { + uint8_t *const beg = GPR_SLICE_START_PTR(slice); + uint8_t *const end = GPR_SLICE_END_PTR(slice); + uint8_t *cur = beg; + grpc_chttp2_data_parser *p = parser; + uint32_t message_flags; + grpc_chttp2_incoming_byte_stream *incoming_byte_stream; + + if (is_last && p->is_last_frame) { + stream_parsing->received_close = 1; + } + + if (cur == end) { + return GRPC_CHTTP2_PARSE_OK; + } + + switch (p->state) { + fh_0: + case GRPC_CHTTP2_DATA_FH_0: + p->frame_type = *cur; + switch (p->frame_type) { + case 0: + p->is_frame_compressed = 0; /* GPR_FALSE */ + break; + case 1: + p->is_frame_compressed = 1; /* GPR_TRUE */ + break; + default: + gpr_log(GPR_ERROR, "Bad GRPC frame type 0x%02x", p->frame_type); + return GRPC_CHTTP2_STREAM_ERROR; + } + if (++cur == end) { + p->state = GRPC_CHTTP2_DATA_FH_1; + return GRPC_CHTTP2_PARSE_OK; + } + /* fallthrough */ + case GRPC_CHTTP2_DATA_FH_1: + p->frame_size = ((uint32_t)*cur) << 24; + if (++cur == end) { + p->state = GRPC_CHTTP2_DATA_FH_2; + return GRPC_CHTTP2_PARSE_OK; + } + /* fallthrough */ + case GRPC_CHTTP2_DATA_FH_2: + p->frame_size |= ((uint32_t)*cur) << 16; + if (++cur == end) { + p->state = GRPC_CHTTP2_DATA_FH_3; + return GRPC_CHTTP2_PARSE_OK; + } + /* fallthrough */ + case GRPC_CHTTP2_DATA_FH_3: + p->frame_size |= ((uint32_t)*cur) << 8; + if (++cur == end) { + p->state = GRPC_CHTTP2_DATA_FH_4; + return GRPC_CHTTP2_PARSE_OK; + } + /* fallthrough */ + case GRPC_CHTTP2_DATA_FH_4: + p->frame_size |= ((uint32_t)*cur); + p->state = GRPC_CHTTP2_DATA_FRAME; + ++cur; + message_flags = 0; + if (p->is_frame_compressed) { + message_flags |= GRPC_WRITE_INTERNAL_COMPRESS; + } + p->parsing_frame = incoming_byte_stream = + grpc_chttp2_incoming_byte_stream_create( + exec_ctx, transport_parsing, stream_parsing, p->frame_size, + message_flags, &p->incoming_frames); + /* fallthrough */ + case GRPC_CHTTP2_DATA_FRAME: + if (cur == end) { + grpc_chttp2_list_add_parsing_seen_stream(transport_parsing, + stream_parsing); + return GRPC_CHTTP2_PARSE_OK; + } + grpc_chttp2_list_add_parsing_seen_stream(transport_parsing, + stream_parsing); + if ((uint32_t)(end - cur) == p->frame_size) { + grpc_chttp2_incoming_byte_stream_push( + exec_ctx, p->parsing_frame, + gpr_slice_sub(slice, (size_t)(cur - beg), (size_t)(end - beg))); + grpc_chttp2_incoming_byte_stream_finished(exec_ctx, p->parsing_frame, 1, + 1); + p->parsing_frame = NULL; + p->state = GRPC_CHTTP2_DATA_FH_0; + return GRPC_CHTTP2_PARSE_OK; + } else if ((uint32_t)(end - cur) > p->frame_size) { + grpc_chttp2_incoming_byte_stream_push( + exec_ctx, p->parsing_frame, + gpr_slice_sub(slice, (size_t)(cur - beg), + (size_t)(cur + p->frame_size - beg))); + grpc_chttp2_incoming_byte_stream_finished(exec_ctx, p->parsing_frame, 1, + 1); + p->parsing_frame = NULL; + cur += p->frame_size; + goto fh_0; /* loop */ + } else { + grpc_chttp2_incoming_byte_stream_push( + exec_ctx, p->parsing_frame, + gpr_slice_sub(slice, (size_t)(cur - beg), (size_t)(end - beg))); + GPR_ASSERT((size_t)(end - cur) <= p->frame_size); + p->frame_size -= (uint32_t)(end - cur); + return GRPC_CHTTP2_PARSE_OK; + } + } + + GPR_UNREACHABLE_CODE(return GRPC_CHTTP2_CONNECTION_ERROR); +} diff --git a/src/core/lib/transport/chttp2/frame_data.h b/src/core/lib/transport/chttp2/frame_data.h new file mode 100644 index 0000000000..da404a42c6 --- /dev/null +++ b/src/core/lib/transport/chttp2/frame_data.h @@ -0,0 +1,101 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CHTTP2_FRAME_DATA_H +#define GRPC_CORE_LIB_TRANSPORT_CHTTP2_FRAME_DATA_H + +/* Parser for GRPC streams embedded in DATA frames */ + +#include <grpc/support/slice.h> +#include <grpc/support/slice_buffer.h> +#include "src/core/lib/iomgr/exec_ctx.h" +#include "src/core/lib/transport/byte_stream.h" +#include "src/core/lib/transport/chttp2/frame.h" + +typedef enum { + GRPC_CHTTP2_DATA_FH_0, + GRPC_CHTTP2_DATA_FH_1, + GRPC_CHTTP2_DATA_FH_2, + GRPC_CHTTP2_DATA_FH_3, + GRPC_CHTTP2_DATA_FH_4, + GRPC_CHTTP2_DATA_FRAME +} grpc_chttp2_stream_state; + +typedef struct grpc_chttp2_incoming_byte_stream + grpc_chttp2_incoming_byte_stream; + +typedef struct grpc_chttp2_incoming_frame_queue { + grpc_chttp2_incoming_byte_stream *head; + grpc_chttp2_incoming_byte_stream *tail; +} grpc_chttp2_incoming_frame_queue; + +typedef struct { + grpc_chttp2_stream_state state; + uint8_t is_last_frame; + uint8_t frame_type; + uint32_t frame_size; + + int is_frame_compressed; + grpc_chttp2_incoming_frame_queue incoming_frames; + grpc_chttp2_incoming_byte_stream *parsing_frame; +} grpc_chttp2_data_parser; + +void grpc_chttp2_incoming_frame_queue_merge( + grpc_chttp2_incoming_frame_queue *head_dst, + grpc_chttp2_incoming_frame_queue *tail_src); +grpc_byte_stream *grpc_chttp2_incoming_frame_queue_pop( + grpc_chttp2_incoming_frame_queue *q); + +/* initialize per-stream state for data frame parsing */ +grpc_chttp2_parse_error grpc_chttp2_data_parser_init( + grpc_chttp2_data_parser *parser); + +void grpc_chttp2_data_parser_destroy(grpc_exec_ctx *exec_ctx, + grpc_chttp2_data_parser *parser); + +/* start processing a new data frame */ +grpc_chttp2_parse_error grpc_chttp2_data_parser_begin_frame( + grpc_chttp2_data_parser *parser, uint8_t flags); + +/* handle a slice of a data frame - is_last indicates the last slice of a + frame */ +grpc_chttp2_parse_error grpc_chttp2_data_parser_parse( + grpc_exec_ctx *exec_ctx, void *parser, + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last); + +void grpc_chttp2_encode_data(uint32_t id, gpr_slice_buffer *inbuf, + uint32_t write_bytes, int is_eof, + gpr_slice_buffer *outbuf); + +#endif /* GRPC_CORE_LIB_TRANSPORT_CHTTP2_FRAME_DATA_H */ diff --git a/src/core/lib/transport/chttp2/frame_goaway.c b/src/core/lib/transport/chttp2/frame_goaway.c new file mode 100644 index 0000000000..bb8c28df90 --- /dev/null +++ b/src/core/lib/transport/chttp2/frame_goaway.c @@ -0,0 +1,193 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/chttp2/frame_goaway.h" +#include "src/core/lib/transport/chttp2/internal.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +void grpc_chttp2_goaway_parser_init(grpc_chttp2_goaway_parser *p) { + p->debug_data = NULL; +} + +void grpc_chttp2_goaway_parser_destroy(grpc_chttp2_goaway_parser *p) { + gpr_free(p->debug_data); +} + +grpc_chttp2_parse_error grpc_chttp2_goaway_parser_begin_frame( + grpc_chttp2_goaway_parser *p, uint32_t length, uint8_t flags) { + if (length < 8) { + gpr_log(GPR_ERROR, "goaway frame too short (%d bytes)", length); + return GRPC_CHTTP2_CONNECTION_ERROR; + } + + gpr_free(p->debug_data); + p->debug_length = length - 8; + p->debug_data = gpr_malloc(p->debug_length); + p->debug_pos = 0; + p->state = GRPC_CHTTP2_GOAWAY_LSI0; + return GRPC_CHTTP2_PARSE_OK; +} + +grpc_chttp2_parse_error grpc_chttp2_goaway_parser_parse( + grpc_exec_ctx *exec_ctx, void *parser, + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) { + uint8_t *const beg = GPR_SLICE_START_PTR(slice); + uint8_t *const end = GPR_SLICE_END_PTR(slice); + uint8_t *cur = beg; + grpc_chttp2_goaway_parser *p = parser; + + switch (p->state) { + case GRPC_CHTTP2_GOAWAY_LSI0: + if (cur == end) { + p->state = GRPC_CHTTP2_GOAWAY_LSI0; + return GRPC_CHTTP2_PARSE_OK; + } + p->last_stream_id = ((uint32_t)*cur) << 24; + ++cur; + /* fallthrough */ + case GRPC_CHTTP2_GOAWAY_LSI1: + if (cur == end) { + p->state = GRPC_CHTTP2_GOAWAY_LSI1; + return GRPC_CHTTP2_PARSE_OK; + } + p->last_stream_id |= ((uint32_t)*cur) << 16; + ++cur; + /* fallthrough */ + case GRPC_CHTTP2_GOAWAY_LSI2: + if (cur == end) { + p->state = GRPC_CHTTP2_GOAWAY_LSI2; + return GRPC_CHTTP2_PARSE_OK; + } + p->last_stream_id |= ((uint32_t)*cur) << 8; + ++cur; + /* fallthrough */ + case GRPC_CHTTP2_GOAWAY_LSI3: + if (cur == end) { + p->state = GRPC_CHTTP2_GOAWAY_LSI3; + return GRPC_CHTTP2_PARSE_OK; + } + p->last_stream_id |= ((uint32_t)*cur); + ++cur; + /* fallthrough */ + case GRPC_CHTTP2_GOAWAY_ERR0: + if (cur == end) { + p->state = GRPC_CHTTP2_GOAWAY_ERR0; + return GRPC_CHTTP2_PARSE_OK; + } + p->error_code = ((uint32_t)*cur) << 24; + ++cur; + /* fallthrough */ + case GRPC_CHTTP2_GOAWAY_ERR1: + if (cur == end) { + p->state = GRPC_CHTTP2_GOAWAY_ERR1; + return GRPC_CHTTP2_PARSE_OK; + } + p->error_code |= ((uint32_t)*cur) << 16; + ++cur; + /* fallthrough */ + case GRPC_CHTTP2_GOAWAY_ERR2: + if (cur == end) { + p->state = GRPC_CHTTP2_GOAWAY_ERR2; + return GRPC_CHTTP2_PARSE_OK; + } + p->error_code |= ((uint32_t)*cur) << 8; + ++cur; + /* fallthrough */ + case GRPC_CHTTP2_GOAWAY_ERR3: + if (cur == end) { + p->state = GRPC_CHTTP2_GOAWAY_ERR3; + return GRPC_CHTTP2_PARSE_OK; + } + p->error_code |= ((uint32_t)*cur); + ++cur; + /* fallthrough */ + case GRPC_CHTTP2_GOAWAY_DEBUG: + memcpy(p->debug_data + p->debug_pos, cur, (size_t)(end - cur)); + GPR_ASSERT((size_t)(end - cur) < UINT32_MAX - p->debug_pos); + p->debug_pos += (uint32_t)(end - cur); + p->state = GRPC_CHTTP2_GOAWAY_DEBUG; + if (is_last) { + transport_parsing->goaway_received = 1; + transport_parsing->goaway_last_stream_index = p->last_stream_id; + gpr_slice_unref(transport_parsing->goaway_text); + transport_parsing->goaway_error = (grpc_status_code)p->error_code; + transport_parsing->goaway_text = + gpr_slice_new(p->debug_data, p->debug_length, gpr_free); + p->debug_data = NULL; + } + return GRPC_CHTTP2_PARSE_OK; + } + GPR_UNREACHABLE_CODE(return GRPC_CHTTP2_CONNECTION_ERROR); +} + +void grpc_chttp2_goaway_append(uint32_t last_stream_id, uint32_t error_code, + gpr_slice debug_data, + gpr_slice_buffer *slice_buffer) { + gpr_slice header = gpr_slice_malloc(9 + 4 + 4); + uint8_t *p = GPR_SLICE_START_PTR(header); + uint32_t frame_length; + GPR_ASSERT(GPR_SLICE_LENGTH(debug_data) < UINT32_MAX - 4 - 4); + frame_length = 4 + 4 + (uint32_t)GPR_SLICE_LENGTH(debug_data); + + /* frame header: length */ + *p++ = (uint8_t)(frame_length >> 16); + *p++ = (uint8_t)(frame_length >> 8); + *p++ = (uint8_t)(frame_length); + /* frame header: type */ + *p++ = GRPC_CHTTP2_FRAME_GOAWAY; + /* frame header: flags */ + *p++ = 0; + /* frame header: stream id */ + *p++ = 0; + *p++ = 0; + *p++ = 0; + *p++ = 0; + /* payload: last stream id */ + *p++ = (uint8_t)(last_stream_id >> 24); + *p++ = (uint8_t)(last_stream_id >> 16); + *p++ = (uint8_t)(last_stream_id >> 8); + *p++ = (uint8_t)(last_stream_id); + /* payload: error code */ + *p++ = (uint8_t)(error_code >> 24); + *p++ = (uint8_t)(error_code >> 16); + *p++ = (uint8_t)(error_code >> 8); + *p++ = (uint8_t)(error_code); + GPR_ASSERT(p == GPR_SLICE_END_PTR(header)); + gpr_slice_buffer_add(slice_buffer, header); + gpr_slice_buffer_add(slice_buffer, debug_data); +} diff --git a/src/core/lib/transport/chttp2/frame_goaway.h b/src/core/lib/transport/chttp2/frame_goaway.h new file mode 100644 index 0000000000..f64c44f3d9 --- /dev/null +++ b/src/core/lib/transport/chttp2/frame_goaway.h @@ -0,0 +1,77 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CHTTP2_FRAME_GOAWAY_H +#define GRPC_CORE_LIB_TRANSPORT_CHTTP2_FRAME_GOAWAY_H + +#include <grpc/support/port_platform.h> +#include <grpc/support/slice.h> +#include <grpc/support/slice_buffer.h> +#include "src/core/lib/iomgr/exec_ctx.h" +#include "src/core/lib/transport/chttp2/frame.h" + +typedef enum { + GRPC_CHTTP2_GOAWAY_LSI0, + GRPC_CHTTP2_GOAWAY_LSI1, + GRPC_CHTTP2_GOAWAY_LSI2, + GRPC_CHTTP2_GOAWAY_LSI3, + GRPC_CHTTP2_GOAWAY_ERR0, + GRPC_CHTTP2_GOAWAY_ERR1, + GRPC_CHTTP2_GOAWAY_ERR2, + GRPC_CHTTP2_GOAWAY_ERR3, + GRPC_CHTTP2_GOAWAY_DEBUG +} grpc_chttp2_goaway_parse_state; + +typedef struct { + grpc_chttp2_goaway_parse_state state; + uint32_t last_stream_id; + uint32_t error_code; + char *debug_data; + uint32_t debug_length; + uint32_t debug_pos; +} grpc_chttp2_goaway_parser; + +void grpc_chttp2_goaway_parser_init(grpc_chttp2_goaway_parser *p); +void grpc_chttp2_goaway_parser_destroy(grpc_chttp2_goaway_parser *p); +grpc_chttp2_parse_error grpc_chttp2_goaway_parser_begin_frame( + grpc_chttp2_goaway_parser *parser, uint32_t length, uint8_t flags); +grpc_chttp2_parse_error grpc_chttp2_goaway_parser_parse( + grpc_exec_ctx *exec_ctx, void *parser, + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last); + +void grpc_chttp2_goaway_append(uint32_t last_stream_id, uint32_t error_code, + gpr_slice debug_data, + gpr_slice_buffer *slice_buffer); + +#endif /* GRPC_CORE_LIB_TRANSPORT_CHTTP2_FRAME_GOAWAY_H */ diff --git a/src/core/lib/transport/chttp2/frame_ping.c b/src/core/lib/transport/chttp2/frame_ping.c new file mode 100644 index 0000000000..14ca394264 --- /dev/null +++ b/src/core/lib/transport/chttp2/frame_ping.c @@ -0,0 +1,97 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/chttp2/frame_ping.h" +#include "src/core/lib/transport/chttp2/internal.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +gpr_slice grpc_chttp2_ping_create(uint8_t ack, uint8_t *opaque_8bytes) { + gpr_slice slice = gpr_slice_malloc(9 + 8); + uint8_t *p = GPR_SLICE_START_PTR(slice); + + *p++ = 0; + *p++ = 0; + *p++ = 8; + *p++ = GRPC_CHTTP2_FRAME_PING; + *p++ = ack ? 1 : 0; + *p++ = 0; + *p++ = 0; + *p++ = 0; + *p++ = 0; + memcpy(p, opaque_8bytes, 8); + + return slice; +} + +grpc_chttp2_parse_error grpc_chttp2_ping_parser_begin_frame( + grpc_chttp2_ping_parser *parser, uint32_t length, uint8_t flags) { + if (flags & 0xfe || length != 8) { + gpr_log(GPR_ERROR, "invalid ping: length=%d, flags=%02x", length, flags); + return GRPC_CHTTP2_CONNECTION_ERROR; + } + parser->byte = 0; + parser->is_ack = flags; + return GRPC_CHTTP2_PARSE_OK; +} + +grpc_chttp2_parse_error grpc_chttp2_ping_parser_parse( + grpc_exec_ctx *exec_ctx, void *parser, + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) { + uint8_t *const beg = GPR_SLICE_START_PTR(slice); + uint8_t *const end = GPR_SLICE_END_PTR(slice); + uint8_t *cur = beg; + grpc_chttp2_ping_parser *p = parser; + + while (p->byte != 8 && cur != end) { + p->opaque_8bytes[p->byte] = *cur; + cur++; + p->byte++; + } + + if (p->byte == 8) { + GPR_ASSERT(is_last); + if (p->is_ack) { + grpc_chttp2_ack_ping(exec_ctx, transport_parsing, p->opaque_8bytes); + } else { + gpr_slice_buffer_add(&transport_parsing->qbuf, + grpc_chttp2_ping_create(1, p->opaque_8bytes)); + } + } + + return GRPC_CHTTP2_PARSE_OK; +} diff --git a/src/core/lib/transport/chttp2/frame_ping.h b/src/core/lib/transport/chttp2/frame_ping.h new file mode 100644 index 0000000000..7640fc4773 --- /dev/null +++ b/src/core/lib/transport/chttp2/frame_ping.h @@ -0,0 +1,56 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CHTTP2_FRAME_PING_H +#define GRPC_CORE_LIB_TRANSPORT_CHTTP2_FRAME_PING_H + +#include <grpc/support/slice.h> +#include "src/core/lib/iomgr/exec_ctx.h" +#include "src/core/lib/transport/chttp2/frame.h" + +typedef struct { + uint8_t byte; + uint8_t is_ack; + uint8_t opaque_8bytes[8]; +} grpc_chttp2_ping_parser; + +gpr_slice grpc_chttp2_ping_create(uint8_t ack, uint8_t *opaque_8bytes); + +grpc_chttp2_parse_error grpc_chttp2_ping_parser_begin_frame( + grpc_chttp2_ping_parser *parser, uint32_t length, uint8_t flags); +grpc_chttp2_parse_error grpc_chttp2_ping_parser_parse( + grpc_exec_ctx *exec_ctx, void *parser, + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last); + +#endif /* GRPC_CORE_LIB_TRANSPORT_CHTTP2_FRAME_PING_H */ diff --git a/src/core/lib/transport/chttp2/frame_rst_stream.c b/src/core/lib/transport/chttp2/frame_rst_stream.c new file mode 100644 index 0000000000..060912afc4 --- /dev/null +++ b/src/core/lib/transport/chttp2/frame_rst_stream.c @@ -0,0 +1,99 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/chttp2/frame_rst_stream.h" +#include "src/core/lib/transport/chttp2/internal.h" + +#include <grpc/support/log.h> + +#include "src/core/lib/transport/chttp2/frame.h" + +gpr_slice grpc_chttp2_rst_stream_create(uint32_t id, uint32_t code) { + gpr_slice slice = gpr_slice_malloc(13); + uint8_t *p = GPR_SLICE_START_PTR(slice); + + *p++ = 0; + *p++ = 0; + *p++ = 4; + *p++ = GRPC_CHTTP2_FRAME_RST_STREAM; + *p++ = 0; + *p++ = (uint8_t)(id >> 24); + *p++ = (uint8_t)(id >> 16); + *p++ = (uint8_t)(id >> 8); + *p++ = (uint8_t)(id); + *p++ = (uint8_t)(code >> 24); + *p++ = (uint8_t)(code >> 16); + *p++ = (uint8_t)(code >> 8); + *p++ = (uint8_t)(code); + + return slice; +} + +grpc_chttp2_parse_error grpc_chttp2_rst_stream_parser_begin_frame( + grpc_chttp2_rst_stream_parser *parser, uint32_t length, uint8_t flags) { + if (length != 4) { + gpr_log(GPR_ERROR, "invalid rst_stream: length=%d, flags=%02x", length, + flags); + return GRPC_CHTTP2_CONNECTION_ERROR; + } + parser->byte = 0; + return GRPC_CHTTP2_PARSE_OK; +} + +grpc_chttp2_parse_error grpc_chttp2_rst_stream_parser_parse( + grpc_exec_ctx *exec_ctx, void *parser, + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) { + uint8_t *const beg = GPR_SLICE_START_PTR(slice); + uint8_t *const end = GPR_SLICE_END_PTR(slice); + uint8_t *cur = beg; + grpc_chttp2_rst_stream_parser *p = parser; + + while (p->byte != 4 && cur != end) { + p->reason_bytes[p->byte] = *cur; + cur++; + p->byte++; + } + + if (p->byte == 4) { + GPR_ASSERT(is_last); + stream_parsing->received_close = 1; + stream_parsing->saw_rst_stream = 1; + stream_parsing->rst_stream_reason = (((uint32_t)p->reason_bytes[0]) << 24) | + (((uint32_t)p->reason_bytes[1]) << 16) | + (((uint32_t)p->reason_bytes[2]) << 8) | + (((uint32_t)p->reason_bytes[3])); + } + + return GRPC_CHTTP2_PARSE_OK; +} diff --git a/src/core/lib/transport/chttp2/frame_rst_stream.h b/src/core/lib/transport/chttp2/frame_rst_stream.h new file mode 100644 index 0000000000..93155fde9d --- /dev/null +++ b/src/core/lib/transport/chttp2/frame_rst_stream.h @@ -0,0 +1,55 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CHTTP2_FRAME_RST_STREAM_H +#define GRPC_CORE_LIB_TRANSPORT_CHTTP2_FRAME_RST_STREAM_H + +#include <grpc/support/slice.h> +#include "src/core/lib/iomgr/exec_ctx.h" +#include "src/core/lib/transport/chttp2/frame.h" + +typedef struct { + uint8_t byte; + uint8_t reason_bytes[4]; +} grpc_chttp2_rst_stream_parser; + +gpr_slice grpc_chttp2_rst_stream_create(uint32_t stream_id, uint32_t code); + +grpc_chttp2_parse_error grpc_chttp2_rst_stream_parser_begin_frame( + grpc_chttp2_rst_stream_parser *parser, uint32_t length, uint8_t flags); +grpc_chttp2_parse_error grpc_chttp2_rst_stream_parser_parse( + grpc_exec_ctx *exec_ctx, void *parser, + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last); + +#endif /* GRPC_CORE_LIB_TRANSPORT_CHTTP2_FRAME_RST_STREAM_H */ diff --git a/src/core/lib/transport/chttp2/frame_settings.c b/src/core/lib/transport/chttp2/frame_settings.c new file mode 100644 index 0000000000..48429c2a78 --- /dev/null +++ b/src/core/lib/transport/chttp2/frame_settings.c @@ -0,0 +1,259 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/chttp2/frame_settings.h" +#include "src/core/lib/transport/chttp2/internal.h" + +#include <string.h> + +#include <grpc/support/log.h> +#include <grpc/support/useful.h> + +#include "src/core/lib/debug/trace.h" +#include "src/core/lib/transport/chttp2/frame.h" +#include "src/core/lib/transport/chttp2/http2_errors.h" +#include "src/core/lib/transport/chttp2_transport.h" + +#define MAX_MAX_HEADER_LIST_SIZE (1024 * 1024 * 1024) + +/* HTTP/2 mandated initial connection settings */ +const grpc_chttp2_setting_parameters + grpc_chttp2_settings_parameters[GRPC_CHTTP2_NUM_SETTINGS] = { + {NULL, 0, 0, 0, GRPC_CHTTP2_DISCONNECT_ON_INVALID_VALUE, + GRPC_CHTTP2_PROTOCOL_ERROR}, + {"HEADER_TABLE_SIZE", 4096, 0, 0xffffffff, + GRPC_CHTTP2_CLAMP_INVALID_VALUE, GRPC_CHTTP2_PROTOCOL_ERROR}, + {"ENABLE_PUSH", 1, 0, 1, GRPC_CHTTP2_DISCONNECT_ON_INVALID_VALUE, + GRPC_CHTTP2_PROTOCOL_ERROR}, + {"MAX_CONCURRENT_STREAMS", 0xffffffffu, 0, 0xffffffffu, + GRPC_CHTTP2_DISCONNECT_ON_INVALID_VALUE, GRPC_CHTTP2_PROTOCOL_ERROR}, + {"INITIAL_WINDOW_SIZE", 65535, 0, 0x7fffffffu, + GRPC_CHTTP2_DISCONNECT_ON_INVALID_VALUE, + GRPC_CHTTP2_FLOW_CONTROL_ERROR}, + {"MAX_FRAME_SIZE", 16384, 16384, 16777215, + GRPC_CHTTP2_DISCONNECT_ON_INVALID_VALUE, GRPC_CHTTP2_PROTOCOL_ERROR}, + {"MAX_HEADER_LIST_SIZE", MAX_MAX_HEADER_LIST_SIZE, 0, + MAX_MAX_HEADER_LIST_SIZE, GRPC_CHTTP2_CLAMP_INVALID_VALUE, + GRPC_CHTTP2_PROTOCOL_ERROR}, +}; + +static uint8_t *fill_header(uint8_t *out, uint32_t length, uint8_t flags) { + *out++ = (uint8_t)(length >> 16); + *out++ = (uint8_t)(length >> 8); + *out++ = (uint8_t)(length); + *out++ = GRPC_CHTTP2_FRAME_SETTINGS; + *out++ = flags; + *out++ = 0; + *out++ = 0; + *out++ = 0; + *out++ = 0; + return out; +} + +gpr_slice grpc_chttp2_settings_create(uint32_t *old, const uint32_t *new, + uint32_t force_mask, size_t count) { + size_t i; + uint32_t n = 0; + gpr_slice output; + uint8_t *p; + + for (i = 0; i < count; i++) { + n += (new[i] != old[i] || (force_mask & (1u << i)) != 0); + } + + output = gpr_slice_malloc(9 + 6 * n); + p = fill_header(GPR_SLICE_START_PTR(output), 6 * n, 0); + + for (i = 0; i < count; i++) { + if (new[i] != old[i] || (force_mask & (1u << i)) != 0) { + GPR_ASSERT(i); + *p++ = (uint8_t)(i >> 8); + *p++ = (uint8_t)(i); + *p++ = (uint8_t)(new[i] >> 24); + *p++ = (uint8_t)(new[i] >> 16); + *p++ = (uint8_t)(new[i] >> 8); + *p++ = (uint8_t)(new[i]); + old[i] = new[i]; + } + } + + GPR_ASSERT(p == GPR_SLICE_END_PTR(output)); + + return output; +} + +gpr_slice grpc_chttp2_settings_ack_create(void) { + gpr_slice output = gpr_slice_malloc(9); + fill_header(GPR_SLICE_START_PTR(output), 0, GRPC_CHTTP2_FLAG_ACK); + return output; +} + +grpc_chttp2_parse_error grpc_chttp2_settings_parser_begin_frame( + grpc_chttp2_settings_parser *parser, uint32_t length, uint8_t flags, + uint32_t *settings) { + parser->target_settings = settings; + memcpy(parser->incoming_settings, settings, + GRPC_CHTTP2_NUM_SETTINGS * sizeof(uint32_t)); + parser->is_ack = 0; + parser->state = GRPC_CHTTP2_SPS_ID0; + if (flags == GRPC_CHTTP2_FLAG_ACK) { + parser->is_ack = 1; + if (length != 0) { + gpr_log(GPR_ERROR, "non-empty settings ack frame received"); + return GRPC_CHTTP2_CONNECTION_ERROR; + } + return GRPC_CHTTP2_PARSE_OK; + } else if (flags != 0) { + gpr_log(GPR_ERROR, "invalid flags on settings frame"); + return GRPC_CHTTP2_CONNECTION_ERROR; + } else if (length % 6 != 0) { + gpr_log(GPR_ERROR, "settings frames must be a multiple of six bytes"); + return GRPC_CHTTP2_CONNECTION_ERROR; + } else { + return GRPC_CHTTP2_PARSE_OK; + } +} + +grpc_chttp2_parse_error grpc_chttp2_settings_parser_parse( + grpc_exec_ctx *exec_ctx, void *p, + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) { + grpc_chttp2_settings_parser *parser = p; + const uint8_t *cur = GPR_SLICE_START_PTR(slice); + const uint8_t *end = GPR_SLICE_END_PTR(slice); + + if (parser->is_ack) { + return GRPC_CHTTP2_PARSE_OK; + } + + for (;;) { + switch (parser->state) { + case GRPC_CHTTP2_SPS_ID0: + if (cur == end) { + parser->state = GRPC_CHTTP2_SPS_ID0; + if (is_last) { + transport_parsing->settings_updated = 1; + memcpy(parser->target_settings, parser->incoming_settings, + GRPC_CHTTP2_NUM_SETTINGS * sizeof(uint32_t)); + gpr_slice_buffer_add(&transport_parsing->qbuf, + grpc_chttp2_settings_ack_create()); + } + return GRPC_CHTTP2_PARSE_OK; + } + parser->id = (uint16_t)(((uint16_t)*cur) << 8); + cur++; + /* fallthrough */ + case GRPC_CHTTP2_SPS_ID1: + if (cur == end) { + parser->state = GRPC_CHTTP2_SPS_ID1; + return GRPC_CHTTP2_PARSE_OK; + } + parser->id = (uint16_t)(parser->id | (*cur)); + cur++; + /* fallthrough */ + case GRPC_CHTTP2_SPS_VAL0: + if (cur == end) { + parser->state = GRPC_CHTTP2_SPS_VAL0; + return GRPC_CHTTP2_PARSE_OK; + } + parser->value = ((uint32_t)*cur) << 24; + cur++; + /* fallthrough */ + case GRPC_CHTTP2_SPS_VAL1: + if (cur == end) { + parser->state = GRPC_CHTTP2_SPS_VAL1; + return GRPC_CHTTP2_PARSE_OK; + } + parser->value |= ((uint32_t)*cur) << 16; + cur++; + /* fallthrough */ + case GRPC_CHTTP2_SPS_VAL2: + if (cur == end) { + parser->state = GRPC_CHTTP2_SPS_VAL2; + return GRPC_CHTTP2_PARSE_OK; + } + parser->value |= ((uint32_t)*cur) << 8; + cur++; + /* fallthrough */ + case GRPC_CHTTP2_SPS_VAL3: + if (cur == end) { + parser->state = GRPC_CHTTP2_SPS_VAL3; + return GRPC_CHTTP2_PARSE_OK; + } else { + parser->state = GRPC_CHTTP2_SPS_ID0; + } + parser->value |= *cur; + cur++; + + if (parser->id > 0 && parser->id < GRPC_CHTTP2_NUM_SETTINGS) { + const grpc_chttp2_setting_parameters *sp = + &grpc_chttp2_settings_parameters[parser->id]; + if (parser->value < sp->min_value || parser->value > sp->max_value) { + switch (sp->invalid_value_behavior) { + case GRPC_CHTTP2_CLAMP_INVALID_VALUE: + parser->value = + GPR_CLAMP(parser->value, sp->min_value, sp->max_value); + break; + case GRPC_CHTTP2_DISCONNECT_ON_INVALID_VALUE: + grpc_chttp2_goaway_append( + transport_parsing->last_incoming_stream_id, sp->error_value, + gpr_slice_from_static_string("HTTP2 settings error"), + &transport_parsing->qbuf); + gpr_log(GPR_ERROR, "invalid value %u passed for %s", + parser->value, sp->name); + return GRPC_CHTTP2_CONNECTION_ERROR; + } + } + if (parser->id == GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE && + parser->incoming_settings[parser->id] != parser->value) { + transport_parsing->initial_window_update = + (int64_t)parser->value - parser->incoming_settings[parser->id]; + if (grpc_http_trace) { + gpr_log(GPR_DEBUG, "adding %d for initial_window change", + (int)transport_parsing->initial_window_update); + } + } + parser->incoming_settings[parser->id] = parser->value; + if (grpc_http_trace) { + gpr_log(GPR_DEBUG, "CHTTP2:%s: got setting %d = %d", + transport_parsing->is_client ? "CLI" : "SVR", parser->id, + parser->value); + } + } else { + gpr_log(GPR_ERROR, "CHTTP2: Ignoring unknown setting %d (value %d)", + parser->id, parser->value); + } + break; + } + } +} diff --git a/src/core/lib/transport/chttp2/frame_settings.h b/src/core/lib/transport/chttp2/frame_settings.h new file mode 100644 index 0000000000..8b294de021 --- /dev/null +++ b/src/core/lib/transport/chttp2/frame_settings.h @@ -0,0 +1,103 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CHTTP2_FRAME_SETTINGS_H +#define GRPC_CORE_LIB_TRANSPORT_CHTTP2_FRAME_SETTINGS_H + +#include <grpc/support/port_platform.h> +#include <grpc/support/slice.h> +#include "src/core/lib/iomgr/exec_ctx.h" +#include "src/core/lib/transport/chttp2/frame.h" + +typedef enum { + GRPC_CHTTP2_SPS_ID0, + GRPC_CHTTP2_SPS_ID1, + GRPC_CHTTP2_SPS_VAL0, + GRPC_CHTTP2_SPS_VAL1, + GRPC_CHTTP2_SPS_VAL2, + GRPC_CHTTP2_SPS_VAL3 +} grpc_chttp2_settings_parse_state; + +/* The things HTTP/2 defines as connection level settings */ +typedef enum { + GRPC_CHTTP2_SETTINGS_HEADER_TABLE_SIZE = 1, + GRPC_CHTTP2_SETTINGS_ENABLE_PUSH = 2, + GRPC_CHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS = 3, + GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE = 4, + GRPC_CHTTP2_SETTINGS_MAX_FRAME_SIZE = 5, + GRPC_CHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE = 6, + GRPC_CHTTP2_NUM_SETTINGS +} grpc_chttp2_setting_id; + +typedef struct { + grpc_chttp2_settings_parse_state state; + uint32_t *target_settings; + uint8_t is_ack; + uint16_t id; + uint32_t value; + uint32_t incoming_settings[GRPC_CHTTP2_NUM_SETTINGS]; +} grpc_chttp2_settings_parser; + +typedef enum { + GRPC_CHTTP2_CLAMP_INVALID_VALUE, + GRPC_CHTTP2_DISCONNECT_ON_INVALID_VALUE +} grpc_chttp2_invalid_value_behavior; + +typedef struct { + const char *name; + uint32_t default_value; + uint32_t min_value; + uint32_t max_value; + grpc_chttp2_invalid_value_behavior invalid_value_behavior; + uint32_t error_value; +} grpc_chttp2_setting_parameters; + +/* HTTP/2 mandated connection setting parameters */ +extern const grpc_chttp2_setting_parameters + grpc_chttp2_settings_parameters[GRPC_CHTTP2_NUM_SETTINGS]; + +/* Create a settings frame by diffing old & new, and updating old to be new */ +gpr_slice grpc_chttp2_settings_create(uint32_t *old, const uint32_t *new, + uint32_t force_mask, size_t count); +/* Create an ack settings frame */ +gpr_slice grpc_chttp2_settings_ack_create(void); + +grpc_chttp2_parse_error grpc_chttp2_settings_parser_begin_frame( + grpc_chttp2_settings_parser *parser, uint32_t length, uint8_t flags, + uint32_t *settings); +grpc_chttp2_parse_error grpc_chttp2_settings_parser_parse( + grpc_exec_ctx *exec_ctx, void *parser, + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last); + +#endif /* GRPC_CORE_LIB_TRANSPORT_CHTTP2_FRAME_SETTINGS_H */ diff --git a/src/core/lib/transport/chttp2/frame_window_update.c b/src/core/lib/transport/chttp2/frame_window_update.c new file mode 100644 index 0000000000..2ab5003316 --- /dev/null +++ b/src/core/lib/transport/chttp2/frame_window_update.c @@ -0,0 +1,113 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/chttp2/frame_window_update.h" +#include "src/core/lib/transport/chttp2/internal.h" + +#include <grpc/support/log.h> + +gpr_slice grpc_chttp2_window_update_create(uint32_t id, + uint32_t window_update) { + gpr_slice slice = gpr_slice_malloc(13); + uint8_t *p = GPR_SLICE_START_PTR(slice); + + GPR_ASSERT(window_update); + + *p++ = 0; + *p++ = 0; + *p++ = 4; + *p++ = GRPC_CHTTP2_FRAME_WINDOW_UPDATE; + *p++ = 0; + *p++ = (uint8_t)(id >> 24); + *p++ = (uint8_t)(id >> 16); + *p++ = (uint8_t)(id >> 8); + *p++ = (uint8_t)(id); + *p++ = (uint8_t)(window_update >> 24); + *p++ = (uint8_t)(window_update >> 16); + *p++ = (uint8_t)(window_update >> 8); + *p++ = (uint8_t)(window_update); + + return slice; +} + +grpc_chttp2_parse_error grpc_chttp2_window_update_parser_begin_frame( + grpc_chttp2_window_update_parser *parser, uint32_t length, uint8_t flags) { + if (flags || length != 4) { + gpr_log(GPR_ERROR, "invalid window update: length=%d, flags=%02x", length, + flags); + return GRPC_CHTTP2_CONNECTION_ERROR; + } + parser->byte = 0; + parser->amount = 0; + return GRPC_CHTTP2_PARSE_OK; +} + +grpc_chttp2_parse_error grpc_chttp2_window_update_parser_parse( + grpc_exec_ctx *exec_ctx, void *parser, + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) { + uint8_t *const beg = GPR_SLICE_START_PTR(slice); + uint8_t *const end = GPR_SLICE_END_PTR(slice); + uint8_t *cur = beg; + grpc_chttp2_window_update_parser *p = parser; + + while (p->byte != 4 && cur != end) { + p->amount |= ((uint32_t)*cur) << (8 * (3 - p->byte)); + cur++; + p->byte++; + } + + if (p->byte == 4) { + uint32_t received_update = p->amount; + if (received_update == 0 || (received_update & 0x80000000u)) { + gpr_log(GPR_ERROR, "invalid window update bytes: %d", p->amount); + return GRPC_CHTTP2_CONNECTION_ERROR; + } + GPR_ASSERT(is_last); + + if (transport_parsing->incoming_stream_id != 0) { + if (stream_parsing != NULL) { + GRPC_CHTTP2_FLOW_CREDIT_STREAM("parse", transport_parsing, + stream_parsing, outgoing_window, + received_update); + grpc_chttp2_list_add_parsing_seen_stream(transport_parsing, + stream_parsing); + } + } else { + GRPC_CHTTP2_FLOW_CREDIT_TRANSPORT("parse", transport_parsing, + outgoing_window, received_update); + } + } + + return GRPC_CHTTP2_PARSE_OK; +} diff --git a/src/core/lib/transport/chttp2/frame_window_update.h b/src/core/lib/transport/chttp2/frame_window_update.h new file mode 100644 index 0000000000..4b1aea294d --- /dev/null +++ b/src/core/lib/transport/chttp2/frame_window_update.h @@ -0,0 +1,56 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CHTTP2_FRAME_WINDOW_UPDATE_H +#define GRPC_CORE_LIB_TRANSPORT_CHTTP2_FRAME_WINDOW_UPDATE_H + +#include <grpc/support/slice.h> +#include "src/core/lib/iomgr/exec_ctx.h" +#include "src/core/lib/transport/chttp2/frame.h" + +typedef struct { + uint8_t byte; + uint8_t is_connection_update; + uint32_t amount; +} grpc_chttp2_window_update_parser; + +gpr_slice grpc_chttp2_window_update_create(uint32_t id, uint32_t window_delta); + +grpc_chttp2_parse_error grpc_chttp2_window_update_parser_begin_frame( + grpc_chttp2_window_update_parser *parser, uint32_t length, uint8_t flags); +grpc_chttp2_parse_error grpc_chttp2_window_update_parser_parse( + grpc_exec_ctx *exec_ctx, void *parser, + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last); + +#endif /* GRPC_CORE_LIB_TRANSPORT_CHTTP2_FRAME_WINDOW_UPDATE_H */ diff --git a/src/core/lib/transport/chttp2/hpack_encoder.c b/src/core/lib/transport/chttp2/hpack_encoder.c new file mode 100644 index 0000000000..6b45929b04 --- /dev/null +++ b/src/core/lib/transport/chttp2/hpack_encoder.c @@ -0,0 +1,568 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/chttp2/hpack_encoder.h" + +#include <assert.h> +#include <string.h> + +/* This is here for grpc_is_binary_header + * TODO(murgatroid99): Remove this + */ +#include <grpc/grpc.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/useful.h> + +#include "src/core/lib/transport/chttp2/bin_encoder.h" +#include "src/core/lib/transport/chttp2/hpack_table.h" +#include "src/core/lib/transport/chttp2/timeout_encoding.h" +#include "src/core/lib/transport/chttp2/varint.h" +#include "src/core/lib/transport/static_metadata.h" + +#define HASH_FRAGMENT_1(x) ((x)&255) +#define HASH_FRAGMENT_2(x) ((x >> 8) & 255) +#define HASH_FRAGMENT_3(x) ((x >> 16) & 255) +#define HASH_FRAGMENT_4(x) ((x >> 24) & 255) + +/* if the probability of this item being seen again is < 1/x then don't add + it to the table */ +#define ONE_ON_ADD_PROBABILITY 128 +/* don't consider adding anything bigger than this to the hpack table */ +#define MAX_DECODER_SPACE_USAGE 512 + +typedef struct { + int is_first_frame; + /* number of bytes in 'output' when we started the frame - used to calculate + frame length */ + size_t output_length_at_start_of_frame; + /* index (in output) of the header for the current frame */ + size_t header_idx; + /* have we seen a regular (non-colon-prefixed) header yet? */ + uint8_t seen_regular_header; + /* output stream id */ + uint32_t stream_id; + gpr_slice_buffer *output; +} framer_state; + +/* fills p (which is expected to be 9 bytes long) with a data frame header */ +static void fill_header(uint8_t *p, uint8_t type, uint32_t id, size_t len, + uint8_t flags) { + GPR_ASSERT(len < 16777316); + *p++ = (uint8_t)(len >> 16); + *p++ = (uint8_t)(len >> 8); + *p++ = (uint8_t)(len); + *p++ = type; + *p++ = flags; + *p++ = (uint8_t)(id >> 24); + *p++ = (uint8_t)(id >> 16); + *p++ = (uint8_t)(id >> 8); + *p++ = (uint8_t)(id); +} + +/* finish a frame - fill in the previously reserved header */ +static void finish_frame(framer_state *st, int is_header_boundary, + int is_last_in_stream) { + uint8_t type = 0xff; + type = st->is_first_frame ? GRPC_CHTTP2_FRAME_HEADER + : GRPC_CHTTP2_FRAME_CONTINUATION; + fill_header( + GPR_SLICE_START_PTR(st->output->slices[st->header_idx]), type, + st->stream_id, st->output->length - st->output_length_at_start_of_frame, + (uint8_t)((is_last_in_stream ? GRPC_CHTTP2_DATA_FLAG_END_STREAM : 0) | + (is_header_boundary ? GRPC_CHTTP2_DATA_FLAG_END_HEADERS : 0))); + st->is_first_frame = 0; +} + +/* begin a new frame: reserve off header space, remember how many bytes we'd + output before beginning */ +static void begin_frame(framer_state *st) { + st->header_idx = + gpr_slice_buffer_add_indexed(st->output, gpr_slice_malloc(9)); + st->output_length_at_start_of_frame = st->output->length; +} + +/* make sure that the current frame is of the type desired, and has sufficient + space to add at least about_to_add bytes -- finishes the current frame if + needed */ +static void ensure_space(framer_state *st, size_t need_bytes) { + if (st->output->length - st->output_length_at_start_of_frame + need_bytes <= + GRPC_CHTTP2_MAX_PAYLOAD_LENGTH) { + return; + } + finish_frame(st, 0, 0); + begin_frame(st); +} + +/* increment a filter count, halve all counts if one element reaches max */ +static void inc_filter(uint8_t idx, uint32_t *sum, uint8_t *elems) { + elems[idx]++; + if (elems[idx] < 255) { + (*sum)++; + } else { + int i; + *sum = 0; + for (i = 0; i < GRPC_CHTTP2_HPACKC_NUM_FILTERS; i++) { + elems[i] /= 2; + (*sum) += elems[i]; + } + } +} + +static void add_header_data(framer_state *st, gpr_slice slice) { + size_t len = GPR_SLICE_LENGTH(slice); + size_t remaining; + if (len == 0) return; + remaining = GRPC_CHTTP2_MAX_PAYLOAD_LENGTH + + st->output_length_at_start_of_frame - st->output->length; + if (len <= remaining) { + gpr_slice_buffer_add(st->output, slice); + } else { + gpr_slice_buffer_add(st->output, gpr_slice_split_head(&slice, remaining)); + finish_frame(st, 0, 0); + begin_frame(st); + add_header_data(st, slice); + } +} + +static uint8_t *add_tiny_header_data(framer_state *st, size_t len) { + ensure_space(st, len); + return gpr_slice_buffer_tiny_add(st->output, len); +} + +static void evict_entry(grpc_chttp2_hpack_compressor *c) { + c->tail_remote_index++; + GPR_ASSERT(c->tail_remote_index > 0); + GPR_ASSERT(c->table_size >= + c->table_elem_size[c->tail_remote_index % c->cap_table_elems]); + GPR_ASSERT(c->table_elems > 0); + c->table_size = + (uint16_t)(c->table_size - + c->table_elem_size[c->tail_remote_index % c->cap_table_elems]); + c->table_elems--; +} + +/* add an element to the decoder table */ +static void add_elem(grpc_chttp2_hpack_compressor *c, grpc_mdelem *elem) { + uint32_t key_hash = elem->key->hash; + uint32_t elem_hash = GRPC_MDSTR_KV_HASH(key_hash, elem->value->hash); + uint32_t new_index = c->tail_remote_index + c->table_elems + 1; + size_t elem_size = 32 + GPR_SLICE_LENGTH(elem->key->slice) + + GPR_SLICE_LENGTH(elem->value->slice); + + GPR_ASSERT(elem_size < 65536); + + if (elem_size > c->max_table_size) { + while (c->table_size > 0) { + evict_entry(c); + } + return; + } + + /* Reserve space for this element in the remote table: if this overflows + the current table, drop elements until it fits, matching the decompressor + algorithm */ + while (c->table_size + elem_size > c->max_table_size) { + evict_entry(c); + } + GPR_ASSERT(c->table_elems < c->max_table_size); + c->table_elem_size[new_index % c->cap_table_elems] = (uint16_t)elem_size; + c->table_size = (uint16_t)(c->table_size + elem_size); + c->table_elems++; + + /* Store this element into {entries,indices}_elem */ + if (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 (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 (c->entries_elems[HASH_FRAGMENT_2(elem_hash)] == NULL) { + /* 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 (c->entries_elems[HASH_FRAGMENT_3(elem_hash)] == NULL) { + /* 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(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(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) */ + + if (c->entries_keys[HASH_FRAGMENT_2(key_hash)] == elem->key) { + c->indices_keys[HASH_FRAGMENT_2(key_hash)] = new_index; + } else if (c->entries_keys[HASH_FRAGMENT_3(key_hash)] == elem->key) { + c->indices_keys[HASH_FRAGMENT_3(key_hash)] = new_index; + } else if (c->entries_keys[HASH_FRAGMENT_2(key_hash)] == NULL) { + c->entries_keys[HASH_FRAGMENT_2(key_hash)] = GRPC_MDSTR_REF(elem->key); + c->indices_keys[HASH_FRAGMENT_2(key_hash)] = new_index; + } else if (c->entries_keys[HASH_FRAGMENT_3(key_hash)] == NULL) { + c->entries_keys[HASH_FRAGMENT_3(key_hash)] = GRPC_MDSTR_REF(elem->key); + c->indices_keys[HASH_FRAGMENT_3(key_hash)] = new_index; + } else if (c->indices_keys[HASH_FRAGMENT_2(key_hash)] < + c->indices_keys[HASH_FRAGMENT_3(key_hash)]) { + GRPC_MDSTR_UNREF(c->entries_keys[HASH_FRAGMENT_2(key_hash)]); + c->entries_keys[HASH_FRAGMENT_2(key_hash)] = GRPC_MDSTR_REF(elem->key); + c->indices_keys[HASH_FRAGMENT_2(key_hash)] = new_index; + } else { + GRPC_MDSTR_UNREF(c->entries_keys[HASH_FRAGMENT_3(key_hash)]); + c->entries_keys[HASH_FRAGMENT_3(key_hash)] = GRPC_MDSTR_REF(elem->key); + c->indices_keys[HASH_FRAGMENT_3(key_hash)] = new_index; + } +} + +static void emit_indexed(grpc_chttp2_hpack_compressor *c, uint32_t elem_index, + framer_state *st) { + uint32_t len = GRPC_CHTTP2_VARINT_LENGTH(elem_index, 1); + GRPC_CHTTP2_WRITE_VARINT(elem_index, 1, 0x80, add_tiny_header_data(st, len), + len); +} + +static gpr_slice get_wire_value(grpc_mdelem *elem, uint8_t *huffman_prefix) { + if (grpc_is_binary_header((const char *)GPR_SLICE_START_PTR(elem->key->slice), + GPR_SLICE_LENGTH(elem->key->slice))) { + *huffman_prefix = 0x80; + return grpc_mdstr_as_base64_encoded_and_huffman_compressed(elem->value); + } + /* TODO(ctiller): opportunistically compress non-binary headers */ + *huffman_prefix = 0x00; + return elem->value->slice; +} + +static void emit_lithdr_incidx(grpc_chttp2_hpack_compressor *c, + uint32_t key_index, grpc_mdelem *elem, + framer_state *st) { + uint32_t len_pfx = GRPC_CHTTP2_VARINT_LENGTH(key_index, 2); + uint8_t huffman_prefix; + gpr_slice value_slice = get_wire_value(elem, &huffman_prefix); + size_t len_val = GPR_SLICE_LENGTH(value_slice); + uint32_t len_val_len; + GPR_ASSERT(len_val <= UINT32_MAX); + len_val_len = GRPC_CHTTP2_VARINT_LENGTH((uint32_t)len_val, 1); + GRPC_CHTTP2_WRITE_VARINT(key_index, 2, 0x40, + add_tiny_header_data(st, len_pfx), len_pfx); + GRPC_CHTTP2_WRITE_VARINT((uint32_t)len_val, 1, huffman_prefix, + add_tiny_header_data(st, len_val_len), len_val_len); + add_header_data(st, gpr_slice_ref(value_slice)); +} + +static void emit_lithdr_noidx(grpc_chttp2_hpack_compressor *c, + uint32_t key_index, grpc_mdelem *elem, + framer_state *st) { + uint32_t len_pfx = GRPC_CHTTP2_VARINT_LENGTH(key_index, 4); + uint8_t huffman_prefix; + gpr_slice value_slice = get_wire_value(elem, &huffman_prefix); + size_t len_val = GPR_SLICE_LENGTH(value_slice); + uint32_t len_val_len; + GPR_ASSERT(len_val <= UINT32_MAX); + len_val_len = GRPC_CHTTP2_VARINT_LENGTH((uint32_t)len_val, 1); + GRPC_CHTTP2_WRITE_VARINT(key_index, 4, 0x00, + add_tiny_header_data(st, len_pfx), len_pfx); + GRPC_CHTTP2_WRITE_VARINT((uint32_t)len_val, 1, huffman_prefix, + add_tiny_header_data(st, len_val_len), len_val_len); + add_header_data(st, gpr_slice_ref(value_slice)); +} + +static void emit_lithdr_incidx_v(grpc_chttp2_hpack_compressor *c, + grpc_mdelem *elem, framer_state *st) { + uint32_t len_key = (uint32_t)GPR_SLICE_LENGTH(elem->key->slice); + uint8_t huffman_prefix; + gpr_slice value_slice = get_wire_value(elem, &huffman_prefix); + uint32_t len_val = (uint32_t)GPR_SLICE_LENGTH(value_slice); + uint32_t len_key_len = GRPC_CHTTP2_VARINT_LENGTH(len_key, 1); + uint32_t len_val_len = GRPC_CHTTP2_VARINT_LENGTH(len_val, 1); + GPR_ASSERT(len_key <= UINT32_MAX); + GPR_ASSERT(GPR_SLICE_LENGTH(value_slice) <= UINT32_MAX); + *add_tiny_header_data(st, 1) = 0x40; + GRPC_CHTTP2_WRITE_VARINT(len_key, 1, 0x00, + add_tiny_header_data(st, len_key_len), len_key_len); + add_header_data(st, gpr_slice_ref(elem->key->slice)); + GRPC_CHTTP2_WRITE_VARINT(len_val, 1, huffman_prefix, + add_tiny_header_data(st, len_val_len), len_val_len); + add_header_data(st, gpr_slice_ref(value_slice)); +} + +static void emit_lithdr_noidx_v(grpc_chttp2_hpack_compressor *c, + grpc_mdelem *elem, framer_state *st) { + uint32_t len_key = (uint32_t)GPR_SLICE_LENGTH(elem->key->slice); + uint8_t huffman_prefix; + gpr_slice value_slice = get_wire_value(elem, &huffman_prefix); + uint32_t len_val = (uint32_t)GPR_SLICE_LENGTH(value_slice); + uint32_t len_key_len = GRPC_CHTTP2_VARINT_LENGTH(len_key, 1); + uint32_t len_val_len = GRPC_CHTTP2_VARINT_LENGTH(len_val, 1); + GPR_ASSERT(len_key <= UINT32_MAX); + GPR_ASSERT(GPR_SLICE_LENGTH(value_slice) <= UINT32_MAX); + *add_tiny_header_data(st, 1) = 0x00; + GRPC_CHTTP2_WRITE_VARINT(len_key, 1, 0x00, + add_tiny_header_data(st, len_key_len), len_key_len); + add_header_data(st, gpr_slice_ref(elem->key->slice)); + GRPC_CHTTP2_WRITE_VARINT(len_val, 1, huffman_prefix, + add_tiny_header_data(st, len_val_len), len_val_len); + add_header_data(st, gpr_slice_ref(value_slice)); +} + +static void emit_advertise_table_size_change(grpc_chttp2_hpack_compressor *c, + framer_state *st) { + uint32_t len = GRPC_CHTTP2_VARINT_LENGTH(c->max_table_size, 3); + GRPC_CHTTP2_WRITE_VARINT(c->max_table_size, 3, 0x20, + add_tiny_header_data(st, len), len); + c->advertise_table_size_change = 0; +} + +static uint32_t dynidx(grpc_chttp2_hpack_compressor *c, uint32_t elem_index) { + return 1 + GRPC_CHTTP2_LAST_STATIC_ENTRY + c->tail_remote_index + + c->table_elems - elem_index; +} + +/* encode an mdelem */ +static void hpack_enc(grpc_chttp2_hpack_compressor *c, grpc_mdelem *elem, + framer_state *st) { + uint32_t key_hash = elem->key->hash; + uint32_t elem_hash = GRPC_MDSTR_KV_HASH(key_hash, elem->value->hash); + size_t decoder_space_usage; + uint32_t indices_key; + int should_add_elem; + + GPR_ASSERT(GPR_SLICE_LENGTH(elem->key->slice) > 0); + if (GPR_SLICE_START_PTR(elem->key->slice)[0] != ':') { /* regular header */ + st->seen_regular_header = 1; + } else { + GPR_ASSERT( + st->seen_regular_header == 0 && + "Reserved header (colon-prefixed) happening after regular ones."); + } + + inc_filter(HASH_FRAGMENT_1(elem_hash), &c->filter_elems_sum, c->filter_elems); + + /* is this elem currently in the decoders table? */ + + if (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(c, dynidx(c, c->indices_elems[HASH_FRAGMENT_2(elem_hash)]), + st); + return; + } + + if (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(c, dynidx(c, c->indices_elems[HASH_FRAGMENT_3(elem_hash)]), + st); + return; + } + + /* should this elem be in the table? */ + decoder_space_usage = 32 + GPR_SLICE_LENGTH(elem->key->slice) + + GPR_SLICE_LENGTH(elem->value->slice); + should_add_elem = decoder_space_usage < MAX_DECODER_SPACE_USAGE && + c->filter_elems[HASH_FRAGMENT_1(elem_hash)] >= + c->filter_elems_sum / ONE_ON_ADD_PROBABILITY; + + /* no hits for the elem... maybe there's a key? */ + + indices_key = c->indices_keys[HASH_FRAGMENT_2(key_hash)]; + if (c->entries_keys[HASH_FRAGMENT_2(key_hash)] == elem->key && + indices_key > c->tail_remote_index) { + /* HIT: key (first cuckoo hash) */ + if (should_add_elem) { + emit_lithdr_incidx(c, dynidx(c, indices_key), elem, st); + add_elem(c, elem); + return; + } else { + emit_lithdr_noidx(c, dynidx(c, indices_key), elem, st); + return; + } + GPR_UNREACHABLE_CODE(return ); + } + + indices_key = c->indices_keys[HASH_FRAGMENT_3(key_hash)]; + if (c->entries_keys[HASH_FRAGMENT_3(key_hash)] == elem->key && + indices_key > c->tail_remote_index) { + /* HIT: key (first cuckoo hash) */ + if (should_add_elem) { + emit_lithdr_incidx(c, dynidx(c, indices_key), elem, st); + add_elem(c, elem); + return; + } else { + emit_lithdr_noidx(c, dynidx(c, indices_key), elem, st); + return; + } + GPR_UNREACHABLE_CODE(return ); + } + + /* no elem, key in the table... fall back to literal emission */ + + if (should_add_elem) { + emit_lithdr_incidx_v(c, elem, st); + add_elem(c, elem); + return; + } else { + emit_lithdr_noidx_v(c, elem, st); + return; + } + GPR_UNREACHABLE_CODE(return ); +} + +#define STRLEN_LIT(x) (sizeof(x) - 1) +#define TIMEOUT_KEY "grpc-timeout" + +static void deadline_enc(grpc_chttp2_hpack_compressor *c, gpr_timespec deadline, + framer_state *st) { + char timeout_str[GRPC_CHTTP2_TIMEOUT_ENCODE_MIN_BUFSIZE]; + grpc_mdelem *mdelem; + grpc_chttp2_encode_timeout( + gpr_time_sub(deadline, gpr_now(deadline.clock_type)), timeout_str); + mdelem = grpc_mdelem_from_metadata_strings( + GRPC_MDSTR_GRPC_TIMEOUT, grpc_mdstr_from_string(timeout_str)); + hpack_enc(c, mdelem, st); + GRPC_MDELEM_UNREF(mdelem); +} + +static uint32_t elems_for_bytes(uint32_t bytes) { return (bytes + 31) / 32; } + +void grpc_chttp2_hpack_compressor_init(grpc_chttp2_hpack_compressor *c) { + memset(c, 0, sizeof(*c)); + c->max_table_size = GRPC_CHTTP2_HPACKC_INITIAL_TABLE_SIZE; + c->cap_table_elems = elems_for_bytes(c->max_table_size); + c->max_table_elems = c->cap_table_elems; + c->max_usable_size = GRPC_CHTTP2_HPACKC_INITIAL_TABLE_SIZE; + c->table_elem_size = + gpr_malloc(sizeof(*c->table_elem_size) * c->cap_table_elems); + memset(c->table_elem_size, 0, + sizeof(*c->table_elem_size) * c->cap_table_elems); +} + +void grpc_chttp2_hpack_compressor_destroy(grpc_chttp2_hpack_compressor *c) { + int i; + for (i = 0; i < GRPC_CHTTP2_HPACKC_NUM_VALUES; i++) { + if (c->entries_keys[i]) GRPC_MDSTR_UNREF(c->entries_keys[i]); + if (c->entries_elems[i]) GRPC_MDELEM_UNREF(c->entries_elems[i]); + } + gpr_free(c->table_elem_size); +} + +void grpc_chttp2_hpack_compressor_set_max_usable_size( + grpc_chttp2_hpack_compressor *c, uint32_t max_table_size) { + c->max_usable_size = max_table_size; + grpc_chttp2_hpack_compressor_set_max_table_size( + c, GPR_MIN(c->max_table_size, max_table_size)); +} + +static void rebuild_elems(grpc_chttp2_hpack_compressor *c, uint32_t new_cap) { + uint16_t *table_elem_size = gpr_malloc(sizeof(*table_elem_size) * new_cap); + uint32_t i; + + memset(table_elem_size, 0, sizeof(*table_elem_size) * new_cap); + GPR_ASSERT(c->table_elems <= new_cap); + + for (i = 0; i < c->table_elems; i++) { + uint32_t ofs = c->tail_remote_index + i + 1; + table_elem_size[ofs % new_cap] = + c->table_elem_size[ofs % c->cap_table_elems]; + } + + c->cap_table_elems = new_cap; + gpr_free(c->table_elem_size); + c->table_elem_size = table_elem_size; +} + +void grpc_chttp2_hpack_compressor_set_max_table_size( + grpc_chttp2_hpack_compressor *c, uint32_t max_table_size) { + max_table_size = GPR_MIN(max_table_size, c->max_usable_size); + if (max_table_size == c->max_table_size) { + return; + } + while (c->table_size > 0 && c->table_size > max_table_size) { + evict_entry(c); + } + c->max_table_size = max_table_size; + c->max_table_elems = elems_for_bytes(max_table_size); + if (c->max_table_elems > c->cap_table_elems) { + rebuild_elems(c, GPR_MAX(c->max_table_elems, 2 * c->cap_table_elems)); + } else if (c->max_table_elems < c->cap_table_elems / 3) { + uint32_t new_cap = GPR_MAX(c->max_table_elems, 16); + if (new_cap != c->cap_table_elems) { + rebuild_elems(c, new_cap); + } + } + c->advertise_table_size_change = 1; + gpr_log(GPR_DEBUG, "set max table size from encoder to %d", max_table_size); +} + +void grpc_chttp2_encode_header(grpc_chttp2_hpack_compressor *c, + uint32_t stream_id, + grpc_metadata_batch *metadata, int is_eof, + gpr_slice_buffer *outbuf) { + framer_state st; + grpc_linked_mdelem *l; + gpr_timespec deadline; + + GPR_ASSERT(stream_id != 0); + + st.seen_regular_header = 0; + st.stream_id = stream_id; + st.output = outbuf; + st.is_first_frame = 1; + + /* Encode a metadata batch; store the returned values, representing + a metadata element that needs to be unreffed back into the metadata + slot. THIS MAY NOT BE THE SAME ELEMENT (if a decoder table slot got + updated). After this loop, we'll do a batch unref of elements. */ + begin_frame(&st); + if (c->advertise_table_size_change != 0) { + emit_advertise_table_size_change(c, &st); + } + grpc_metadata_batch_assert_ok(metadata); + for (l = metadata->list.head; l; l = l->next) { + hpack_enc(c, l->md, &st); + } + deadline = metadata->deadline; + if (gpr_time_cmp(deadline, gpr_inf_future(deadline.clock_type)) != 0) { + deadline_enc(c, deadline, &st); + } + + finish_frame(&st, 1, is_eof); +} diff --git a/src/core/lib/transport/chttp2/hpack_encoder.h b/src/core/lib/transport/chttp2/hpack_encoder.h new file mode 100644 index 0000000000..de46a8f146 --- /dev/null +++ b/src/core/lib/transport/chttp2/hpack_encoder.h @@ -0,0 +1,95 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CHTTP2_HPACK_ENCODER_H +#define GRPC_CORE_LIB_TRANSPORT_CHTTP2_HPACK_ENCODER_H + +#include <grpc/support/port_platform.h> +#include <grpc/support/slice.h> +#include <grpc/support/slice_buffer.h> +#include "src/core/lib/transport/chttp2/frame.h" +#include "src/core/lib/transport/metadata.h" +#include "src/core/lib/transport/metadata_batch.h" + +#define GRPC_CHTTP2_HPACKC_NUM_FILTERS 256 +#define GRPC_CHTTP2_HPACKC_NUM_VALUES 256 +/* initial table size, per spec */ +#define GRPC_CHTTP2_HPACKC_INITIAL_TABLE_SIZE 4096 +/* maximum table size we'll actually use */ +#define GRPC_CHTTP2_HPACKC_MAX_TABLE_SIZE (1024 * 1024) + +typedef struct { + uint32_t filter_elems_sum; + uint32_t max_table_size; + uint32_t max_table_elems; + uint32_t cap_table_elems; + /** if non-zero, advertise to the decoder that we'll start using a table + of this size */ + uint8_t advertise_table_size_change; + /** maximum number of bytes we'll use for the decode table (to guard against + peers ooming us by setting decode table size high) */ + uint32_t max_usable_size; + /* one before the lowest usable table index */ + uint32_t tail_remote_index; + uint32_t table_size; + uint32_t table_elems; + + /* filter tables for elems: this tables provides an approximate + popularity count for particular hashes, and are used to determine whether + a new literal should be added to the compression table or not. + They track a single integer that counts how often a particular value has + been seen. When that count reaches max (255), all values are halved. */ + uint8_t filter_elems[GRPC_CHTTP2_HPACKC_NUM_FILTERS]; + + /* entry tables for keys & elems: these tables track values that have been + seen and *may* be in the decompressor table */ + grpc_mdstr *entries_keys[GRPC_CHTTP2_HPACKC_NUM_VALUES]; + grpc_mdelem *entries_elems[GRPC_CHTTP2_HPACKC_NUM_VALUES]; + uint32_t indices_keys[GRPC_CHTTP2_HPACKC_NUM_VALUES]; + uint32_t indices_elems[GRPC_CHTTP2_HPACKC_NUM_VALUES]; + + uint16_t *table_elem_size; +} grpc_chttp2_hpack_compressor; + +void grpc_chttp2_hpack_compressor_init(grpc_chttp2_hpack_compressor *c); +void grpc_chttp2_hpack_compressor_destroy(grpc_chttp2_hpack_compressor *c); +void grpc_chttp2_hpack_compressor_set_max_table_size( + grpc_chttp2_hpack_compressor *c, uint32_t max_table_size); +void grpc_chttp2_hpack_compressor_set_max_usable_size( + grpc_chttp2_hpack_compressor *c, uint32_t max_table_size); + +void grpc_chttp2_encode_header(grpc_chttp2_hpack_compressor *c, uint32_t id, + grpc_metadata_batch *metadata, int is_eof, + gpr_slice_buffer *outbuf); + +#endif /* GRPC_CORE_LIB_TRANSPORT_CHTTP2_HPACK_ENCODER_H */ diff --git a/src/core/lib/transport/chttp2/hpack_parser.c b/src/core/lib/transport/chttp2/hpack_parser.c new file mode 100644 index 0000000000..d41ebab147 --- /dev/null +++ b/src/core/lib/transport/chttp2/hpack_parser.c @@ -0,0 +1,1449 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/chttp2/hpack_parser.h" +#include "src/core/lib/transport/chttp2/internal.h" + +#include <assert.h> +#include <stddef.h> +#include <string.h> + +/* This is here for grpc_is_binary_header + * TODO(murgatroid99): Remove this + */ +#include <grpc/grpc.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/port_platform.h> +#include <grpc/support/useful.h> + +#include "src/core/lib/profiling/timers.h" +#include "src/core/lib/support/string.h" +#include "src/core/lib/transport/chttp2/bin_encoder.h" + +typedef enum { + NOT_BINARY, + B64_BYTE0, + B64_BYTE1, + B64_BYTE2, + B64_BYTE3 +} binary_state; + +/* How parsing works: + + The parser object keeps track of a function pointer which represents the + current parse state. + + Each time new bytes are presented, we call into the current state, which + recursively parses until all bytes in the given chunk are exhausted. + + The parse state that terminates then saves its function pointer to be the + current state so that it can resume when more bytes are available. + + It's expected that most optimizing compilers will turn this code into + a set of indirect jumps, and so not waste stack space. */ + +/* forward declarations for parsing states */ +static int parse_begin(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end); +static int parse_error(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end); +static int parse_illegal_op(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end); + +static int parse_string_prefix(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end); +static int parse_key_string(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end); +static int parse_value_string_with_indexed_key(grpc_chttp2_hpack_parser *p, + const uint8_t *cur, + const uint8_t *end); +static int parse_value_string_with_literal_key(grpc_chttp2_hpack_parser *p, + const uint8_t *cur, + const uint8_t *end); + +static int parse_value0(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end); +static int parse_value1(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end); +static int parse_value2(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end); +static int parse_value3(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end); +static int parse_value4(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end); +static int parse_value5up(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end); + +static int parse_indexed_field(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end); +static int parse_indexed_field_x(grpc_chttp2_hpack_parser *p, + const uint8_t *cur, const uint8_t *end); +static int parse_lithdr_incidx(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end); +static int parse_lithdr_incidx_x(grpc_chttp2_hpack_parser *p, + const uint8_t *cur, const uint8_t *end); +static int parse_lithdr_incidx_v(grpc_chttp2_hpack_parser *p, + const uint8_t *cur, const uint8_t *end); +static int parse_lithdr_notidx(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end); +static int parse_lithdr_notidx_x(grpc_chttp2_hpack_parser *p, + const uint8_t *cur, const uint8_t *end); +static int parse_lithdr_notidx_v(grpc_chttp2_hpack_parser *p, + const uint8_t *cur, const uint8_t *end); +static int parse_lithdr_nvridx(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end); +static int parse_lithdr_nvridx_x(grpc_chttp2_hpack_parser *p, + const uint8_t *cur, const uint8_t *end); +static int parse_lithdr_nvridx_v(grpc_chttp2_hpack_parser *p, + const uint8_t *cur, const uint8_t *end); +static int parse_max_tbl_size(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end); +static int parse_max_tbl_size_x(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end); + +/* we translate the first byte of a hpack field into one of these decoding + cases, then use a lookup table to jump directly to the appropriate parser. + + _X => the integer index is all ones, meaning we need to do varint decoding + _V => the integer index is all zeros, meaning we need to decode an additional + string value */ +typedef enum { + INDEXED_FIELD, + INDEXED_FIELD_X, + LITHDR_INCIDX, + LITHDR_INCIDX_X, + LITHDR_INCIDX_V, + LITHDR_NOTIDX, + LITHDR_NOTIDX_X, + LITHDR_NOTIDX_V, + LITHDR_NVRIDX, + LITHDR_NVRIDX_X, + LITHDR_NVRIDX_V, + MAX_TBL_SIZE, + MAX_TBL_SIZE_X, + ILLEGAL +} first_byte_type; + +/* jump table of parse state functions -- order must match first_byte_type + above */ +static const grpc_chttp2_hpack_parser_state first_byte_action[] = { + parse_indexed_field, parse_indexed_field_x, parse_lithdr_incidx, + parse_lithdr_incidx_x, parse_lithdr_incidx_v, parse_lithdr_notidx, + parse_lithdr_notidx_x, parse_lithdr_notidx_v, parse_lithdr_nvridx, + parse_lithdr_nvridx_x, parse_lithdr_nvridx_v, parse_max_tbl_size, + parse_max_tbl_size_x, parse_illegal_op}; + +/* indexes the first byte to a parse state function - generated by + gen_hpack_tables.c */ +static const uint8_t first_byte_lut[256] = { + LITHDR_NOTIDX_V, LITHDR_NOTIDX, LITHDR_NOTIDX, LITHDR_NOTIDX, + LITHDR_NOTIDX, LITHDR_NOTIDX, LITHDR_NOTIDX, LITHDR_NOTIDX, + LITHDR_NOTIDX, LITHDR_NOTIDX, LITHDR_NOTIDX, LITHDR_NOTIDX, + LITHDR_NOTIDX, LITHDR_NOTIDX, LITHDR_NOTIDX, LITHDR_NOTIDX_X, + LITHDR_NVRIDX_V, LITHDR_NVRIDX, LITHDR_NVRIDX, LITHDR_NVRIDX, + LITHDR_NVRIDX, LITHDR_NVRIDX, LITHDR_NVRIDX, LITHDR_NVRIDX, + LITHDR_NVRIDX, LITHDR_NVRIDX, LITHDR_NVRIDX, LITHDR_NVRIDX, + LITHDR_NVRIDX, LITHDR_NVRIDX, LITHDR_NVRIDX, LITHDR_NVRIDX_X, + MAX_TBL_SIZE, MAX_TBL_SIZE, MAX_TBL_SIZE, MAX_TBL_SIZE, + MAX_TBL_SIZE, MAX_TBL_SIZE, MAX_TBL_SIZE, MAX_TBL_SIZE, + MAX_TBL_SIZE, MAX_TBL_SIZE, MAX_TBL_SIZE, MAX_TBL_SIZE, + MAX_TBL_SIZE, MAX_TBL_SIZE, MAX_TBL_SIZE, MAX_TBL_SIZE, + MAX_TBL_SIZE, MAX_TBL_SIZE, MAX_TBL_SIZE, MAX_TBL_SIZE, + MAX_TBL_SIZE, MAX_TBL_SIZE, MAX_TBL_SIZE, MAX_TBL_SIZE, + MAX_TBL_SIZE, MAX_TBL_SIZE, MAX_TBL_SIZE, MAX_TBL_SIZE, + MAX_TBL_SIZE, MAX_TBL_SIZE, MAX_TBL_SIZE, MAX_TBL_SIZE_X, + LITHDR_INCIDX_V, LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, + LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, + LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, + LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, + LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, + LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, + LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, + LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, + LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, + LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, + LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, + LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, + LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, + LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, + LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, + LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX, LITHDR_INCIDX_X, + ILLEGAL, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, + INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD, INDEXED_FIELD_X, +}; + +/* state table for huffman decoding: given a state, gives an index/16 into + next_sub_tbl. Taking that index and adding the value of the nibble being + considered returns the next state. + + generated by gen_hpack_tables.c */ +static const uint8_t next_tbl[256] = { + 0, 1, 2, 3, 4, 1, 2, 5, 6, 1, 7, 8, 1, 3, 3, 9, 10, 11, 1, 1, + 1, 12, 1, 2, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, + 14, 1, 15, 16, 1, 17, 1, 15, 2, 7, 3, 18, 19, 1, 1, 1, 1, 20, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 15, 2, 2, 7, 21, 1, 22, 1, 1, 1, 1, 1, + 1, 1, 1, 15, 2, 2, 2, 2, 2, 2, 23, 24, 25, 1, 1, 1, 1, 2, 2, 2, + 26, 3, 3, 27, 10, 28, 1, 1, 1, 1, 1, 1, 2, 3, 29, 10, 30, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 31, 1, 1, 1, 1, 1, 1, 1, 2, + 2, 2, 2, 2, 2, 2, 2, 32, 1, 1, 15, 33, 1, 34, 35, 9, 36, 1, 1, 1, + 1, 1, 1, 1, 37, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 26, 9, + 38, 1, 1, 1, 1, 1, 1, 1, 15, 2, 2, 2, 2, 26, 3, 3, 39, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 7, 3, 3, 3, 40, 2, + 41, 1, 1, 1, 42, 43, 1, 1, 44, 1, 1, 1, 1, 15, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 45, 46, 1, 1, 2, 2, 2, 35, 3, 3, 18, 47, 2, +}; + +/* next state, based upon current state and the current nibble: see above. + generated by gen_hpack_tables.c */ +static const int16_t next_sub_tbl[48 * 16] = { + 1, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, + 218, 2, 6, 10, 13, 14, 15, 16, 17, 2, 6, 10, 13, 14, 15, + 16, 17, 3, 7, 11, 24, 3, 7, 11, 24, 3, 7, 11, 24, 3, + 7, 11, 24, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, + 4, 8, 4, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, + 199, 200, 201, 202, 203, 4, 8, 4, 8, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 9, 133, 134, 135, 136, 137, 138, 139, 140, + 141, 142, 143, 144, 145, 146, 147, 3, 7, 11, 24, 3, 7, 11, 24, + 4, 8, 4, 8, 4, 8, 4, 8, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 12, 132, 4, 8, 4, 8, 4, 8, + 4, 8, 4, 8, 4, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 18, 19, 20, 21, 4, 8, 4, + 8, 4, 8, 4, 8, 4, 8, 0, 0, 0, 22, 23, 91, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 3, + 7, 11, 24, 3, 7, 11, 24, 0, 0, 0, 0, 0, 41, 42, 43, + 2, 6, 10, 13, 14, 15, 16, 17, 3, 7, 11, 24, 3, 7, 11, + 24, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 0, 0, + 44, 45, 2, 6, 10, 13, 14, 15, 16, 17, 46, 47, 48, 49, 50, + 51, 52, 57, 4, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 53, 54, 55, 56, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, + 68, 69, 70, 71, 72, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 73, 75, 76, 77, 78, 79, 80, 81, 82, + 83, 84, 85, 86, 87, 88, 89, 90, 3, 7, 11, 24, 3, 7, 11, + 24, 3, 7, 11, 24, 0, 0, 0, 0, 3, 7, 11, 24, 3, 7, + 11, 24, 4, 8, 4, 8, 0, 0, 0, 92, 0, 0, 0, 93, 94, + 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 3, 7, 11, 24, + 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, + 8, 4, 8, 4, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 4, + 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 0, 0, + 0, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, + 131, 2, 6, 10, 13, 14, 15, 16, 17, 4, 8, 4, 8, 4, 8, + 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 148, + 149, 150, 151, 3, 7, 11, 24, 4, 8, 4, 8, 0, 0, 0, 0, + 0, 0, 152, 153, 3, 7, 11, 24, 3, 7, 11, 24, 3, 7, 11, + 24, 154, 155, 156, 164, 3, 7, 11, 24, 3, 7, 11, 24, 3, 7, + 11, 24, 4, 8, 4, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 157, 158, 159, 160, 161, 162, 163, 165, 166, 167, 168, 169, 170, 171, 172, + 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, + 188, 189, 190, 191, 192, 193, 194, 195, 196, 4, 8, 4, 8, 4, 8, + 4, 8, 4, 8, 4, 8, 4, 8, 197, 198, 4, 8, 4, 8, 4, + 8, 4, 8, 0, 0, 0, 0, 0, 0, 219, 220, 3, 7, 11, 24, + 4, 8, 4, 8, 4, 8, 0, 0, 221, 222, 223, 224, 3, 7, 11, + 24, 3, 7, 11, 24, 4, 8, 4, 8, 4, 8, 225, 228, 4, 8, + 4, 8, 4, 8, 0, 0, 0, 0, 0, 0, 0, 0, 226, 227, 229, + 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, + 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 245, 246, 247, 248, 249, 250, 251, 252, + 253, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 255, +}; + +/* emission table: indexed like next_tbl, ultimately gives the byte to be + emitted, or -1 for no byte, or 256 for end of stream + + generated by gen_hpack_tables.c */ +static const uint16_t emit_tbl[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 0, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 0, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 0, 55, 56, + 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 0, + 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, + 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, + 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, + 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, + 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, + 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 0, + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, + 0, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, + 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, + 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, + 219, 220, 221, 0, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, + 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, + 248, +}; + +/* generated by gen_hpack_tables.c */ +static const int16_t emit_sub_tbl[249 * 16] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 48, 48, 48, 48, 48, 48, 48, 48, 49, 49, 49, 49, 49, 49, + 49, 49, 48, 48, 48, 48, 49, 49, 49, 49, 50, 50, 50, 50, 97, + 97, 97, 97, 48, 48, 49, 49, 50, 50, 97, 97, 99, 99, 101, 101, + 105, 105, 111, 111, 48, 49, 50, 97, 99, 101, 105, 111, 115, 116, -1, + -1, -1, -1, -1, -1, 32, 32, 32, 32, 32, 32, 32, 32, 37, 37, + 37, 37, 37, 37, 37, 37, 99, 99, 99, 99, 101, 101, 101, 101, 105, + 105, 105, 105, 111, 111, 111, 111, 115, 115, 116, 116, 32, 37, 45, 46, + 47, 51, 52, 53, 54, 55, 56, 57, 61, 61, 61, 61, 61, 61, 61, + 61, 65, 65, 65, 65, 65, 65, 65, 65, 115, 115, 115, 115, 116, 116, + 116, 116, 32, 32, 37, 37, 45, 45, 46, 46, 61, 65, 95, 98, 100, + 102, 103, 104, 108, 109, 110, 112, 114, 117, -1, -1, 58, 58, 58, 58, + 58, 58, 58, 58, 66, 66, 66, 66, 66, 66, 66, 66, 47, 47, 51, + 51, 52, 52, 53, 53, 54, 54, 55, 55, 56, 56, 57, 57, 61, 61, + 65, 65, 95, 95, 98, 98, 100, 100, 102, 102, 103, 103, 104, 104, 108, + 108, 109, 109, 110, 110, 112, 112, 114, 114, 117, 117, 58, 66, 67, 68, + 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 89, 106, 107, 113, 118, 119, 120, 121, 122, -1, -1, + -1, -1, 38, 38, 38, 38, 38, 38, 38, 38, 42, 42, 42, 42, 42, + 42, 42, 42, 44, 44, 44, 44, 44, 44, 44, 44, 59, 59, 59, 59, + 59, 59, 59, 59, 88, 88, 88, 88, 88, 88, 88, 88, 90, 90, 90, + 90, 90, 90, 90, 90, 33, 33, 34, 34, 40, 40, 41, 41, 63, 63, + 39, 43, 124, -1, -1, -1, 35, 35, 35, 35, 35, 35, 35, 35, 62, + 62, 62, 62, 62, 62, 62, 62, 0, 0, 0, 0, 36, 36, 36, 36, + 64, 64, 64, 64, 91, 91, 91, 91, 69, 69, 69, 69, 69, 69, 69, + 69, 70, 70, 70, 70, 70, 70, 70, 70, 71, 71, 71, 71, 71, 71, + 71, 71, 72, 72, 72, 72, 72, 72, 72, 72, 73, 73, 73, 73, 73, + 73, 73, 73, 74, 74, 74, 74, 74, 74, 74, 74, 75, 75, 75, 75, + 75, 75, 75, 75, 76, 76, 76, 76, 76, 76, 76, 76, 77, 77, 77, + 77, 77, 77, 77, 77, 78, 78, 78, 78, 78, 78, 78, 78, 79, 79, + 79, 79, 79, 79, 79, 79, 80, 80, 80, 80, 80, 80, 80, 80, 81, + 81, 81, 81, 81, 81, 81, 81, 82, 82, 82, 82, 82, 82, 82, 82, + 83, 83, 83, 83, 83, 83, 83, 83, 84, 84, 84, 84, 84, 84, 84, + 84, 85, 85, 85, 85, 85, 85, 85, 85, 86, 86, 86, 86, 86, 86, + 86, 86, 87, 87, 87, 87, 87, 87, 87, 87, 89, 89, 89, 89, 89, + 89, 89, 89, 106, 106, 106, 106, 106, 106, 106, 106, 107, 107, 107, 107, + 107, 107, 107, 107, 113, 113, 113, 113, 113, 113, 113, 113, 118, 118, 118, + 118, 118, 118, 118, 118, 119, 119, 119, 119, 119, 119, 119, 119, 120, 120, + 120, 120, 120, 120, 120, 120, 121, 121, 121, 121, 121, 121, 121, 121, 122, + 122, 122, 122, 122, 122, 122, 122, 38, 38, 38, 38, 42, 42, 42, 42, + 44, 44, 44, 44, 59, 59, 59, 59, 88, 88, 88, 88, 90, 90, 90, + 90, 33, 34, 40, 41, 63, -1, -1, -1, 39, 39, 39, 39, 39, 39, + 39, 39, 43, 43, 43, 43, 43, 43, 43, 43, 124, 124, 124, 124, 124, + 124, 124, 124, 35, 35, 35, 35, 62, 62, 62, 62, 0, 0, 36, 36, + 64, 64, 91, 91, 93, 93, 126, 126, 94, 125, -1, -1, 60, 60, 60, + 60, 60, 60, 60, 60, 96, 96, 96, 96, 96, 96, 96, 96, 123, 123, + 123, 123, 123, 123, 123, 123, -1, -1, -1, -1, -1, -1, -1, -1, 92, + 92, 92, 92, 92, 92, 92, 92, 195, 195, 195, 195, 195, 195, 195, 195, + 208, 208, 208, 208, 208, 208, 208, 208, 128, 128, 128, 128, 130, 130, 130, + 130, 131, 131, 131, 131, 162, 162, 162, 162, 184, 184, 184, 184, 194, 194, + 194, 194, 224, 224, 224, 224, 226, 226, 226, 226, 153, 153, 161, 161, 167, + 167, 172, 172, 176, 176, 177, 177, 179, 179, 209, 209, 216, 216, 217, 217, + 227, 227, 229, 229, 230, 230, 129, 132, 133, 134, 136, 146, 154, 156, 160, + 163, 164, 169, 170, 173, 178, 181, 185, 186, 187, 189, 190, 196, 198, 228, + 232, 233, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 135, + 135, 135, 135, 135, 135, 135, 135, 137, 137, 137, 137, 137, 137, 137, 137, + 138, 138, 138, 138, 138, 138, 138, 138, 139, 139, 139, 139, 139, 139, 139, + 139, 140, 140, 140, 140, 140, 140, 140, 140, 141, 141, 141, 141, 141, 141, + 141, 141, 143, 143, 143, 143, 143, 143, 143, 143, 147, 147, 147, 147, 147, + 147, 147, 147, 149, 149, 149, 149, 149, 149, 149, 149, 150, 150, 150, 150, + 150, 150, 150, 150, 151, 151, 151, 151, 151, 151, 151, 151, 152, 152, 152, + 152, 152, 152, 152, 152, 155, 155, 155, 155, 155, 155, 155, 155, 157, 157, + 157, 157, 157, 157, 157, 157, 158, 158, 158, 158, 158, 158, 158, 158, 165, + 165, 165, 165, 165, 165, 165, 165, 166, 166, 166, 166, 166, 166, 166, 166, + 168, 168, 168, 168, 168, 168, 168, 168, 174, 174, 174, 174, 174, 174, 174, + 174, 175, 175, 175, 175, 175, 175, 175, 175, 180, 180, 180, 180, 180, 180, + 180, 180, 182, 182, 182, 182, 182, 182, 182, 182, 183, 183, 183, 183, 183, + 183, 183, 183, 188, 188, 188, 188, 188, 188, 188, 188, 191, 191, 191, 191, + 191, 191, 191, 191, 197, 197, 197, 197, 197, 197, 197, 197, 231, 231, 231, + 231, 231, 231, 231, 231, 239, 239, 239, 239, 239, 239, 239, 239, 9, 9, + 9, 9, 142, 142, 142, 142, 144, 144, 144, 144, 145, 145, 145, 145, 148, + 148, 148, 148, 159, 159, 159, 159, 171, 171, 171, 171, 206, 206, 206, 206, + 215, 215, 215, 215, 225, 225, 225, 225, 236, 236, 236, 236, 237, 237, 237, + 237, 199, 199, 207, 207, 234, 234, 235, 235, 192, 193, 200, 201, 202, 205, + 210, 213, 218, 219, 238, 240, 242, 243, 255, -1, 203, 203, 203, 203, 203, + 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 211, 211, 211, 211, + 211, 211, 211, 211, 212, 212, 212, 212, 212, 212, 212, 212, 214, 214, 214, + 214, 214, 214, 214, 214, 221, 221, 221, 221, 221, 221, 221, 221, 222, 222, + 222, 222, 222, 222, 222, 222, 223, 223, 223, 223, 223, 223, 223, 223, 241, + 241, 241, 241, 241, 241, 241, 241, 244, 244, 244, 244, 244, 244, 244, 244, + 245, 245, 245, 245, 245, 245, 245, 245, 246, 246, 246, 246, 246, 246, 246, + 246, 247, 247, 247, 247, 247, 247, 247, 247, 248, 248, 248, 248, 248, 248, + 248, 248, 250, 250, 250, 250, 250, 250, 250, 250, 251, 251, 251, 251, 251, + 251, 251, 251, 252, 252, 252, 252, 252, 252, 252, 252, 253, 253, 253, 253, + 253, 253, 253, 253, 254, 254, 254, 254, 254, 254, 254, 254, 2, 2, 2, + 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, + 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 11, 11, 11, 11, 12, + 12, 12, 12, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, + 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, + 20, 21, 21, 21, 21, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, + 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, + 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31, 127, 127, 127, 127, + 220, 220, 220, 220, 249, 249, 249, 249, 10, 13, 22, 256, 93, 93, 93, + 93, 126, 126, 126, 126, 94, 94, 125, 125, 60, 96, 123, -1, 92, 195, + 208, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 128, + 128, 128, 128, 128, 128, 128, 128, 130, 130, 130, 130, 130, 130, 130, 130, + 131, 131, 131, 131, 131, 131, 131, 131, 162, 162, 162, 162, 162, 162, 162, + 162, 184, 184, 184, 184, 184, 184, 184, 184, 194, 194, 194, 194, 194, 194, + 194, 194, 224, 224, 224, 224, 224, 224, 224, 224, 226, 226, 226, 226, 226, + 226, 226, 226, 153, 153, 153, 153, 161, 161, 161, 161, 167, 167, 167, 167, + 172, 172, 172, 172, 176, 176, 176, 176, 177, 177, 177, 177, 179, 179, 179, + 179, 209, 209, 209, 209, 216, 216, 216, 216, 217, 217, 217, 217, 227, 227, + 227, 227, 229, 229, 229, 229, 230, 230, 230, 230, 129, 129, 132, 132, 133, + 133, 134, 134, 136, 136, 146, 146, 154, 154, 156, 156, 160, 160, 163, 163, + 164, 164, 169, 169, 170, 170, 173, 173, 178, 178, 181, 181, 185, 185, 186, + 186, 187, 187, 189, 189, 190, 190, 196, 196, 198, 198, 228, 228, 232, 232, + 233, 233, 1, 135, 137, 138, 139, 140, 141, 143, 147, 149, 150, 151, 152, + 155, 157, 158, 165, 166, 168, 174, 175, 180, 182, 183, 188, 191, 197, 231, + 239, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 9, 9, 9, + 9, 9, 9, 9, 9, 142, 142, 142, 142, 142, 142, 142, 142, 144, 144, + 144, 144, 144, 144, 144, 144, 145, 145, 145, 145, 145, 145, 145, 145, 148, + 148, 148, 148, 148, 148, 148, 148, 159, 159, 159, 159, 159, 159, 159, 159, + 171, 171, 171, 171, 171, 171, 171, 171, 206, 206, 206, 206, 206, 206, 206, + 206, 215, 215, 215, 215, 215, 215, 215, 215, 225, 225, 225, 225, 225, 225, + 225, 225, 236, 236, 236, 236, 236, 236, 236, 236, 237, 237, 237, 237, 237, + 237, 237, 237, 199, 199, 199, 199, 207, 207, 207, 207, 234, 234, 234, 234, + 235, 235, 235, 235, 192, 192, 193, 193, 200, 200, 201, 201, 202, 202, 205, + 205, 210, 210, 213, 213, 218, 218, 219, 219, 238, 238, 240, 240, 242, 242, + 243, 243, 255, 255, 203, 204, 211, 212, 214, 221, 222, 223, 241, 244, 245, + 246, 247, 248, 250, 251, 252, 253, 254, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 2, 2, 2, 2, 2, 2, 2, + 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, + 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, + 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, + 8, 8, 8, 8, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, + 12, 12, 12, 12, 12, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, + 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 17, + 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, + 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, + 20, 21, 21, 21, 21, 21, 21, 21, 21, 23, 23, 23, 23, 23, 23, + 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, + 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, + 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, + 29, 29, 29, 29, 29, 30, 30, 30, 30, 30, 30, 30, 30, 31, 31, + 31, 31, 31, 31, 31, 31, 127, 127, 127, 127, 127, 127, 127, 127, 220, + 220, 220, 220, 220, 220, 220, 220, 249, 249, 249, 249, 249, 249, 249, 249, + 10, 10, 13, 13, 22, 22, 256, 256, 67, 67, 67, 67, 67, 67, 67, + 67, 68, 68, 68, 68, 68, 68, 68, 68, 95, 95, 95, 95, 95, 95, + 95, 95, 98, 98, 98, 98, 98, 98, 98, 98, 100, 100, 100, 100, 100, + 100, 100, 100, 102, 102, 102, 102, 102, 102, 102, 102, 103, 103, 103, 103, + 103, 103, 103, 103, 104, 104, 104, 104, 104, 104, 104, 104, 108, 108, 108, + 108, 108, 108, 108, 108, 109, 109, 109, 109, 109, 109, 109, 109, 110, 110, + 110, 110, 110, 110, 110, 110, 112, 112, 112, 112, 112, 112, 112, 112, 114, + 114, 114, 114, 114, 114, 114, 114, 117, 117, 117, 117, 117, 117, 117, 117, + 58, 58, 58, 58, 66, 66, 66, 66, 67, 67, 67, 67, 68, 68, 68, + 68, 69, 69, 69, 69, 70, 70, 70, 70, 71, 71, 71, 71, 72, 72, + 72, 72, 73, 73, 73, 73, 74, 74, 74, 74, 75, 75, 75, 75, 76, + 76, 76, 76, 77, 77, 77, 77, 78, 78, 78, 78, 79, 79, 79, 79, + 80, 80, 80, 80, 81, 81, 81, 81, 82, 82, 82, 82, 83, 83, 83, + 83, 84, 84, 84, 84, 85, 85, 85, 85, 86, 86, 86, 86, 87, 87, + 87, 87, 89, 89, 89, 89, 106, 106, 106, 106, 107, 107, 107, 107, 113, + 113, 113, 113, 118, 118, 118, 118, 119, 119, 119, 119, 120, 120, 120, 120, + 121, 121, 121, 121, 122, 122, 122, 122, 38, 38, 42, 42, 44, 44, 59, + 59, 88, 88, 90, 90, -1, -1, -1, -1, 33, 33, 33, 33, 33, 33, + 33, 33, 34, 34, 34, 34, 34, 34, 34, 34, 40, 40, 40, 40, 40, + 40, 40, 40, 41, 41, 41, 41, 41, 41, 41, 41, 63, 63, 63, 63, + 63, 63, 63, 63, 39, 39, 39, 39, 43, 43, 43, 43, 124, 124, 124, + 124, 35, 35, 62, 62, 0, 36, 64, 91, 93, 126, -1, -1, 94, 94, + 94, 94, 94, 94, 94, 94, 125, 125, 125, 125, 125, 125, 125, 125, 60, + 60, 60, 60, 96, 96, 96, 96, 123, 123, 123, 123, -1, -1, -1, -1, + 92, 92, 92, 92, 195, 195, 195, 195, 208, 208, 208, 208, 128, 128, 130, + 130, 131, 131, 162, 162, 184, 184, 194, 194, 224, 224, 226, 226, 153, 161, + 167, 172, 176, 177, 179, 209, 216, 217, 227, 229, 230, -1, -1, -1, -1, + -1, -1, -1, 129, 129, 129, 129, 129, 129, 129, 129, 132, 132, 132, 132, + 132, 132, 132, 132, 133, 133, 133, 133, 133, 133, 133, 133, 134, 134, 134, + 134, 134, 134, 134, 134, 136, 136, 136, 136, 136, 136, 136, 136, 146, 146, + 146, 146, 146, 146, 146, 146, 154, 154, 154, 154, 154, 154, 154, 154, 156, + 156, 156, 156, 156, 156, 156, 156, 160, 160, 160, 160, 160, 160, 160, 160, + 163, 163, 163, 163, 163, 163, 163, 163, 164, 164, 164, 164, 164, 164, 164, + 164, 169, 169, 169, 169, 169, 169, 169, 169, 170, 170, 170, 170, 170, 170, + 170, 170, 173, 173, 173, 173, 173, 173, 173, 173, 178, 178, 178, 178, 178, + 178, 178, 178, 181, 181, 181, 181, 181, 181, 181, 181, 185, 185, 185, 185, + 185, 185, 185, 185, 186, 186, 186, 186, 186, 186, 186, 186, 187, 187, 187, + 187, 187, 187, 187, 187, 189, 189, 189, 189, 189, 189, 189, 189, 190, 190, + 190, 190, 190, 190, 190, 190, 196, 196, 196, 196, 196, 196, 196, 196, 198, + 198, 198, 198, 198, 198, 198, 198, 228, 228, 228, 228, 228, 228, 228, 228, + 232, 232, 232, 232, 232, 232, 232, 232, 233, 233, 233, 233, 233, 233, 233, + 233, 1, 1, 1, 1, 135, 135, 135, 135, 137, 137, 137, 137, 138, 138, + 138, 138, 139, 139, 139, 139, 140, 140, 140, 140, 141, 141, 141, 141, 143, + 143, 143, 143, 147, 147, 147, 147, 149, 149, 149, 149, 150, 150, 150, 150, + 151, 151, 151, 151, 152, 152, 152, 152, 155, 155, 155, 155, 157, 157, 157, + 157, 158, 158, 158, 158, 165, 165, 165, 165, 166, 166, 166, 166, 168, 168, + 168, 168, 174, 174, 174, 174, 175, 175, 175, 175, 180, 180, 180, 180, 182, + 182, 182, 182, 183, 183, 183, 183, 188, 188, 188, 188, 191, 191, 191, 191, + 197, 197, 197, 197, 231, 231, 231, 231, 239, 239, 239, 239, 9, 9, 142, + 142, 144, 144, 145, 145, 148, 148, 159, 159, 171, 171, 206, 206, 215, 215, + 225, 225, 236, 236, 237, 237, 199, 207, 234, 235, 192, 192, 192, 192, 192, + 192, 192, 192, 193, 193, 193, 193, 193, 193, 193, 193, 200, 200, 200, 200, + 200, 200, 200, 200, 201, 201, 201, 201, 201, 201, 201, 201, 202, 202, 202, + 202, 202, 202, 202, 202, 205, 205, 205, 205, 205, 205, 205, 205, 210, 210, + 210, 210, 210, 210, 210, 210, 213, 213, 213, 213, 213, 213, 213, 213, 218, + 218, 218, 218, 218, 218, 218, 218, 219, 219, 219, 219, 219, 219, 219, 219, + 238, 238, 238, 238, 238, 238, 238, 238, 240, 240, 240, 240, 240, 240, 240, + 240, 242, 242, 242, 242, 242, 242, 242, 242, 243, 243, 243, 243, 243, 243, + 243, 243, 255, 255, 255, 255, 255, 255, 255, 255, 203, 203, 203, 203, 204, + 204, 204, 204, 211, 211, 211, 211, 212, 212, 212, 212, 214, 214, 214, 214, + 221, 221, 221, 221, 222, 222, 222, 222, 223, 223, 223, 223, 241, 241, 241, + 241, 244, 244, 244, 244, 245, 245, 245, 245, 246, 246, 246, 246, 247, 247, + 247, 247, 248, 248, 248, 248, 250, 250, 250, 250, 251, 251, 251, 251, 252, + 252, 252, 252, 253, 253, 253, 253, 254, 254, 254, 254, 2, 2, 3, 3, + 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 11, 11, 12, 12, 14, + 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, + 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30, + 30, 31, 31, 127, 127, 220, 220, 249, 249, -1, -1, 10, 10, 10, 10, + 10, 10, 10, 10, 13, 13, 13, 13, 13, 13, 13, 13, 22, 22, 22, + 22, 22, 22, 22, 22, 256, 256, 256, 256, 256, 256, 256, 256, 45, 45, + 45, 45, 45, 45, 45, 45, 46, 46, 46, 46, 46, 46, 46, 46, 47, + 47, 47, 47, 47, 47, 47, 47, 51, 51, 51, 51, 51, 51, 51, 51, + 52, 52, 52, 52, 52, 52, 52, 52, 53, 53, 53, 53, 53, 53, 53, + 53, 54, 54, 54, 54, 54, 54, 54, 54, 55, 55, 55, 55, 55, 55, + 55, 55, 56, 56, 56, 56, 56, 56, 56, 56, 57, 57, 57, 57, 57, + 57, 57, 57, 50, 50, 50, 50, 50, 50, 50, 50, 97, 97, 97, 97, + 97, 97, 97, 97, 99, 99, 99, 99, 99, 99, 99, 99, 101, 101, 101, + 101, 101, 101, 101, 101, 105, 105, 105, 105, 105, 105, 105, 105, 111, 111, + 111, 111, 111, 111, 111, 111, 115, 115, 115, 115, 115, 115, 115, 115, 116, + 116, 116, 116, 116, 116, 116, 116, 32, 32, 32, 32, 37, 37, 37, 37, + 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 51, 51, 51, + 51, 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, 54, 55, 55, + 55, 55, 56, 56, 56, 56, 57, 57, 57, 57, 61, 61, 61, 61, 65, + 65, 65, 65, 95, 95, 95, 95, 98, 98, 98, 98, 100, 100, 100, 100, + 102, 102, 102, 102, 103, 103, 103, 103, 104, 104, 104, 104, 108, 108, 108, + 108, 109, 109, 109, 109, 110, 110, 110, 110, 112, 112, 112, 112, 114, 114, + 114, 114, 117, 117, 117, 117, 58, 58, 66, 66, 67, 67, 68, 68, 69, + 69, 70, 70, 71, 71, 72, 72, 73, 73, 74, 74, 75, 75, 76, 76, + 77, 77, 78, 78, 79, 79, 80, 80, 81, 81, 82, 82, 83, 83, 84, + 84, 85, 85, 86, 86, 87, 87, 89, 89, 106, 106, 107, 107, 113, 113, + 118, 118, 119, 119, 120, 120, 121, 121, 122, 122, 38, 42, 44, 59, 88, + 90, -1, -1, 33, 33, 33, 33, 34, 34, 34, 34, 40, 40, 40, 40, + 41, 41, 41, 41, 63, 63, 63, 63, 39, 39, 43, 43, 124, 124, 35, + 62, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 36, 36, + 36, 36, 36, 36, 36, 36, 64, 64, 64, 64, 64, 64, 64, 64, 91, + 91, 91, 91, 91, 91, 91, 91, 93, 93, 93, 93, 93, 93, 93, 93, + 126, 126, 126, 126, 126, 126, 126, 126, 94, 94, 94, 94, 125, 125, 125, + 125, 60, 60, 96, 96, 123, 123, -1, -1, 92, 92, 195, 195, 208, 208, + 128, 130, 131, 162, 184, 194, 224, 226, -1, -1, 153, 153, 153, 153, 153, + 153, 153, 153, 161, 161, 161, 161, 161, 161, 161, 161, 167, 167, 167, 167, + 167, 167, 167, 167, 172, 172, 172, 172, 172, 172, 172, 172, 176, 176, 176, + 176, 176, 176, 176, 176, 177, 177, 177, 177, 177, 177, 177, 177, 179, 179, + 179, 179, 179, 179, 179, 179, 209, 209, 209, 209, 209, 209, 209, 209, 216, + 216, 216, 216, 216, 216, 216, 216, 217, 217, 217, 217, 217, 217, 217, 217, + 227, 227, 227, 227, 227, 227, 227, 227, 229, 229, 229, 229, 229, 229, 229, + 229, 230, 230, 230, 230, 230, 230, 230, 230, 129, 129, 129, 129, 132, 132, + 132, 132, 133, 133, 133, 133, 134, 134, 134, 134, 136, 136, 136, 136, 146, + 146, 146, 146, 154, 154, 154, 154, 156, 156, 156, 156, 160, 160, 160, 160, + 163, 163, 163, 163, 164, 164, 164, 164, 169, 169, 169, 169, 170, 170, 170, + 170, 173, 173, 173, 173, 178, 178, 178, 178, 181, 181, 181, 181, 185, 185, + 185, 185, 186, 186, 186, 186, 187, 187, 187, 187, 189, 189, 189, 189, 190, + 190, 190, 190, 196, 196, 196, 196, 198, 198, 198, 198, 228, 228, 228, 228, + 232, 232, 232, 232, 233, 233, 233, 233, 1, 1, 135, 135, 137, 137, 138, + 138, 139, 139, 140, 140, 141, 141, 143, 143, 147, 147, 149, 149, 150, 150, + 151, 151, 152, 152, 155, 155, 157, 157, 158, 158, 165, 165, 166, 166, 168, + 168, 174, 174, 175, 175, 180, 180, 182, 182, 183, 183, 188, 188, 191, 191, + 197, 197, 231, 231, 239, 239, 9, 142, 144, 145, 148, 159, 171, 206, 215, + 225, 236, 237, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 199, 199, + 199, 199, 199, 199, 199, 199, 207, 207, 207, 207, 207, 207, 207, 207, 234, + 234, 234, 234, 234, 234, 234, 234, 235, 235, 235, 235, 235, 235, 235, 235, + 192, 192, 192, 192, 193, 193, 193, 193, 200, 200, 200, 200, 201, 201, 201, + 201, 202, 202, 202, 202, 205, 205, 205, 205, 210, 210, 210, 210, 213, 213, + 213, 213, 218, 218, 218, 218, 219, 219, 219, 219, 238, 238, 238, 238, 240, + 240, 240, 240, 242, 242, 242, 242, 243, 243, 243, 243, 255, 255, 255, 255, + 203, 203, 204, 204, 211, 211, 212, 212, 214, 214, 221, 221, 222, 222, 223, + 223, 241, 241, 244, 244, 245, 245, 246, 246, 247, 247, 248, 248, 250, 250, + 251, 251, 252, 252, 253, 253, 254, 254, 2, 3, 4, 5, 6, 7, 8, + 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 127, 220, 249, -1, 10, 10, 10, 10, 13, 13, 13, + 13, 22, 22, 22, 22, 256, 256, 256, 256, +}; + +static const uint8_t inverse_base64[256] = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, + 255, 255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, + 255, 64, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 255, 255, 255, 255, 255, 255, 26, 27, 28, 29, 30, 31, 32, 33, + 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, +}; + +/* emission helpers */ +static int on_hdr(grpc_chttp2_hpack_parser *p, grpc_mdelem *md, + int add_to_table) { + if (add_to_table) { + if (!grpc_chttp2_hptbl_add(&p->table, md)) { + return 0; + } + } + p->on_header(p->on_header_user_data, md); + return 1; +} + +static grpc_mdstr *take_string(grpc_chttp2_hpack_parser *p, + grpc_chttp2_hpack_parser_string *str) { + grpc_mdstr *s = grpc_mdstr_from_buffer((uint8_t *)str->str, str->length); + str->length = 0; + return s; +} + +/* jump to the next state */ +static int parse_next(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + p->state = *p->next_state++; + return p->state(p, cur, end); +} + +/* begin parsing a header: all functionality is encoded into lookup tables + above */ +static int parse_begin(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + if (cur == end) { + p->state = parse_begin; + return 1; + } + + return first_byte_action[first_byte_lut[*cur]](p, cur, end); +} + +/* stream dependency and prioritization data: we just skip it */ +static int parse_stream_weight(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + if (cur == end) { + p->state = parse_stream_weight; + return 1; + } + + return p->after_prioritization(p, cur + 1, end); +} + +static int parse_stream_dep3(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + if (cur == end) { + p->state = parse_stream_dep3; + return 1; + } + + return parse_stream_weight(p, cur + 1, end); +} + +static int parse_stream_dep2(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + if (cur == end) { + p->state = parse_stream_dep2; + return 1; + } + + return parse_stream_dep3(p, cur + 1, end); +} + +static int parse_stream_dep1(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + if (cur == end) { + p->state = parse_stream_dep1; + return 1; + } + + return parse_stream_dep2(p, cur + 1, end); +} + +static int parse_stream_dep0(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + if (cur == end) { + p->state = parse_stream_dep0; + return 1; + } + + return parse_stream_dep1(p, cur + 1, end); +} + +/* emit an indexed field; for now just logs it to console; jumps to + begin the next field on completion */ +static int finish_indexed_field(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + grpc_mdelem *md = grpc_chttp2_hptbl_lookup(&p->table, p->index); + if (md == NULL) { + gpr_log(GPR_ERROR, "Invalid HPACK index received: %d", p->index); + return 0; + } + GRPC_MDELEM_REF(md); + return on_hdr(p, md, 0) && parse_begin(p, cur, end); +} + +/* parse an indexed field with index < 127 */ +static int parse_indexed_field(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + p->dynamic_table_update_allowed = 0; + p->index = (*cur) & 0x7f; + return finish_indexed_field(p, cur + 1, end); +} + +/* parse an indexed field with index >= 127 */ +static int parse_indexed_field_x(grpc_chttp2_hpack_parser *p, + const uint8_t *cur, const uint8_t *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + finish_indexed_field}; + p->dynamic_table_update_allowed = 0; + p->next_state = and_then; + p->index = 0x7f; + p->parsing.value = &p->index; + return parse_value0(p, cur + 1, end); +} + +/* finish a literal header with incremental indexing: just log, and jump to ' + begin */ +static int finish_lithdr_incidx(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + grpc_mdelem *md = grpc_chttp2_hptbl_lookup(&p->table, p->index); + GPR_ASSERT(md != NULL); /* handled in string parsing */ + return on_hdr(p, grpc_mdelem_from_metadata_strings(GRPC_MDSTR_REF(md->key), + take_string(p, &p->value)), + 1) && + parse_begin(p, cur, end); +} + +/* finish a literal header with incremental indexing with no index */ +static int finish_lithdr_incidx_v(grpc_chttp2_hpack_parser *p, + const uint8_t *cur, const uint8_t *end) { + return on_hdr(p, grpc_mdelem_from_metadata_strings(take_string(p, &p->key), + take_string(p, &p->value)), + 1) && + parse_begin(p, cur, end); +} + +/* parse a literal header with incremental indexing; index < 63 */ +static int parse_lithdr_incidx(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + parse_value_string_with_indexed_key, finish_lithdr_incidx}; + p->dynamic_table_update_allowed = 0; + p->next_state = and_then; + p->index = (*cur) & 0x3f; + return parse_string_prefix(p, cur + 1, end); +} + +/* parse a literal header with incremental indexing; index >= 63 */ +static int parse_lithdr_incidx_x(grpc_chttp2_hpack_parser *p, + const uint8_t *cur, const uint8_t *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + parse_string_prefix, parse_value_string_with_indexed_key, + finish_lithdr_incidx}; + p->dynamic_table_update_allowed = 0; + p->next_state = and_then; + p->index = 0x3f; + p->parsing.value = &p->index; + return parse_value0(p, cur + 1, end); +} + +/* parse a literal header with incremental indexing; index = 0 */ +static int parse_lithdr_incidx_v(grpc_chttp2_hpack_parser *p, + const uint8_t *cur, const uint8_t *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + parse_key_string, parse_string_prefix, + parse_value_string_with_literal_key, finish_lithdr_incidx_v}; + p->dynamic_table_update_allowed = 0; + p->next_state = and_then; + return parse_string_prefix(p, cur + 1, end); +} + +/* finish a literal header without incremental indexing */ +static int finish_lithdr_notidx(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + grpc_mdelem *md = grpc_chttp2_hptbl_lookup(&p->table, p->index); + GPR_ASSERT(md != NULL); /* handled in string parsing */ + return on_hdr(p, grpc_mdelem_from_metadata_strings(GRPC_MDSTR_REF(md->key), + take_string(p, &p->value)), + 0) && + parse_begin(p, cur, end); +} + +/* finish a literal header without incremental indexing with index = 0 */ +static int finish_lithdr_notidx_v(grpc_chttp2_hpack_parser *p, + const uint8_t *cur, const uint8_t *end) { + return on_hdr(p, grpc_mdelem_from_metadata_strings(take_string(p, &p->key), + take_string(p, &p->value)), + 0) && + parse_begin(p, cur, end); +} + +/* parse a literal header without incremental indexing; index < 15 */ +static int parse_lithdr_notidx(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + parse_value_string_with_indexed_key, finish_lithdr_notidx}; + p->dynamic_table_update_allowed = 0; + p->next_state = and_then; + p->index = (*cur) & 0xf; + return parse_string_prefix(p, cur + 1, end); +} + +/* parse a literal header without incremental indexing; index >= 15 */ +static int parse_lithdr_notidx_x(grpc_chttp2_hpack_parser *p, + const uint8_t *cur, const uint8_t *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + parse_string_prefix, parse_value_string_with_indexed_key, + finish_lithdr_notidx}; + p->dynamic_table_update_allowed = 0; + p->next_state = and_then; + p->index = 0xf; + p->parsing.value = &p->index; + return parse_value0(p, cur + 1, end); +} + +/* parse a literal header without incremental indexing; index == 0 */ +static int parse_lithdr_notidx_v(grpc_chttp2_hpack_parser *p, + const uint8_t *cur, const uint8_t *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + parse_key_string, parse_string_prefix, + parse_value_string_with_literal_key, finish_lithdr_notidx_v}; + p->dynamic_table_update_allowed = 0; + p->next_state = and_then; + return parse_string_prefix(p, cur + 1, end); +} + +/* finish a literal header that is never indexed */ +static int finish_lithdr_nvridx(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + grpc_mdelem *md = grpc_chttp2_hptbl_lookup(&p->table, p->index); + GPR_ASSERT(md != NULL); /* handled in string parsing */ + return on_hdr(p, grpc_mdelem_from_metadata_strings(GRPC_MDSTR_REF(md->key), + take_string(p, &p->value)), + 0) && + parse_begin(p, cur, end); +} + +/* finish a literal header that is never indexed with an extra value */ +static int finish_lithdr_nvridx_v(grpc_chttp2_hpack_parser *p, + const uint8_t *cur, const uint8_t *end) { + return on_hdr(p, grpc_mdelem_from_metadata_strings(take_string(p, &p->key), + take_string(p, &p->value)), + 0) && + parse_begin(p, cur, end); +} + +/* parse a literal header that is never indexed; index < 15 */ +static int parse_lithdr_nvridx(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + parse_value_string_with_indexed_key, finish_lithdr_nvridx}; + p->dynamic_table_update_allowed = 0; + p->next_state = and_then; + p->index = (*cur) & 0xf; + return parse_string_prefix(p, cur + 1, end); +} + +/* parse a literal header that is never indexed; index >= 15 */ +static int parse_lithdr_nvridx_x(grpc_chttp2_hpack_parser *p, + const uint8_t *cur, const uint8_t *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + parse_string_prefix, parse_value_string_with_indexed_key, + finish_lithdr_nvridx}; + p->dynamic_table_update_allowed = 0; + p->next_state = and_then; + p->index = 0xf; + p->parsing.value = &p->index; + return parse_value0(p, cur + 1, end); +} + +/* parse a literal header that is never indexed; index == 0 */ +static int parse_lithdr_nvridx_v(grpc_chttp2_hpack_parser *p, + const uint8_t *cur, const uint8_t *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + parse_key_string, parse_string_prefix, + parse_value_string_with_literal_key, finish_lithdr_nvridx_v}; + p->dynamic_table_update_allowed = 0; + p->next_state = and_then; + return parse_string_prefix(p, cur + 1, end); +} + +/* finish parsing a max table size change */ +static int finish_max_tbl_size(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + gpr_log(GPR_INFO, "MAX TABLE SIZE: %d", p->index); + return grpc_chttp2_hptbl_set_current_table_size(&p->table, p->index) && + parse_begin(p, cur, end); +} + +/* parse a max table size change, max size < 15 */ +static int parse_max_tbl_size(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + if (p->dynamic_table_update_allowed == 0) { + return 0; + } + p->dynamic_table_update_allowed--; + p->index = (*cur) & 0x1f; + return finish_max_tbl_size(p, cur + 1, end); +} + +/* parse a max table size change, max size >= 15 */ +static int parse_max_tbl_size_x(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + finish_max_tbl_size}; + if (p->dynamic_table_update_allowed == 0) { + return 0; + } + p->dynamic_table_update_allowed--; + p->next_state = and_then; + p->index = 0x1f; + p->parsing.value = &p->index; + return parse_value0(p, cur + 1, end); +} + +/* a parse error: jam the parse state into parse_error, and return error */ +static int parse_error(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + p->state = parse_error; + return 0; +} + +static int parse_illegal_op(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + GPR_ASSERT(cur != end); + gpr_log(GPR_DEBUG, "Illegal hpack op code %d", *cur); + return parse_error(p, cur, end); +} + +/* parse the 1st byte of a varint into p->parsing.value + no overflow is possible */ +static int parse_value0(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + if (cur == end) { + p->state = parse_value0; + return 1; + } + + *p->parsing.value += (*cur) & 0x7f; + + if ((*cur) & 0x80) { + return parse_value1(p, cur + 1, end); + } else { + return parse_next(p, cur + 1, end); + } +} + +/* parse the 2nd byte of a varint into p->parsing.value + no overflow is possible */ +static int parse_value1(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + if (cur == end) { + p->state = parse_value1; + return 1; + } + + *p->parsing.value += (((uint32_t)*cur) & 0x7f) << 7; + + if ((*cur) & 0x80) { + return parse_value2(p, cur + 1, end); + } else { + return parse_next(p, cur + 1, end); + } +} + +/* parse the 3rd byte of a varint into p->parsing.value + no overflow is possible */ +static int parse_value2(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + if (cur == end) { + p->state = parse_value2; + return 1; + } + + *p->parsing.value += (((uint32_t)*cur) & 0x7f) << 14; + + if ((*cur) & 0x80) { + return parse_value3(p, cur + 1, end); + } else { + return parse_next(p, cur + 1, end); + } +} + +/* parse the 4th byte of a varint into p->parsing.value + no overflow is possible */ +static int parse_value3(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + if (cur == end) { + p->state = parse_value3; + return 1; + } + + *p->parsing.value += (((uint32_t)*cur) & 0x7f) << 21; + + if ((*cur) & 0x80) { + return parse_value4(p, cur + 1, end); + } else { + return parse_next(p, cur + 1, end); + } +} + +/* parse the 5th byte of a varint into p->parsing.value + depending on the byte, we may overflow, and care must be taken */ +static int parse_value4(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + uint8_t c; + uint32_t cur_value; + uint32_t add_value; + + if (cur == end) { + p->state = parse_value4; + return 1; + } + + c = (*cur) & 0x7f; + if (c > 0xf) { + goto error; + } + + cur_value = *p->parsing.value; + add_value = ((uint32_t)c) << 28; + if (add_value > 0xffffffffu - cur_value) { + goto error; + } + + *p->parsing.value = cur_value + add_value; + + if ((*cur) & 0x80) { + return parse_value5up(p, cur + 1, end); + } else { + return parse_next(p, cur + 1, end); + } + +error: + gpr_log(GPR_ERROR, + "integer overflow in hpack integer decoding: have 0x%08x, " + "got byte 0x%02x on byte 5", + *p->parsing.value, *cur); + return parse_error(p, cur, end); +} + +/* parse any trailing bytes in a varint: it's possible to append an arbitrary + number of 0x80's and not affect the value - a zero will terminate - and + anything else will overflow */ +static int parse_value5up(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + while (cur != end && *cur == 0x80) { + ++cur; + } + + if (cur == end) { + p->state = parse_value5up; + return 1; + } + + if (*cur == 0) { + return parse_next(p, cur + 1, end); + } + + gpr_log(GPR_ERROR, + "integer overflow in hpack integer decoding: have 0x%08x, " + "got byte 0x%02x sometime after byte 5", + *p->parsing.value, *cur); + return parse_error(p, cur, end); +} + +/* parse a string prefix */ +static int parse_string_prefix(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + if (cur == end) { + p->state = parse_string_prefix; + return 1; + } + + p->strlen = (*cur) & 0x7f; + p->huff = (*cur) >> 7; + if (p->strlen == 0x7f) { + p->parsing.value = &p->strlen; + return parse_value0(p, cur + 1, end); + } else { + return parse_next(p, cur + 1, end); + } +} + +/* append some bytes to a string */ +static void append_bytes(grpc_chttp2_hpack_parser_string *str, + const uint8_t *data, size_t length) { + if (length + str->length > str->capacity) { + GPR_ASSERT(str->length + length <= UINT32_MAX); + str->capacity = (uint32_t)(str->length + length); + str->str = gpr_realloc(str->str, str->capacity); + } + memcpy(str->str + str->length, data, length); + GPR_ASSERT(length <= UINT32_MAX - str->length); + str->length += (uint32_t)length; +} + +static int append_string(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + grpc_chttp2_hpack_parser_string *str = p->parsing.str; + uint32_t bits; + uint8_t decoded[3]; + switch ((binary_state)p->binary) { + case NOT_BINARY: + append_bytes(str, cur, (size_t)(end - cur)); + return 1; + b64_byte0: + case B64_BYTE0: + if (cur == end) { + p->binary = B64_BYTE0; + return 1; + } + bits = inverse_base64[*cur]; + ++cur; + if (bits == 255) + return 0; + else if (bits == 64) + goto b64_byte0; + p->base64_buffer = bits << 18; + /* fallthrough */ + b64_byte1: + case B64_BYTE1: + if (cur == end) { + p->binary = B64_BYTE1; + return 1; + } + bits = inverse_base64[*cur]; + ++cur; + if (bits == 255) + return 0; + else if (bits == 64) + goto b64_byte1; + p->base64_buffer |= bits << 12; + /* fallthrough */ + b64_byte2: + case B64_BYTE2: + if (cur == end) { + p->binary = B64_BYTE2; + return 1; + } + bits = inverse_base64[*cur]; + ++cur; + if (bits == 255) + return 0; + else if (bits == 64) + goto b64_byte2; + p->base64_buffer |= bits << 6; + /* fallthrough */ + b64_byte3: + case B64_BYTE3: + if (cur == end) { + p->binary = B64_BYTE3; + return 1; + } + bits = inverse_base64[*cur]; + ++cur; + if (bits == 255) + return 0; + else if (bits == 64) + goto b64_byte3; + p->base64_buffer |= bits; + bits = p->base64_buffer; + decoded[0] = (uint8_t)(bits >> 16); + decoded[1] = (uint8_t)(bits >> 8); + decoded[2] = (uint8_t)(bits); + append_bytes(str, decoded, 3); + goto b64_byte0; + } + GPR_UNREACHABLE_CODE(return 1); +} + +/* append a null terminator to a string */ +static int finish_str(grpc_chttp2_hpack_parser *p) { + uint8_t terminator = 0; + uint8_t decoded[2]; + uint32_t bits; + grpc_chttp2_hpack_parser_string *str = p->parsing.str; + switch ((binary_state)p->binary) { + case NOT_BINARY: + break; + case B64_BYTE0: + break; + case B64_BYTE1: + gpr_log(GPR_ERROR, "illegal base64 encoding"); + return 0; /* illegal encoding */ + case B64_BYTE2: + bits = p->base64_buffer; + if (bits & 0xffff) { + gpr_log(GPR_ERROR, "trailing bits in base64 encoding: 0x%04x", + bits & 0xffff); + return 0; + } + decoded[0] = (uint8_t)(bits >> 16); + append_bytes(str, decoded, 1); + break; + case B64_BYTE3: + bits = p->base64_buffer; + if (bits & 0xff) { + gpr_log(GPR_ERROR, "trailing bits in base64 encoding: 0x%02x", + bits & 0xff); + return 0; + } + decoded[0] = (uint8_t)(bits >> 16); + decoded[1] = (uint8_t)(bits >> 8); + append_bytes(str, decoded, 2); + break; + } + append_bytes(str, &terminator, 1); + p->parsing.str->length--; /* don't actually count the null terminator */ + return 1; +} + +/* decode a nibble from a huffman encoded stream */ +static int huff_nibble(grpc_chttp2_hpack_parser *p, uint8_t nibble) { + int16_t emit = emit_sub_tbl[16 * emit_tbl[p->huff_state] + nibble]; + int16_t next = next_sub_tbl[16 * next_tbl[p->huff_state] + nibble]; + if (emit != -1) { + if (emit >= 0 && emit < 256) { + uint8_t c = (uint8_t)emit; + if (!append_string(p, &c, (&c) + 1)) return 0; + } else { + assert(emit == 256); + } + } + p->huff_state = next; + return 1; +} + +/* decode full bytes from a huffman encoded stream */ +static int add_huff_bytes(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + for (; cur != end; ++cur) { + if (!huff_nibble(p, *cur >> 4) || !huff_nibble(p, *cur & 0xf)) return 0; + } + return 1; +} + +/* decode some string bytes based on the current decoding mode + (huffman or not) */ +static int add_str_bytes(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + if (p->huff) { + return add_huff_bytes(p, cur, end); + } else { + return append_string(p, cur, end); + } +} + +/* parse a string - tries to do large chunks at a time */ +static int parse_string(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + size_t remaining = p->strlen - p->strgot; + size_t given = (size_t)(end - cur); + if (remaining <= given) { + return add_str_bytes(p, cur, cur + remaining) && finish_str(p) && + parse_next(p, cur + remaining, end); + } else { + if (!add_str_bytes(p, cur, cur + given)) return 0; + GPR_ASSERT(given <= UINT32_MAX - p->strgot); + p->strgot += (uint32_t)given; + p->state = parse_string; + return 1; + } +} + +/* begin parsing a string - performs setup, calls parse_string */ +static int begin_parse_string(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end, uint8_t binary, + grpc_chttp2_hpack_parser_string *str) { + p->strgot = 0; + str->length = 0; + p->parsing.str = str; + p->huff_state = 0; + p->binary = binary; + return parse_string(p, cur, end); +} + +/* parse the key string */ +static int parse_key_string(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end) { + return begin_parse_string(p, cur, end, NOT_BINARY, &p->key); +} + +/* check if a key represents a binary header or not */ +typedef enum { BINARY_HEADER, PLAINTEXT_HEADER, ERROR_HEADER } is_binary_header; + +static is_binary_header is_binary_literal_header(grpc_chttp2_hpack_parser *p) { + return grpc_is_binary_header(p->key.str, p->key.length) ? BINARY_HEADER + : PLAINTEXT_HEADER; +} + +static is_binary_header is_binary_indexed_header(grpc_chttp2_hpack_parser *p) { + grpc_mdelem *elem = grpc_chttp2_hptbl_lookup(&p->table, p->index); + if (!elem) { + gpr_log(GPR_ERROR, "Invalid HPACK index received: %d", p->index); + return ERROR_HEADER; + } + return grpc_is_binary_header( + (const char *)GPR_SLICE_START_PTR(elem->key->slice), + GPR_SLICE_LENGTH(elem->key->slice)) + ? BINARY_HEADER + : PLAINTEXT_HEADER; +} + +/* parse the value string */ +static int parse_value_string(grpc_chttp2_hpack_parser *p, const uint8_t *cur, + const uint8_t *end, is_binary_header type) { + switch (type) { + case BINARY_HEADER: + return begin_parse_string(p, cur, end, B64_BYTE0, &p->value); + case PLAINTEXT_HEADER: + return begin_parse_string(p, cur, end, NOT_BINARY, &p->value); + case ERROR_HEADER: + return 0; + } + /* Add code to prevent return without value error */ + GPR_UNREACHABLE_CODE(return 0); +} + +static int parse_value_string_with_indexed_key(grpc_chttp2_hpack_parser *p, + const uint8_t *cur, + const uint8_t *end) { + return parse_value_string(p, cur, end, is_binary_indexed_header(p)); +} + +static int parse_value_string_with_literal_key(grpc_chttp2_hpack_parser *p, + const uint8_t *cur, + const uint8_t *end) { + return parse_value_string(p, cur, end, is_binary_literal_header(p)); +} + +/* PUBLIC INTERFACE */ + +static void on_header_not_set(void *user_data, grpc_mdelem *md) { + GPR_UNREACHABLE_CODE(return ); +} + +void grpc_chttp2_hpack_parser_init(grpc_chttp2_hpack_parser *p) { + p->on_header = on_header_not_set; + p->on_header_user_data = NULL; + p->state = parse_begin; + p->key.str = NULL; + p->key.capacity = 0; + p->key.length = 0; + p->value.str = NULL; + p->value.capacity = 0; + p->value.length = 0; + p->dynamic_table_update_allowed = 2; + grpc_chttp2_hptbl_init(&p->table); +} + +void grpc_chttp2_hpack_parser_set_has_priority(grpc_chttp2_hpack_parser *p) { + p->after_prioritization = p->state; + p->state = parse_stream_dep0; +} + +void grpc_chttp2_hpack_parser_destroy(grpc_chttp2_hpack_parser *p) { + grpc_chttp2_hptbl_destroy(&p->table); + gpr_free(p->key.str); + gpr_free(p->value.str); +} + +int grpc_chttp2_hpack_parser_parse(grpc_chttp2_hpack_parser *p, + const uint8_t *beg, const uint8_t *end) { + /* TODO(ctiller): limit the distance of end from beg, and perform multiple + steps in the event of a large chunk of data to limit + stack space usage when no tail call optimization is + available */ + return p->state(p, beg, end); +} + +grpc_chttp2_parse_error grpc_chttp2_header_parser_parse( + grpc_exec_ctx *exec_ctx, void *hpack_parser, + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) { + grpc_chttp2_hpack_parser *parser = hpack_parser; + GPR_TIMER_BEGIN("grpc_chttp2_hpack_parser_parse", 0); + if (!grpc_chttp2_hpack_parser_parse(parser, GPR_SLICE_START_PTR(slice), + GPR_SLICE_END_PTR(slice))) { + GPR_TIMER_END("grpc_chttp2_hpack_parser_parse", 0); + return GRPC_CHTTP2_CONNECTION_ERROR; + } + if (is_last) { + if (parser->is_boundary && parser->state != parse_begin) { + gpr_log(GPR_ERROR, + "end of header frame not aligned with a hpack record boundary"); + GPR_TIMER_END("grpc_chttp2_hpack_parser_parse", 0); + return GRPC_CHTTP2_CONNECTION_ERROR; + } + /* need to check for null stream: this can occur if we receive an invalid + stream id on a header */ + if (stream_parsing != NULL) { + if (parser->is_boundary) { + stream_parsing + ->got_metadata_on_parse[stream_parsing->header_frames_received] = 1; + stream_parsing->header_frames_received++; + grpc_chttp2_list_add_parsing_seen_stream(transport_parsing, + stream_parsing); + } + if (parser->is_eof) { + stream_parsing->received_close = 1; + } + } + parser->on_header = on_header_not_set; + parser->on_header_user_data = NULL; + parser->is_boundary = 0xde; + parser->is_eof = 0xde; + parser->dynamic_table_update_allowed = 2; + } + GPR_TIMER_END("grpc_chttp2_hpack_parser_parse", 0); + return GRPC_CHTTP2_PARSE_OK; +} diff --git a/src/core/lib/transport/chttp2/hpack_parser.h b/src/core/lib/transport/chttp2/hpack_parser.h new file mode 100644 index 0000000000..a534fd5cf4 --- /dev/null +++ b/src/core/lib/transport/chttp2/hpack_parser.h @@ -0,0 +1,116 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CHTTP2_HPACK_PARSER_H +#define GRPC_CORE_LIB_TRANSPORT_CHTTP2_HPACK_PARSER_H + +#include <stddef.h> + +#include <grpc/support/port_platform.h> +#include "src/core/lib/iomgr/exec_ctx.h" +#include "src/core/lib/transport/chttp2/frame.h" +#include "src/core/lib/transport/chttp2/hpack_table.h" +#include "src/core/lib/transport/metadata.h" + +typedef struct grpc_chttp2_hpack_parser grpc_chttp2_hpack_parser; + +typedef int (*grpc_chttp2_hpack_parser_state)(grpc_chttp2_hpack_parser *p, + const uint8_t *beg, + const uint8_t *end); + +typedef struct { + char *str; + uint32_t length; + uint32_t capacity; +} grpc_chttp2_hpack_parser_string; + +struct grpc_chttp2_hpack_parser { + /* user specified callback for each header output */ + void (*on_header)(void *user_data, grpc_mdelem *md); + void *on_header_user_data; + + /* current parse state - or a function that implements it */ + grpc_chttp2_hpack_parser_state state; + /* future states dependent on the opening op code */ + const grpc_chttp2_hpack_parser_state *next_state; + /* what to do after skipping prioritization data */ + grpc_chttp2_hpack_parser_state after_prioritization; + /* the value we're currently parsing */ + union { + uint32_t *value; + grpc_chttp2_hpack_parser_string *str; + } parsing; + /* string parameters for each chunk */ + grpc_chttp2_hpack_parser_string key; + grpc_chttp2_hpack_parser_string value; + /* parsed index */ + uint32_t index; + /* length of source bytes for the currently parsing string */ + uint32_t strlen; + /* number of source bytes read for the currently parsing string */ + uint32_t strgot; + /* huffman decoding state */ + int16_t huff_state; + /* is the string being decoded binary? */ + uint8_t binary; + /* is the current string huffman encoded? */ + uint8_t huff; + /* is a dynamic table update allowed? */ + uint8_t dynamic_table_update_allowed; + /* set by higher layers, used by grpc_chttp2_header_parser_parse to signal + it should append a metadata boundary at the end of frame */ + uint8_t is_boundary; + uint8_t is_eof; + uint32_t base64_buffer; + + /* hpack table */ + grpc_chttp2_hptbl table; +}; + +void grpc_chttp2_hpack_parser_init(grpc_chttp2_hpack_parser *p); +void grpc_chttp2_hpack_parser_destroy(grpc_chttp2_hpack_parser *p); + +void grpc_chttp2_hpack_parser_set_has_priority(grpc_chttp2_hpack_parser *p); + +/* returns 1 on success, 0 on error */ +int grpc_chttp2_hpack_parser_parse(grpc_chttp2_hpack_parser *p, + const uint8_t *beg, const uint8_t *end); + +/* wraps grpc_chttp2_hpack_parser_parse to provide a frame level parser for + the transport */ +grpc_chttp2_parse_error grpc_chttp2_header_parser_parse( + grpc_exec_ctx *exec_ctx, void *hpack_parser, + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last); + +#endif /* GRPC_CORE_LIB_TRANSPORT_CHTTP2_HPACK_PARSER_H */ diff --git a/src/core/lib/transport/chttp2/hpack_table.c b/src/core/lib/transport/chttp2/hpack_table.c new file mode 100644 index 0000000000..f92bc26585 --- /dev/null +++ b/src/core/lib/transport/chttp2/hpack_table.c @@ -0,0 +1,361 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/chttp2/hpack_table.h" + +#include <assert.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +#include "src/core/lib/support/murmur_hash.h" + +static struct { + const char *key; + const char *value; +} static_table[] = { + /* 0: */ + {NULL, NULL}, + /* 1: */ + {":authority", ""}, + /* 2: */ + {":method", "GET"}, + /* 3: */ + {":method", "POST"}, + /* 4: */ + {":path", "/"}, + /* 5: */ + {":path", "/index.html"}, + /* 6: */ + {":scheme", "http"}, + /* 7: */ + {":scheme", "https"}, + /* 8: */ + {":status", "200"}, + /* 9: */ + {":status", "204"}, + /* 10: */ + {":status", "206"}, + /* 11: */ + {":status", "304"}, + /* 12: */ + {":status", "400"}, + /* 13: */ + {":status", "404"}, + /* 14: */ + {":status", "500"}, + /* 15: */ + {"accept-charset", ""}, + /* 16: */ + {"accept-encoding", "gzip, deflate"}, + /* 17: */ + {"accept-language", ""}, + /* 18: */ + {"accept-ranges", ""}, + /* 19: */ + {"accept", ""}, + /* 20: */ + {"access-control-allow-origin", ""}, + /* 21: */ + {"age", ""}, + /* 22: */ + {"allow", ""}, + /* 23: */ + {"authorization", ""}, + /* 24: */ + {"cache-control", ""}, + /* 25: */ + {"content-disposition", ""}, + /* 26: */ + {"content-encoding", ""}, + /* 27: */ + {"content-language", ""}, + /* 28: */ + {"content-length", ""}, + /* 29: */ + {"content-location", ""}, + /* 30: */ + {"content-range", ""}, + /* 31: */ + {"content-type", ""}, + /* 32: */ + {"cookie", ""}, + /* 33: */ + {"date", ""}, + /* 34: */ + {"etag", ""}, + /* 35: */ + {"expect", ""}, + /* 36: */ + {"expires", ""}, + /* 37: */ + {"from", ""}, + /* 38: */ + {"host", ""}, + /* 39: */ + {"if-match", ""}, + /* 40: */ + {"if-modified-since", ""}, + /* 41: */ + {"if-none-match", ""}, + /* 42: */ + {"if-range", ""}, + /* 43: */ + {"if-unmodified-since", ""}, + /* 44: */ + {"last-modified", ""}, + /* 45: */ + {"link", ""}, + /* 46: */ + {"location", ""}, + /* 47: */ + {"max-forwards", ""}, + /* 48: */ + {"proxy-authenticate", ""}, + /* 49: */ + {"proxy-authorization", ""}, + /* 50: */ + {"range", ""}, + /* 51: */ + {"referer", ""}, + /* 52: */ + {"refresh", ""}, + /* 53: */ + {"retry-after", ""}, + /* 54: */ + {"server", ""}, + /* 55: */ + {"set-cookie", ""}, + /* 56: */ + {"strict-transport-security", ""}, + /* 57: */ + {"transfer-encoding", ""}, + /* 58: */ + {"user-agent", ""}, + /* 59: */ + {"vary", ""}, + /* 60: */ + {"via", ""}, + /* 61: */ + {"www-authenticate", ""}, +}; + +static uint32_t entries_for_bytes(uint32_t bytes) { + return (bytes + GRPC_CHTTP2_HPACK_ENTRY_OVERHEAD - 1) / + GRPC_CHTTP2_HPACK_ENTRY_OVERHEAD; +} + +void grpc_chttp2_hptbl_init(grpc_chttp2_hptbl *tbl) { + size_t i; + + memset(tbl, 0, sizeof(*tbl)); + tbl->current_table_bytes = tbl->max_bytes = + GRPC_CHTTP2_INITIAL_HPACK_TABLE_SIZE; + tbl->max_entries = tbl->cap_entries = + entries_for_bytes(tbl->current_table_bytes); + tbl->ents = gpr_malloc(sizeof(*tbl->ents) * tbl->cap_entries); + memset(tbl->ents, 0, sizeof(*tbl->ents) * tbl->cap_entries); + for (i = 1; i <= GRPC_CHTTP2_LAST_STATIC_ENTRY; i++) { + tbl->static_ents[i - 1] = + grpc_mdelem_from_strings(static_table[i].key, static_table[i].value); + } +} + +void grpc_chttp2_hptbl_destroy(grpc_chttp2_hptbl *tbl) { + size_t i; + for (i = 0; i < GRPC_CHTTP2_LAST_STATIC_ENTRY; i++) { + GRPC_MDELEM_UNREF(tbl->static_ents[i]); + } + for (i = 0; i < tbl->num_ents; i++) { + GRPC_MDELEM_UNREF(tbl->ents[(tbl->first_ent + i) % tbl->cap_entries]); + } + gpr_free(tbl->ents); +} + +grpc_mdelem *grpc_chttp2_hptbl_lookup(const grpc_chttp2_hptbl *tbl, + uint32_t tbl_index) { + /* Static table comes first, just return an entry from it */ + if (tbl_index <= GRPC_CHTTP2_LAST_STATIC_ENTRY) { + return tbl->static_ents[tbl_index - 1]; + } + /* Otherwise, find the value in the list of valid entries */ + tbl_index -= (GRPC_CHTTP2_LAST_STATIC_ENTRY + 1); + if (tbl_index < tbl->num_ents) { + uint32_t offset = + (tbl->num_ents - 1u - tbl_index + tbl->first_ent) % tbl->cap_entries; + return tbl->ents[offset]; + } + /* Invalid entry: return error */ + return NULL; +} + +/* Evict one element from the table */ +static void evict1(grpc_chttp2_hptbl *tbl) { + grpc_mdelem *first_ent = tbl->ents[tbl->first_ent]; + size_t elem_bytes = GPR_SLICE_LENGTH(first_ent->key->slice) + + GPR_SLICE_LENGTH(first_ent->value->slice) + + GRPC_CHTTP2_HPACK_ENTRY_OVERHEAD; + GPR_ASSERT(elem_bytes <= tbl->mem_used); + tbl->mem_used -= (uint32_t)elem_bytes; + tbl->first_ent = ((tbl->first_ent + 1) % tbl->cap_entries); + tbl->num_ents--; + GRPC_MDELEM_UNREF(first_ent); +} + +static void rebuild_ents(grpc_chttp2_hptbl *tbl, uint32_t new_cap) { + grpc_mdelem **ents = gpr_malloc(sizeof(*ents) * new_cap); + uint32_t i; + + for (i = 0; i < tbl->num_ents; i++) { + ents[i] = tbl->ents[(tbl->first_ent + i) % tbl->cap_entries]; + } + gpr_free(tbl->ents); + tbl->ents = ents; + tbl->cap_entries = new_cap; + tbl->first_ent = 0; +} + +void grpc_chttp2_hptbl_set_max_bytes(grpc_chttp2_hptbl *tbl, + uint32_t max_bytes) { + if (tbl->max_bytes == max_bytes) { + return; + } + gpr_log(GPR_DEBUG, "Update hpack parser max size to %d", max_bytes); + while (tbl->mem_used > max_bytes) { + evict1(tbl); + } + tbl->max_bytes = max_bytes; +} + +int grpc_chttp2_hptbl_set_current_table_size(grpc_chttp2_hptbl *tbl, + uint32_t bytes) { + if (tbl->current_table_bytes == bytes) { + return 1; + } + if (bytes > tbl->max_bytes) { + gpr_log(GPR_ERROR, + "Attempt to make hpack table %d bytes when max is %d bytes", bytes, + tbl->max_bytes); + return 0; + } + gpr_log(GPR_DEBUG, "Update hpack parser table size to %d", bytes); + while (tbl->mem_used > bytes) { + evict1(tbl); + } + tbl->current_table_bytes = bytes; + tbl->max_entries = entries_for_bytes(bytes); + if (tbl->max_entries > tbl->cap_entries) { + rebuild_ents(tbl, GPR_MAX(tbl->max_entries, 2 * tbl->cap_entries)); + } else if (tbl->max_entries < tbl->cap_entries / 3) { + uint32_t new_cap = GPR_MAX(tbl->max_entries, 16u); + if (new_cap != tbl->cap_entries) { + rebuild_ents(tbl, new_cap); + } + } + return 1; +} + +int grpc_chttp2_hptbl_add(grpc_chttp2_hptbl *tbl, grpc_mdelem *md) { + /* determine how many bytes of buffer this entry represents */ + size_t elem_bytes = GPR_SLICE_LENGTH(md->key->slice) + + GPR_SLICE_LENGTH(md->value->slice) + + GRPC_CHTTP2_HPACK_ENTRY_OVERHEAD; + + if (tbl->current_table_bytes > tbl->max_bytes) { + gpr_log(GPR_ERROR, + "HPACK max table size reduced to %d but not reflected by hpack " + "stream (still at %d)", + tbl->max_bytes, tbl->current_table_bytes); + return 0; + } + + /* we can't add elements bigger than the max table size */ + if (elem_bytes > tbl->current_table_bytes) { + /* HPACK draft 10 section 4.4 states: + * If the size of the new entry is less than or equal to the maximum + * size, that entry is added to the table. It is not an error to + * attempt to add an entry that is larger than the maximum size; an + * attempt to add an entry larger than the entire table causes + * the table + * to be emptied of all existing entries, and results in an + * empty table. + */ + while (tbl->num_ents) { + evict1(tbl); + } + return 1; + } + + /* evict entries to ensure no overflow */ + while (elem_bytes > (size_t)tbl->current_table_bytes - tbl->mem_used) { + evict1(tbl); + } + + /* copy the finalized entry in */ + tbl->ents[(tbl->first_ent + tbl->num_ents) % tbl->cap_entries] = + GRPC_MDELEM_REF(md); + + /* update accounting values */ + tbl->num_ents++; + tbl->mem_used += (uint32_t)elem_bytes; + return 1; +} + +grpc_chttp2_hptbl_find_result grpc_chttp2_hptbl_find( + const grpc_chttp2_hptbl *tbl, grpc_mdelem *md) { + grpc_chttp2_hptbl_find_result r = {0, 0}; + uint32_t i; + + /* See if the string is in the static table */ + for (i = 0; i < GRPC_CHTTP2_LAST_STATIC_ENTRY; i++) { + grpc_mdelem *ent = tbl->static_ents[i]; + if (md->key != ent->key) continue; + r.index = i + 1u; + r.has_value = md->value == ent->value; + if (r.has_value) return r; + } + + /* Scan the dynamic table */ + for (i = 0; i < tbl->num_ents; i++) { + uint32_t idx = + (uint32_t)(tbl->num_ents - i + GRPC_CHTTP2_LAST_STATIC_ENTRY); + grpc_mdelem *ent = tbl->ents[(tbl->first_ent + i) % tbl->cap_entries]; + if (md->key != ent->key) continue; + r.index = idx; + r.has_value = md->value == ent->value; + if (r.has_value) return r; + } + + return r; +} diff --git a/src/core/lib/transport/chttp2/hpack_table.h b/src/core/lib/transport/chttp2/hpack_table.h new file mode 100644 index 0000000000..2cbc02dd9c --- /dev/null +++ b/src/core/lib/transport/chttp2/hpack_table.h @@ -0,0 +1,108 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CHTTP2_HPACK_TABLE_H +#define GRPC_CORE_LIB_TRANSPORT_CHTTP2_HPACK_TABLE_H + +#include <grpc/support/port_platform.h> +#include <grpc/support/slice.h> +#include "src/core/lib/transport/metadata.h" + +/* HPACK header table */ + +/* last index in the static table */ +#define GRPC_CHTTP2_LAST_STATIC_ENTRY 61 + +/* Initial table size as per the spec */ +#define GRPC_CHTTP2_INITIAL_HPACK_TABLE_SIZE 4096 +/* Maximum table size that we'll use */ +#define GRPC_CHTTP2_MAX_HPACK_TABLE_SIZE GRPC_CHTTP2_INITIAL_HPACK_TABLE_SIZE +/* Per entry overhead bytes as per the spec */ +#define GRPC_CHTTP2_HPACK_ENTRY_OVERHEAD 32 +#if 0 +/* Maximum number of entries we could possibly fit in the table, given defined + overheads */ +#define GRPC_CHTTP2_MAX_TABLE_COUNT \ + ((GRPC_CHTTP2_MAX_HPACK_TABLE_SIZE + GRPC_CHTTP2_HPACK_ENTRY_OVERHEAD - 1) / \ + GRPC_CHTTP2_HPACK_ENTRY_OVERHEAD) +#endif + +/* hpack decoder table */ +typedef struct { + /* the first used entry in ents */ + uint32_t first_ent; + /* how many entries are in the table */ + uint32_t num_ents; + /* the amount of memory used by the table, according to the hpack algorithm */ + uint32_t mem_used; + /* the max memory allowed to be used by the table, according to the hpack + algorithm */ + uint32_t max_bytes; + /* the currently agreed size of the table, according to the hpack algorithm */ + uint32_t current_table_bytes; + /* Maximum number of entries we could possibly fit in the table, given defined + overheads */ + uint32_t max_entries; + /* Number of entries allocated in ents */ + uint32_t cap_entries; + /* a circular buffer of headers - this is stored in the opposite order to + what hpack specifies, in order to simplify table management a little... + meaning lookups need to SUBTRACT from the end position */ + grpc_mdelem **ents; + grpc_mdelem *static_ents[GRPC_CHTTP2_LAST_STATIC_ENTRY]; +} grpc_chttp2_hptbl; + +/* initialize a hpack table */ +void grpc_chttp2_hptbl_init(grpc_chttp2_hptbl *tbl); +void grpc_chttp2_hptbl_destroy(grpc_chttp2_hptbl *tbl); +void grpc_chttp2_hptbl_set_max_bytes(grpc_chttp2_hptbl *tbl, + uint32_t max_bytes); +int grpc_chttp2_hptbl_set_current_table_size(grpc_chttp2_hptbl *tbl, + uint32_t bytes); + +/* lookup a table entry based on its hpack index */ +grpc_mdelem *grpc_chttp2_hptbl_lookup(const grpc_chttp2_hptbl *tbl, + uint32_t index); +/* add a table entry to the index */ +int grpc_chttp2_hptbl_add(grpc_chttp2_hptbl *tbl, + grpc_mdelem *md) GRPC_MUST_USE_RESULT; +/* Find a key/value pair in the table... returns the index in the table of the + most similar entry, or 0 if the value was not found */ +typedef struct { + uint32_t index; + int has_value; +} grpc_chttp2_hptbl_find_result; +grpc_chttp2_hptbl_find_result grpc_chttp2_hptbl_find( + const grpc_chttp2_hptbl *tbl, grpc_mdelem *md); + +#endif /* GRPC_CORE_LIB_TRANSPORT_CHTTP2_HPACK_TABLE_H */ diff --git a/src/core/lib/transport/chttp2/hpack_tables.txt b/src/core/lib/transport/chttp2/hpack_tables.txt new file mode 100644 index 0000000000..08842a0267 --- /dev/null +++ b/src/core/lib/transport/chttp2/hpack_tables.txt @@ -0,0 +1,66 @@ +Static table, from the spec: + +-------+-----------------------------+---------------+ + | Index | Header Name | Header Value | + +-------+-----------------------------+---------------+ + | 1 | :authority | | + | 2 | :method | GET | + | 3 | :method | POST | + | 4 | :path | / | + | 5 | :path | /index.html | + | 6 | :scheme | http | + | 7 | :scheme | https | + | 8 | :status | 200 | + | 9 | :status | 204 | + | 10 | :status | 206 | + | 11 | :status | 304 | + | 12 | :status | 400 | + | 13 | :status | 404 | + | 14 | :status | 500 | + | 15 | accept-charset | | + | 16 | accept-encoding | gzip, deflate | + | 17 | accept-language | | + | 18 | accept-ranges | | + | 19 | accept | | + | 20 | access-control-allow-origin | | + | 21 | age | | + | 22 | allow | | + | 23 | authorization | | + | 24 | cache-control | | + | 25 | content-disposition | | + | 26 | content-encoding | | + | 27 | content-language | | + | 28 | content-length | | + | 29 | content-location | | + | 30 | content-range | | + | 31 | content-type | | + | 32 | cookie | | + | 33 | date | | + | 34 | etag | | + | 35 | expect | | + | 36 | expires | | + | 37 | from | | + | 38 | host | | + | 39 | if-match | | + | 40 | if-modified-since | | + | 41 | if-none-match | | + | 42 | if-range | | + | 43 | if-unmodified-since | | + | 44 | last-modified | | + | 45 | link | | + | 46 | location | | + | 47 | max-forwards | | + | 48 | proxy-authenticate | | + | 49 | proxy-authorization | | + | 50 | range | | + | 51 | referer | | + | 52 | refresh | | + | 53 | retry-after | | + | 54 | server | | + | 55 | set-cookie | | + | 56 | strict-transport-security | | + | 57 | transfer-encoding | | + | 58 | user-agent | | + | 59 | vary | | + | 60 | via | | + | 61 | www-authenticate | | + +-------+-----------------------------+---------------+ diff --git a/src/core/lib/transport/chttp2/http2_errors.h b/src/core/lib/transport/chttp2/http2_errors.h new file mode 100644 index 0000000000..0238f9d80b --- /dev/null +++ b/src/core/lib/transport/chttp2/http2_errors.h @@ -0,0 +1,56 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CHTTP2_HTTP2_ERRORS_H +#define GRPC_CORE_LIB_TRANSPORT_CHTTP2_HTTP2_ERRORS_H + +/* error codes for RST_STREAM from http2 draft 14 section 7 */ +typedef enum { + GRPC_CHTTP2_NO_ERROR = 0x0, + GRPC_CHTTP2_PROTOCOL_ERROR = 0x1, + GRPC_CHTTP2_INTERNAL_ERROR = 0x2, + GRPC_CHTTP2_FLOW_CONTROL_ERROR = 0x3, + GRPC_CHTTP2_SETTINGS_TIMEOUT = 0x4, + GRPC_CHTTP2_STREAM_CLOSED = 0x5, + GRPC_CHTTP2_FRAME_SIZE_ERROR = 0x6, + GRPC_CHTTP2_REFUSED_STREAM = 0x7, + GRPC_CHTTP2_CANCEL = 0x8, + GRPC_CHTTP2_COMPRESSION_ERROR = 0x9, + GRPC_CHTTP2_CONNECT_ERROR = 0xa, + GRPC_CHTTP2_ENHANCE_YOUR_CALM = 0xb, + GRPC_CHTTP2_INADEQUATE_SECURITY = 0xc, + /* force use of a default clause */ + GRPC_CHTTP2__ERROR_DO_NOT_USE = -1 +} grpc_chttp2_error_code; + +#endif /* GRPC_CORE_LIB_TRANSPORT_CHTTP2_HTTP2_ERRORS_H */ diff --git a/src/core/lib/transport/chttp2/huffsyms.c b/src/core/lib/transport/chttp2/huffsyms.c new file mode 100644 index 0000000000..27497e6ae0 --- /dev/null +++ b/src/core/lib/transport/chttp2/huffsyms.c @@ -0,0 +1,105 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/chttp2/huffsyms.h" + +/* Constants pulled from the HPACK spec, and converted to C using the vim + command: + :%s/.* \([0-9a-f]\+\) \[ *\([0-9]\+\)\]/{0x\1, \2},/g */ +const grpc_chttp2_huffsym grpc_chttp2_huffsyms[GRPC_CHTTP2_NUM_HUFFSYMS] = { + {0x1ff8, 13}, {0x7fffd8, 23}, {0xfffffe2, 28}, {0xfffffe3, 28}, + {0xfffffe4, 28}, {0xfffffe5, 28}, {0xfffffe6, 28}, {0xfffffe7, 28}, + {0xfffffe8, 28}, {0xffffea, 24}, {0x3ffffffc, 30}, {0xfffffe9, 28}, + {0xfffffea, 28}, {0x3ffffffd, 30}, {0xfffffeb, 28}, {0xfffffec, 28}, + {0xfffffed, 28}, {0xfffffee, 28}, {0xfffffef, 28}, {0xffffff0, 28}, + {0xffffff1, 28}, {0xffffff2, 28}, {0x3ffffffe, 30}, {0xffffff3, 28}, + {0xffffff4, 28}, {0xffffff5, 28}, {0xffffff6, 28}, {0xffffff7, 28}, + {0xffffff8, 28}, {0xffffff9, 28}, {0xffffffa, 28}, {0xffffffb, 28}, + {0x14, 6}, {0x3f8, 10}, {0x3f9, 10}, {0xffa, 12}, + {0x1ff9, 13}, {0x15, 6}, {0xf8, 8}, {0x7fa, 11}, + {0x3fa, 10}, {0x3fb, 10}, {0xf9, 8}, {0x7fb, 11}, + {0xfa, 8}, {0x16, 6}, {0x17, 6}, {0x18, 6}, + {0x0, 5}, {0x1, 5}, {0x2, 5}, {0x19, 6}, + {0x1a, 6}, {0x1b, 6}, {0x1c, 6}, {0x1d, 6}, + {0x1e, 6}, {0x1f, 6}, {0x5c, 7}, {0xfb, 8}, + {0x7ffc, 15}, {0x20, 6}, {0xffb, 12}, {0x3fc, 10}, + {0x1ffa, 13}, {0x21, 6}, {0x5d, 7}, {0x5e, 7}, + {0x5f, 7}, {0x60, 7}, {0x61, 7}, {0x62, 7}, + {0x63, 7}, {0x64, 7}, {0x65, 7}, {0x66, 7}, + {0x67, 7}, {0x68, 7}, {0x69, 7}, {0x6a, 7}, + {0x6b, 7}, {0x6c, 7}, {0x6d, 7}, {0x6e, 7}, + {0x6f, 7}, {0x70, 7}, {0x71, 7}, {0x72, 7}, + {0xfc, 8}, {0x73, 7}, {0xfd, 8}, {0x1ffb, 13}, + {0x7fff0, 19}, {0x1ffc, 13}, {0x3ffc, 14}, {0x22, 6}, + {0x7ffd, 15}, {0x3, 5}, {0x23, 6}, {0x4, 5}, + {0x24, 6}, {0x5, 5}, {0x25, 6}, {0x26, 6}, + {0x27, 6}, {0x6, 5}, {0x74, 7}, {0x75, 7}, + {0x28, 6}, {0x29, 6}, {0x2a, 6}, {0x7, 5}, + {0x2b, 6}, {0x76, 7}, {0x2c, 6}, {0x8, 5}, + {0x9, 5}, {0x2d, 6}, {0x77, 7}, {0x78, 7}, + {0x79, 7}, {0x7a, 7}, {0x7b, 7}, {0x7ffe, 15}, + {0x7fc, 11}, {0x3ffd, 14}, {0x1ffd, 13}, {0xffffffc, 28}, + {0xfffe6, 20}, {0x3fffd2, 22}, {0xfffe7, 20}, {0xfffe8, 20}, + {0x3fffd3, 22}, {0x3fffd4, 22}, {0x3fffd5, 22}, {0x7fffd9, 23}, + {0x3fffd6, 22}, {0x7fffda, 23}, {0x7fffdb, 23}, {0x7fffdc, 23}, + {0x7fffdd, 23}, {0x7fffde, 23}, {0xffffeb, 24}, {0x7fffdf, 23}, + {0xffffec, 24}, {0xffffed, 24}, {0x3fffd7, 22}, {0x7fffe0, 23}, + {0xffffee, 24}, {0x7fffe1, 23}, {0x7fffe2, 23}, {0x7fffe3, 23}, + {0x7fffe4, 23}, {0x1fffdc, 21}, {0x3fffd8, 22}, {0x7fffe5, 23}, + {0x3fffd9, 22}, {0x7fffe6, 23}, {0x7fffe7, 23}, {0xffffef, 24}, + {0x3fffda, 22}, {0x1fffdd, 21}, {0xfffe9, 20}, {0x3fffdb, 22}, + {0x3fffdc, 22}, {0x7fffe8, 23}, {0x7fffe9, 23}, {0x1fffde, 21}, + {0x7fffea, 23}, {0x3fffdd, 22}, {0x3fffde, 22}, {0xfffff0, 24}, + {0x1fffdf, 21}, {0x3fffdf, 22}, {0x7fffeb, 23}, {0x7fffec, 23}, + {0x1fffe0, 21}, {0x1fffe1, 21}, {0x3fffe0, 22}, {0x1fffe2, 21}, + {0x7fffed, 23}, {0x3fffe1, 22}, {0x7fffee, 23}, {0x7fffef, 23}, + {0xfffea, 20}, {0x3fffe2, 22}, {0x3fffe3, 22}, {0x3fffe4, 22}, + {0x7ffff0, 23}, {0x3fffe5, 22}, {0x3fffe6, 22}, {0x7ffff1, 23}, + {0x3ffffe0, 26}, {0x3ffffe1, 26}, {0xfffeb, 20}, {0x7fff1, 19}, + {0x3fffe7, 22}, {0x7ffff2, 23}, {0x3fffe8, 22}, {0x1ffffec, 25}, + {0x3ffffe2, 26}, {0x3ffffe3, 26}, {0x3ffffe4, 26}, {0x7ffffde, 27}, + {0x7ffffdf, 27}, {0x3ffffe5, 26}, {0xfffff1, 24}, {0x1ffffed, 25}, + {0x7fff2, 19}, {0x1fffe3, 21}, {0x3ffffe6, 26}, {0x7ffffe0, 27}, + {0x7ffffe1, 27}, {0x3ffffe7, 26}, {0x7ffffe2, 27}, {0xfffff2, 24}, + {0x1fffe4, 21}, {0x1fffe5, 21}, {0x3ffffe8, 26}, {0x3ffffe9, 26}, + {0xffffffd, 28}, {0x7ffffe3, 27}, {0x7ffffe4, 27}, {0x7ffffe5, 27}, + {0xfffec, 20}, {0xfffff3, 24}, {0xfffed, 20}, {0x1fffe6, 21}, + {0x3fffe9, 22}, {0x1fffe7, 21}, {0x1fffe8, 21}, {0x7ffff3, 23}, + {0x3fffea, 22}, {0x3fffeb, 22}, {0x1ffffee, 25}, {0x1ffffef, 25}, + {0xfffff4, 24}, {0xfffff5, 24}, {0x3ffffea, 26}, {0x7ffff4, 23}, + {0x3ffffeb, 26}, {0x7ffffe6, 27}, {0x3ffffec, 26}, {0x3ffffed, 26}, + {0x7ffffe7, 27}, {0x7ffffe8, 27}, {0x7ffffe9, 27}, {0x7ffffea, 27}, + {0x7ffffeb, 27}, {0xffffffe, 28}, {0x7ffffec, 27}, {0x7ffffed, 27}, + {0x7ffffee, 27}, {0x7ffffef, 27}, {0x7fffff0, 27}, {0x3ffffee, 26}, + {0x3fffffff, 30}, +}; diff --git a/src/core/lib/transport/chttp2/huffsyms.h b/src/core/lib/transport/chttp2/huffsyms.h new file mode 100644 index 0000000000..1ca77b9207 --- /dev/null +++ b/src/core/lib/transport/chttp2/huffsyms.h @@ -0,0 +1,48 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CHTTP2_HUFFSYMS_H +#define GRPC_CORE_LIB_TRANSPORT_CHTTP2_HUFFSYMS_H + +/* HPACK static huffman table */ + +#define GRPC_CHTTP2_NUM_HUFFSYMS 257 + +typedef struct { + unsigned bits; + unsigned length; +} grpc_chttp2_huffsym; + +extern const grpc_chttp2_huffsym grpc_chttp2_huffsyms[GRPC_CHTTP2_NUM_HUFFSYMS]; + +#endif /* GRPC_CORE_LIB_TRANSPORT_CHTTP2_HUFFSYMS_H */ diff --git a/src/core/lib/transport/chttp2/incoming_metadata.c b/src/core/lib/transport/chttp2/incoming_metadata.c new file mode 100644 index 0000000000..a1a8d37562 --- /dev/null +++ b/src/core/lib/transport/chttp2/incoming_metadata.c @@ -0,0 +1,96 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/chttp2/incoming_metadata.h" + +#include <string.h> + +#include "src/core/lib/transport/chttp2/internal.h" + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +void grpc_chttp2_incoming_metadata_buffer_init( + grpc_chttp2_incoming_metadata_buffer *buffer) { + buffer->deadline = gpr_inf_future(GPR_CLOCK_REALTIME); +} + +void grpc_chttp2_incoming_metadata_buffer_destroy( + grpc_chttp2_incoming_metadata_buffer *buffer) { + size_t i; + if (!buffer->published) { + for (i = 0; i < buffer->count; i++) { + GRPC_MDELEM_UNREF(buffer->elems[i].md); + } + } + gpr_free(buffer->elems); +} + +void grpc_chttp2_incoming_metadata_buffer_add( + grpc_chttp2_incoming_metadata_buffer *buffer, grpc_mdelem *elem) { + GPR_ASSERT(!buffer->published); + if (buffer->capacity == buffer->count) { + buffer->capacity = GPR_MAX(8, 2 * buffer->capacity); + buffer->elems = + gpr_realloc(buffer->elems, sizeof(*buffer->elems) * buffer->capacity); + } + buffer->elems[buffer->count++].md = elem; +} + +void grpc_chttp2_incoming_metadata_buffer_set_deadline( + grpc_chttp2_incoming_metadata_buffer *buffer, gpr_timespec deadline) { + GPR_ASSERT(!buffer->published); + buffer->deadline = deadline; +} + +void grpc_chttp2_incoming_metadata_buffer_publish( + grpc_chttp2_incoming_metadata_buffer *buffer, grpc_metadata_batch *batch) { + GPR_ASSERT(!buffer->published); + buffer->published = 1; + if (buffer->count > 0) { + size_t i; + for (i = 1; i < buffer->count; i++) { + buffer->elems[i].prev = &buffer->elems[i - 1]; + } + for (i = 0; i < buffer->count - 1; i++) { + buffer->elems[i].next = &buffer->elems[i + 1]; + } + buffer->elems[0].prev = NULL; + buffer->elems[buffer->count - 1].next = NULL; + batch->list.head = &buffer->elems[0]; + batch->list.tail = &buffer->elems[buffer->count - 1]; + } else { + batch->list.head = batch->list.tail = NULL; + } + batch->deadline = buffer->deadline; +} diff --git a/src/core/lib/transport/chttp2/incoming_metadata.h b/src/core/lib/transport/chttp2/incoming_metadata.h new file mode 100644 index 0000000000..edfa0adf9d --- /dev/null +++ b/src/core/lib/transport/chttp2/incoming_metadata.h @@ -0,0 +1,60 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CHTTP2_INCOMING_METADATA_H +#define GRPC_CORE_LIB_TRANSPORT_CHTTP2_INCOMING_METADATA_H + +#include "src/core/lib/transport/transport.h" + +typedef struct { + grpc_linked_mdelem *elems; + size_t count; + size_t capacity; + gpr_timespec deadline; + int published; +} grpc_chttp2_incoming_metadata_buffer; + +/** assumes everything initially zeroed */ +void grpc_chttp2_incoming_metadata_buffer_init( + grpc_chttp2_incoming_metadata_buffer *buffer); +void grpc_chttp2_incoming_metadata_buffer_destroy( + grpc_chttp2_incoming_metadata_buffer *buffer); +void grpc_chttp2_incoming_metadata_buffer_publish( + grpc_chttp2_incoming_metadata_buffer *buffer, grpc_metadata_batch *batch); + +void grpc_chttp2_incoming_metadata_buffer_add( + grpc_chttp2_incoming_metadata_buffer *buffer, grpc_mdelem *elem); +void grpc_chttp2_incoming_metadata_buffer_set_deadline( + grpc_chttp2_incoming_metadata_buffer *buffer, gpr_timespec deadline); + +#endif /* GRPC_CORE_LIB_TRANSPORT_CHTTP2_INCOMING_METADATA_H */ diff --git a/src/core/lib/transport/chttp2/internal.h b/src/core/lib/transport/chttp2/internal.h new file mode 100644 index 0000000000..346e404204 --- /dev/null +++ b/src/core/lib/transport/chttp2/internal.h @@ -0,0 +1,780 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CHTTP2_INTERNAL_H +#define GRPC_CORE_LIB_TRANSPORT_CHTTP2_INTERNAL_H + +#include <assert.h> +#include <stdbool.h> + +#include "src/core/lib/iomgr/endpoint.h" +#include "src/core/lib/transport/chttp2/frame.h" +#include "src/core/lib/transport/chttp2/frame_data.h" +#include "src/core/lib/transport/chttp2/frame_goaway.h" +#include "src/core/lib/transport/chttp2/frame_ping.h" +#include "src/core/lib/transport/chttp2/frame_rst_stream.h" +#include "src/core/lib/transport/chttp2/frame_settings.h" +#include "src/core/lib/transport/chttp2/frame_window_update.h" +#include "src/core/lib/transport/chttp2/hpack_encoder.h" +#include "src/core/lib/transport/chttp2/hpack_parser.h" +#include "src/core/lib/transport/chttp2/incoming_metadata.h" +#include "src/core/lib/transport/chttp2/stream_map.h" +#include "src/core/lib/transport/connectivity_state.h" +#include "src/core/lib/transport/transport_impl.h" + +typedef struct grpc_chttp2_transport grpc_chttp2_transport; +typedef struct grpc_chttp2_stream grpc_chttp2_stream; + +/* streams are kept in various linked lists depending on what things need to + happen to them... this enum labels each list */ +typedef enum { + GRPC_CHTTP2_LIST_ALL_STREAMS, + GRPC_CHTTP2_LIST_CHECK_READ_OPS, + GRPC_CHTTP2_LIST_UNANNOUNCED_INCOMING_WINDOW_AVAILABLE, + GRPC_CHTTP2_LIST_WRITABLE, + GRPC_CHTTP2_LIST_WRITING, + GRPC_CHTTP2_LIST_WRITTEN, + GRPC_CHTTP2_LIST_PARSING_SEEN, + GRPC_CHTTP2_LIST_CLOSED_WAITING_FOR_PARSING, + GRPC_CHTTP2_LIST_CLOSED_WAITING_FOR_WRITING, + GRPC_CHTTP2_LIST_STALLED_BY_TRANSPORT, + /* streams waiting for the outgoing window in the writing path, they will be + * merged to the stalled list or writable list under transport lock. */ + GRPC_CHTTP2_LIST_WRITING_STALLED_BY_TRANSPORT, + /** streams that are waiting to start because there are too many concurrent + streams on the connection */ + GRPC_CHTTP2_LIST_WAITING_FOR_CONCURRENCY, + STREAM_LIST_COUNT /* must be last */ +} grpc_chttp2_stream_list_id; + +/* deframer state for the overall http2 stream of bytes */ +typedef enum { + /* prefix: one entry per http2 connection prefix byte */ + GRPC_DTS_CLIENT_PREFIX_0 = 0, + GRPC_DTS_CLIENT_PREFIX_1, + GRPC_DTS_CLIENT_PREFIX_2, + GRPC_DTS_CLIENT_PREFIX_3, + GRPC_DTS_CLIENT_PREFIX_4, + GRPC_DTS_CLIENT_PREFIX_5, + GRPC_DTS_CLIENT_PREFIX_6, + GRPC_DTS_CLIENT_PREFIX_7, + GRPC_DTS_CLIENT_PREFIX_8, + GRPC_DTS_CLIENT_PREFIX_9, + GRPC_DTS_CLIENT_PREFIX_10, + GRPC_DTS_CLIENT_PREFIX_11, + GRPC_DTS_CLIENT_PREFIX_12, + GRPC_DTS_CLIENT_PREFIX_13, + GRPC_DTS_CLIENT_PREFIX_14, + GRPC_DTS_CLIENT_PREFIX_15, + GRPC_DTS_CLIENT_PREFIX_16, + GRPC_DTS_CLIENT_PREFIX_17, + GRPC_DTS_CLIENT_PREFIX_18, + GRPC_DTS_CLIENT_PREFIX_19, + GRPC_DTS_CLIENT_PREFIX_20, + GRPC_DTS_CLIENT_PREFIX_21, + GRPC_DTS_CLIENT_PREFIX_22, + GRPC_DTS_CLIENT_PREFIX_23, + /* frame header byte 0... */ + /* must follow from the prefix states */ + GRPC_DTS_FH_0, + GRPC_DTS_FH_1, + GRPC_DTS_FH_2, + GRPC_DTS_FH_3, + GRPC_DTS_FH_4, + GRPC_DTS_FH_5, + GRPC_DTS_FH_6, + GRPC_DTS_FH_7, + /* ... frame header byte 8 */ + GRPC_DTS_FH_8, + /* inside a http2 frame */ + GRPC_DTS_FRAME +} grpc_chttp2_deframe_transport_state; + +typedef struct { + grpc_chttp2_stream *head; + grpc_chttp2_stream *tail; +} grpc_chttp2_stream_list; + +typedef struct { + grpc_chttp2_stream *next; + grpc_chttp2_stream *prev; +} grpc_chttp2_stream_link; + +/* We keep several sets of connection wide parameters */ +typedef enum { + /* The settings our peer has asked for (and we have acked) */ + GRPC_PEER_SETTINGS = 0, + /* The settings we'd like to have */ + GRPC_LOCAL_SETTINGS, + /* The settings we've published to our peer */ + GRPC_SENT_SETTINGS, + /* The settings the peer has acked */ + GRPC_ACKED_SETTINGS, + GRPC_NUM_SETTING_SETS +} grpc_chttp2_setting_set; + +/* Outstanding ping request data */ +typedef struct grpc_chttp2_outstanding_ping { + uint8_t id[8]; + grpc_closure *on_recv; + struct grpc_chttp2_outstanding_ping *next; + struct grpc_chttp2_outstanding_ping *prev; +} grpc_chttp2_outstanding_ping; + +/* forward declared in frame_data.h */ +struct grpc_chttp2_incoming_byte_stream { + grpc_byte_stream base; + gpr_refcount refs; + struct grpc_chttp2_incoming_byte_stream *next_message; + int failed; + + grpc_chttp2_transport *transport; + grpc_chttp2_stream *stream; + int is_tail; + gpr_slice_buffer slices; + grpc_closure *on_next; + gpr_slice *next; +}; + +typedef struct { + /** data to write next write */ + gpr_slice_buffer qbuf; + + /** window available for us to send to peer */ + int64_t outgoing_window; + /** window available to announce to peer */ + int64_t announce_incoming_window; + /** how much window would we like to have for incoming_window */ + uint32_t connection_window_target; + + /** have we seen a goaway */ + uint8_t seen_goaway; + /** have we sent a goaway */ + uint8_t sent_goaway; + + /** is this transport a client? */ + uint8_t is_client; + /** are the local settings dirty and need to be sent? */ + uint8_t dirtied_local_settings; + /** have local settings been sent? */ + uint8_t sent_local_settings; + /** bitmask of setting indexes to send out */ + uint32_t force_send_settings; + /** settings values */ + uint32_t settings[GRPC_NUM_SETTING_SETS][GRPC_CHTTP2_NUM_SETTINGS]; + + /** what is the next stream id to be allocated by this peer? + copied to next_stream_id in parsing when parsing commences */ + uint32_t next_stream_id; + + /** how far to lookahead in a stream? */ + uint32_t stream_lookahead; + + /** last received stream id */ + uint32_t last_incoming_stream_id; + + /** pings awaiting responses */ + grpc_chttp2_outstanding_ping pings; + /** next payload for an outgoing ping */ + uint64_t ping_counter; + + /** concurrent stream count: updated when not parsing, + so this is a strict over-estimation on the client */ + uint32_t concurrent_stream_count; +} grpc_chttp2_transport_global; + +typedef struct { + /** data to write now */ + gpr_slice_buffer outbuf; + /** hpack encoding */ + grpc_chttp2_hpack_compressor hpack_compressor; + int64_t outgoing_window; + /** is this a client? */ + uint8_t is_client; + /** callback for when writing is done */ + grpc_closure done_cb; +} grpc_chttp2_transport_writing; + +struct grpc_chttp2_transport_parsing { + /** is this transport a client? (boolean) */ + uint8_t is_client; + + /** were settings updated? */ + uint8_t settings_updated; + /** was a settings ack received? */ + uint8_t settings_ack_received; + /** was a goaway frame received? */ + uint8_t goaway_received; + + /** the last sent max_table_size setting */ + uint32_t last_sent_max_table_size; + + /** initial window change */ + int64_t initial_window_update; + + /** data to write later - after parsing */ + gpr_slice_buffer qbuf; + /** parser for headers */ + grpc_chttp2_hpack_parser hpack_parser; + /** simple one shot parsers */ + union { + grpc_chttp2_window_update_parser window_update; + grpc_chttp2_settings_parser settings; + grpc_chttp2_ping_parser ping; + grpc_chttp2_rst_stream_parser rst_stream; + } simple; + /** parser for goaway frames */ + grpc_chttp2_goaway_parser goaway_parser; + + /** window available for peer to send to us */ + int64_t incoming_window; + + /** next stream id available at the time of beginning parsing */ + uint32_t next_stream_id; + uint32_t last_incoming_stream_id; + + /* deframing */ + grpc_chttp2_deframe_transport_state deframe_state; + uint8_t incoming_frame_type; + uint8_t incoming_frame_flags; + uint8_t header_eof; + uint32_t expect_continuation_stream_id; + uint32_t incoming_frame_size; + uint32_t incoming_stream_id; + + /* active parser */ + void *parser_data; + grpc_chttp2_stream_parsing *incoming_stream; + grpc_chttp2_parse_error (*parser)( + grpc_exec_ctx *exec_ctx, void *parser_user_data, + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last); + + /* received settings */ + uint32_t settings[GRPC_CHTTP2_NUM_SETTINGS]; + + /* goaway data */ + grpc_status_code goaway_error; + uint32_t goaway_last_stream_index; + gpr_slice goaway_text; + + int64_t outgoing_window; +}; + +struct grpc_chttp2_transport { + grpc_transport base; /* must be first */ + grpc_endpoint *ep; + gpr_refcount refs; + char *peer_string; + + /** when this drops to zero it's safe to shutdown the endpoint */ + gpr_refcount shutdown_ep_refs; + + gpr_mu mu; + + /** is the transport destroying itself? */ + uint8_t destroying; + /** has the upper layer closed the transport? */ + uint8_t closed; + + /** is a thread currently writing */ + uint8_t writing_active; + /** is a thread currently parsing */ + uint8_t parsing_active; + + /** is there a read request to the endpoint outstanding? */ + uint8_t endpoint_reading; + + /** various lists of streams */ + grpc_chttp2_stream_list lists[STREAM_LIST_COUNT]; + + /** global state for reading/writing */ + grpc_chttp2_transport_global global; + /** state only accessible by the chain of execution that + set writing_active=1 */ + grpc_chttp2_transport_writing writing; + /** state only accessible by the chain of execution that + set parsing_active=1 */ + grpc_chttp2_transport_parsing parsing; + + /** maps stream id to grpc_chttp2_stream objects; + owned by the parsing thread when parsing */ + grpc_chttp2_stream_map parsing_stream_map; + + /** streams created by the client (possibly during parsing); + merged with parsing_stream_map during unlock when no + parsing is occurring */ + grpc_chttp2_stream_map new_stream_map; + + /** closure to execute writing */ + grpc_closure writing_action; + /** closure to finish reading from the endpoint */ + grpc_closure recv_data; + + /** incoming read bytes */ + gpr_slice_buffer read_buffer; + + /** address to place a newly accepted stream - set and unset by + grpc_chttp2_parsing_accept_stream; used by init_stream to + publish the accepted server stream */ + grpc_chttp2_stream **accepting_stream; + + struct { + /* accept stream callback */ + void (*accept_stream)(grpc_exec_ctx *exec_ctx, void *user_data, + grpc_transport *transport, const void *server_data); + void *accept_stream_user_data; + + /** connectivity tracking */ + grpc_connectivity_state_tracker state_tracker; + } channel_callback; + + /** Transport op to be applied post-parsing */ + grpc_transport_op *post_parsing_op; +}; + +typedef struct { + /** HTTP2 stream id for this stream, or zero if one has not been assigned */ + uint32_t id; + + /** window available for us to send to peer */ + int64_t outgoing_window; + /** The number of bytes the upper layers have offered to receive. + As the upper layer offers more bytes, this value increases. + As bytes are read, this value decreases. */ + uint32_t max_recv_bytes; + /** The number of bytes the upper layer has offered to read but we have + not yet announced to HTTP2 flow control. + As the upper layers offer to read more bytes, this value increases. + As we advertise incoming flow control window, this value decreases. */ + uint32_t unannounced_incoming_window_for_parse; + uint32_t unannounced_incoming_window_for_writing; + /** things the upper layers would like to send */ + grpc_metadata_batch *send_initial_metadata; + grpc_closure *send_initial_metadata_finished; + grpc_byte_stream *send_message; + grpc_closure *send_message_finished; + grpc_metadata_batch *send_trailing_metadata; + grpc_closure *send_trailing_metadata_finished; + + grpc_metadata_batch *recv_initial_metadata; + grpc_closure *recv_initial_metadata_ready; + grpc_byte_stream **recv_message; + grpc_closure *recv_message_ready; + grpc_metadata_batch *recv_trailing_metadata; + grpc_closure *recv_trailing_metadata_finished; + + /** when the application requests writes be closed, the write_closed is + 'queued'; when the close is flow controlled into the send path, we are + 'sending' it; when the write has been performed it is 'sent' */ + uint8_t write_closed; + /** is this stream reading half-closed (boolean) */ + uint8_t read_closed; + /** is this stream in the stream map? (boolean) */ + uint8_t in_stream_map; + /** has this stream seen an error? if 1, then pending incoming frames + can be thrown away */ + uint8_t seen_error; + + uint8_t published_initial_metadata; + uint8_t published_trailing_metadata; + uint8_t faked_trailing_metadata; + + grpc_chttp2_incoming_metadata_buffer received_initial_metadata; + grpc_chttp2_incoming_metadata_buffer received_trailing_metadata; + + grpc_chttp2_incoming_frame_queue incoming_frames; +} grpc_chttp2_stream_global; + +typedef struct { + /** HTTP2 stream id for this stream, or zero if one has not been assigned */ + uint32_t id; + uint8_t fetching; + bool sent_initial_metadata; + uint8_t sent_message; + uint8_t sent_trailing_metadata; + uint8_t read_closed; + /** send this initial metadata */ + grpc_metadata_batch *send_initial_metadata; + grpc_byte_stream *send_message; + grpc_metadata_batch *send_trailing_metadata; + int64_t outgoing_window; + /** how much window should we announce? */ + uint32_t announce_window; + gpr_slice_buffer flow_controlled_buffer; + gpr_slice fetching_slice; + size_t stream_fetched; + grpc_closure finished_fetch; +} grpc_chttp2_stream_writing; + +struct grpc_chttp2_stream_parsing { + /** HTTP2 stream id for this stream, or zero if one has not been assigned */ + uint32_t id; + /** has this stream received a close */ + uint8_t received_close; + /** saw a rst_stream */ + uint8_t saw_rst_stream; + /** how many header frames have we received? */ + uint8_t header_frames_received; + /** which metadata did we get (on this parse) */ + uint8_t got_metadata_on_parse[2]; + /** should we raise the seen_error flag in transport_global */ + uint8_t seen_error; + /** window available for peer to send to us */ + int64_t incoming_window; + /** parsing state for data frames */ + grpc_chttp2_data_parser data_parser; + /** reason give to rst_stream */ + uint32_t rst_stream_reason; + /** amount of window given */ + int64_t outgoing_window; + /** number of bytes received - reset at end of parse thread execution */ + int64_t received_bytes; + + /** incoming metadata */ + grpc_chttp2_incoming_metadata_buffer metadata_buffer[2]; +}; + +struct grpc_chttp2_stream { + grpc_stream_refcount *refcount; + grpc_chttp2_stream_global global; + grpc_chttp2_stream_writing writing; + grpc_chttp2_stream_parsing parsing; + + grpc_chttp2_stream_link links[STREAM_LIST_COUNT]; + uint8_t included[STREAM_LIST_COUNT]; +}; + +/** Transport writing call flow: + chttp2_transport.c calls grpc_chttp2_unlocking_check_writes to see if writes + are required; + if they are, chttp2_transport.c calls grpc_chttp2_perform_writes to do the + writes. + Once writes have been completed (meaning another write could potentially be + started), + grpc_chttp2_terminate_writing is called. This will call + grpc_chttp2_cleanup_writing, at which + point the write phase is complete. */ + +/** Someone is unlocking the transport mutex: check to see if writes + are required, and schedule them if so */ +int grpc_chttp2_unlocking_check_writes(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_global *global, + grpc_chttp2_transport_writing *writing, + int is_parsing); +void grpc_chttp2_perform_writes( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_writing *transport_writing, + grpc_endpoint *endpoint); +void grpc_chttp2_terminate_writing(grpc_exec_ctx *exec_ctx, + void *transport_writing, bool success); +void grpc_chttp2_cleanup_writing(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_global *global, + grpc_chttp2_transport_writing *writing); + +void grpc_chttp2_prepare_to_read(grpc_chttp2_transport_global *global, + grpc_chttp2_transport_parsing *parsing); +/** Process one slice of incoming data; return 1 if the connection is still + viable after reading, or 0 if the connection should be torn down */ +int grpc_chttp2_perform_read(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_parsing *transport_parsing, + gpr_slice slice); +void grpc_chttp2_publish_reads(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_global *global, + grpc_chttp2_transport_parsing *parsing); + +bool grpc_chttp2_list_add_writable_stream( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global); +/** Get a writable stream + returns non-zero if there was a stream available */ +int grpc_chttp2_list_pop_writable_stream( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_transport_writing *transport_writing, + grpc_chttp2_stream_global **stream_global, + grpc_chttp2_stream_writing **stream_writing); +bool grpc_chttp2_list_remove_writable_stream( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global) GRPC_MUST_USE_RESULT; + +void grpc_chttp2_list_add_writing_stream( + grpc_chttp2_transport_writing *transport_writing, + grpc_chttp2_stream_writing *stream_writing); +int grpc_chttp2_list_have_writing_streams( + grpc_chttp2_transport_writing *transport_writing); +int grpc_chttp2_list_pop_writing_stream( + grpc_chttp2_transport_writing *transport_writing, + grpc_chttp2_stream_writing **stream_writing); + +void grpc_chttp2_list_add_written_stream( + grpc_chttp2_transport_writing *transport_writing, + grpc_chttp2_stream_writing *stream_writing); +int grpc_chttp2_list_pop_written_stream( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_transport_writing *transport_writing, + grpc_chttp2_stream_global **stream_global, + grpc_chttp2_stream_writing **stream_writing); + +void grpc_chttp2_list_add_parsing_seen_stream( + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_parsing *stream_parsing); +int grpc_chttp2_list_pop_parsing_seen_stream( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_global **stream_global, + grpc_chttp2_stream_parsing **stream_parsing); + +void grpc_chttp2_list_add_waiting_for_concurrency( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global); +int grpc_chttp2_list_pop_waiting_for_concurrency( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global **stream_global); + +void grpc_chttp2_list_add_check_read_ops( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global); +int grpc_chttp2_list_pop_check_read_ops( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global **stream_global); + +void grpc_chttp2_list_add_writing_stalled_by_transport( + grpc_chttp2_transport_writing *transport_writing, + grpc_chttp2_stream_writing *stream_writing); +void grpc_chttp2_list_flush_writing_stalled_by_transport( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_writing *transport_writing, + bool is_window_available); + +void grpc_chttp2_list_add_stalled_by_transport( + grpc_chttp2_transport_writing *transport_writing, + grpc_chttp2_stream_writing *stream_writing); +int grpc_chttp2_list_pop_stalled_by_transport( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global **stream_global); +void grpc_chttp2_list_remove_stalled_by_transport( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global); + +void grpc_chttp2_list_add_unannounced_incoming_window_available( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global); +void grpc_chttp2_list_remove_unannounced_incoming_window_available( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global); +int grpc_chttp2_list_pop_unannounced_incoming_window_available( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_global **stream_global, + grpc_chttp2_stream_parsing **stream_parsing); + +void grpc_chttp2_list_add_closed_waiting_for_parsing( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global); +int grpc_chttp2_list_pop_closed_waiting_for_parsing( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global **stream_global); + +void grpc_chttp2_list_add_closed_waiting_for_writing( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global); +int grpc_chttp2_list_pop_closed_waiting_for_writing( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global **stream_global); + +grpc_chttp2_stream_parsing *grpc_chttp2_parsing_lookup_stream( + grpc_chttp2_transport_parsing *transport_parsing, uint32_t id); +grpc_chttp2_stream_parsing *grpc_chttp2_parsing_accept_stream( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing, + uint32_t id); + +void grpc_chttp2_add_incoming_goaway( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global, + uint32_t goaway_error, gpr_slice goaway_text); + +void grpc_chttp2_register_stream(grpc_chttp2_transport *t, + grpc_chttp2_stream *s); +/* returns 1 if this is the last stream, 0 otherwise */ +int grpc_chttp2_unregister_stream(grpc_chttp2_transport *t, + grpc_chttp2_stream *s) GRPC_MUST_USE_RESULT; +int grpc_chttp2_has_streams(grpc_chttp2_transport *t); +void grpc_chttp2_for_all_streams( + grpc_chttp2_transport_global *transport_global, void *user_data, + void (*cb)(grpc_chttp2_transport_global *transport_global, void *user_data, + grpc_chttp2_stream_global *stream_global)); + +void grpc_chttp2_parsing_become_skip_parser( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing); + +void grpc_chttp2_complete_closure_step(grpc_exec_ctx *exec_ctx, + grpc_closure **pclosure, int success); + +#define GRPC_CHTTP2_CLIENT_CONNECT_STRING "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" +#define GRPC_CHTTP2_CLIENT_CONNECT_STRLEN \ + (sizeof(GRPC_CHTTP2_CLIENT_CONNECT_STRING) - 1) + +extern int grpc_http_trace; +extern int grpc_flowctl_trace; + +#define GRPC_CHTTP2_IF_TRACING(stmt) \ + if (!(grpc_http_trace)) \ + ; \ + else \ + stmt + +typedef enum { + GRPC_CHTTP2_FLOWCTL_MOVE, + GRPC_CHTTP2_FLOWCTL_CREDIT, + GRPC_CHTTP2_FLOWCTL_DEBIT +} grpc_chttp2_flowctl_op; + +#define GRPC_CHTTP2_FLOW_MOVE_COMMON(phase, transport, id1, id2, dst_context, \ + dst_var, src_context, src_var) \ + do { \ + assert(id1 == id2); \ + if (grpc_flowctl_trace) { \ + grpc_chttp2_flowctl_trace( \ + __FILE__, __LINE__, phase, GRPC_CHTTP2_FLOWCTL_MOVE, #dst_context, \ + #dst_var, #src_context, #src_var, transport->is_client, id1, \ + dst_context->dst_var, src_context->src_var); \ + } \ + dst_context->dst_var += src_context->src_var; \ + src_context->src_var = 0; \ + } while (0) + +#define GRPC_CHTTP2_FLOW_MOVE_STREAM(phase, transport, dst_context, dst_var, \ + src_context, src_var) \ + GRPC_CHTTP2_FLOW_MOVE_COMMON(phase, transport, dst_context->id, \ + src_context->id, dst_context, dst_var, \ + src_context, src_var) +#define GRPC_CHTTP2_FLOW_MOVE_TRANSPORT(phase, dst_context, dst_var, \ + src_context, src_var) \ + GRPC_CHTTP2_FLOW_MOVE_COMMON(phase, dst_context, 0, 0, dst_context, dst_var, \ + src_context, src_var) + +#define GRPC_CHTTP2_FLOW_CREDIT_COMMON(phase, transport, id, dst_context, \ + dst_var, amount) \ + do { \ + if (grpc_flowctl_trace) { \ + grpc_chttp2_flowctl_trace(__FILE__, __LINE__, phase, \ + GRPC_CHTTP2_FLOWCTL_CREDIT, #dst_context, \ + #dst_var, NULL, #amount, transport->is_client, \ + id, dst_context->dst_var, amount); \ + } \ + dst_context->dst_var += amount; \ + } while (0) + +#define GRPC_CHTTP2_FLOW_CREDIT_STREAM(phase, transport, dst_context, dst_var, \ + amount) \ + GRPC_CHTTP2_FLOW_CREDIT_COMMON(phase, transport, dst_context->id, \ + dst_context, dst_var, amount) +#define GRPC_CHTTP2_FLOW_CREDIT_TRANSPORT(phase, dst_context, dst_var, amount) \ + GRPC_CHTTP2_FLOW_CREDIT_COMMON(phase, dst_context, 0, dst_context, dst_var, \ + amount) + +#define GRPC_CHTTP2_FLOW_DEBIT_COMMON(phase, transport, id, dst_context, \ + dst_var, amount) \ + do { \ + if (grpc_flowctl_trace) { \ + grpc_chttp2_flowctl_trace(__FILE__, __LINE__, phase, \ + GRPC_CHTTP2_FLOWCTL_DEBIT, #dst_context, \ + #dst_var, NULL, #amount, transport->is_client, \ + id, dst_context->dst_var, amount); \ + } \ + dst_context->dst_var -= amount; \ + } while (0) + +#define GRPC_CHTTP2_FLOW_DEBIT_STREAM(phase, transport, dst_context, dst_var, \ + amount) \ + GRPC_CHTTP2_FLOW_DEBIT_COMMON(phase, transport, dst_context->id, \ + dst_context, dst_var, amount) +#define GRPC_CHTTP2_FLOW_DEBIT_TRANSPORT(phase, dst_context, dst_var, amount) \ + GRPC_CHTTP2_FLOW_DEBIT_COMMON(phase, dst_context, 0, dst_context, dst_var, \ + amount) + +void grpc_chttp2_flowctl_trace(const char *file, int line, const char *phase, + grpc_chttp2_flowctl_op op, const char *context1, + const char *var1, const char *context2, + const char *var2, int is_client, + uint32_t stream_id, int64_t val1, int64_t val2); + +void grpc_chttp2_fake_status(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream, + grpc_status_code status, gpr_slice *details); +void grpc_chttp2_mark_stream_closed( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global, int close_reads, + int close_writes); +void grpc_chttp2_start_writing(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_global *transport_global); + +#ifdef GRPC_STREAM_REFCOUNT_DEBUG +#define GRPC_CHTTP2_STREAM_REF(stream_global, reason) \ + grpc_chttp2_stream_ref(stream_global, reason) +#define GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, reason) \ + grpc_chttp2_stream_unref(exec_ctx, stream_global, reason) +void grpc_chttp2_stream_ref(grpc_chttp2_stream_global *stream_global, + const char *reason); +void grpc_chttp2_stream_unref(grpc_exec_ctx *exec_ctx, + grpc_chttp2_stream_global *stream_global, + const char *reason); +#else +#define GRPC_CHTTP2_STREAM_REF(stream_global, reason) \ + grpc_chttp2_stream_ref(stream_global) +#define GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, reason) \ + grpc_chttp2_stream_unref(exec_ctx, stream_global) +void grpc_chttp2_stream_ref(grpc_chttp2_stream_global *stream_global); +void grpc_chttp2_stream_unref(grpc_exec_ctx *exec_ctx, + grpc_chttp2_stream_global *stream_global); +#endif + +grpc_chttp2_incoming_byte_stream *grpc_chttp2_incoming_byte_stream_create( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_parsing *stream_parsing, uint32_t frame_size, + uint32_t flags, grpc_chttp2_incoming_frame_queue *add_to_queue); +void grpc_chttp2_incoming_byte_stream_push(grpc_exec_ctx *exec_ctx, + grpc_chttp2_incoming_byte_stream *bs, + gpr_slice slice); +void grpc_chttp2_incoming_byte_stream_finished( + grpc_exec_ctx *exec_ctx, grpc_chttp2_incoming_byte_stream *bs, int success, + int from_parsing_thread); + +void grpc_chttp2_ack_ping(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_parsing *parsing, + const uint8_t *opaque_8bytes); + +/** add a ref to the stream and add it to the writable list; + ref will be dropped in writing.c */ +void grpc_chttp2_become_writable(grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global); + +#endif /* GRPC_CORE_LIB_TRANSPORT_CHTTP2_INTERNAL_H */ diff --git a/src/core/lib/transport/chttp2/parsing.c b/src/core/lib/transport/chttp2/parsing.c new file mode 100644 index 0000000000..9ee52f63f2 --- /dev/null +++ b/src/core/lib/transport/chttp2/parsing.c @@ -0,0 +1,866 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/chttp2/internal.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> + +#include "src/core/lib/profiling/timers.h" +#include "src/core/lib/transport/chttp2/http2_errors.h" +#include "src/core/lib/transport/chttp2/status_conversion.h" +#include "src/core/lib/transport/chttp2/timeout_encoding.h" +#include "src/core/lib/transport/static_metadata.h" + +static int init_frame_parser(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_parsing *transport_parsing); +static int init_header_frame_parser( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing, + int is_continuation); +static int init_data_frame_parser( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing); +static int init_rst_stream_parser( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing); +static int init_settings_frame_parser( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing); +static int init_window_update_frame_parser( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing); +static int init_ping_parser(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_parsing *transport_parsing); +static int init_goaway_parser(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_parsing *transport_parsing); +static int init_skip_frame_parser( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing, + int is_header); + +static int parse_frame_slice(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_parsing *transport_parsing, + gpr_slice slice, int is_last); + +void grpc_chttp2_prepare_to_read( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_transport_parsing *transport_parsing) { + grpc_chttp2_stream_global *stream_global; + grpc_chttp2_stream_parsing *stream_parsing; + + GPR_TIMER_BEGIN("grpc_chttp2_prepare_to_read", 0); + + transport_parsing->next_stream_id = transport_global->next_stream_id; + transport_parsing->last_sent_max_table_size = + transport_global->settings[GRPC_SENT_SETTINGS] + [GRPC_CHTTP2_SETTINGS_HEADER_TABLE_SIZE]; + + /* update the parsing view of incoming window */ + while (grpc_chttp2_list_pop_unannounced_incoming_window_available( + transport_global, transport_parsing, &stream_global, &stream_parsing)) { + GRPC_CHTTP2_FLOW_MOVE_STREAM("parse", transport_parsing, stream_parsing, + incoming_window, stream_global, + unannounced_incoming_window_for_parse); + } + + GPR_TIMER_END("grpc_chttp2_prepare_to_read", 0); +} + +void grpc_chttp2_publish_reads( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global, + grpc_chttp2_transport_parsing *transport_parsing) { + grpc_chttp2_stream_global *stream_global; + grpc_chttp2_stream_parsing *stream_parsing; + int was_zero; + int is_zero; + + /* transport_parsing->last_incoming_stream_id is used as + last-grpc_chttp2_stream-id when + sending GOAWAY frame. + https://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-6.8 + says that last-grpc_chttp2_stream-id is peer-initiated grpc_chttp2_stream + ID. So, + since we don't have server pushed streams, client should send + GOAWAY last-grpc_chttp2_stream-id=0 in this case. */ + if (!transport_parsing->is_client) { + transport_global->last_incoming_stream_id = + transport_parsing->incoming_stream_id; + } + + /* update global settings */ + if (transport_parsing->settings_updated) { + memcpy(transport_global->settings[GRPC_PEER_SETTINGS], + transport_parsing->settings, sizeof(transport_parsing->settings)); + transport_parsing->settings_updated = 0; + } + + /* update settings based on ack if received */ + if (transport_parsing->settings_ack_received) { + memcpy(transport_global->settings[GRPC_ACKED_SETTINGS], + transport_global->settings[GRPC_SENT_SETTINGS], + GRPC_CHTTP2_NUM_SETTINGS * sizeof(uint32_t)); + transport_parsing->settings_ack_received = 0; + transport_global->sent_local_settings = 0; + } + + /* move goaway to the global state if we received one (it will be + published later */ + if (transport_parsing->goaway_received) { + grpc_chttp2_add_incoming_goaway(exec_ctx, transport_global, + (uint32_t)transport_parsing->goaway_error, + transport_parsing->goaway_text); + transport_parsing->goaway_text = gpr_empty_slice(); + transport_parsing->goaway_received = 0; + } + + /* propagate flow control tokens to global state */ + was_zero = transport_global->outgoing_window <= 0; + GRPC_CHTTP2_FLOW_MOVE_TRANSPORT("parsed", transport_global, outgoing_window, + transport_parsing, outgoing_window); + is_zero = transport_global->outgoing_window <= 0; + if (was_zero && !is_zero) { + while (grpc_chttp2_list_pop_stalled_by_transport(transport_global, + &stream_global)) { + grpc_chttp2_become_writable(transport_global, stream_global); + } + } + + if (transport_parsing->incoming_window < + transport_global->connection_window_target * 3 / 4) { + int64_t announce_bytes = transport_global->connection_window_target - + transport_parsing->incoming_window; + GRPC_CHTTP2_FLOW_CREDIT_TRANSPORT("parsed", transport_global, + announce_incoming_window, announce_bytes); + GRPC_CHTTP2_FLOW_CREDIT_TRANSPORT("parsed", transport_parsing, + incoming_window, announce_bytes); + } + + /* for each stream that saw an update, fixup global state */ + while (grpc_chttp2_list_pop_parsing_seen_stream( + transport_global, transport_parsing, &stream_global, &stream_parsing)) { + if (stream_parsing->seen_error) { + stream_global->seen_error = 1; + grpc_chttp2_list_add_check_read_ops(transport_global, stream_global); + } + + /* update outgoing flow control window */ + was_zero = stream_global->outgoing_window <= 0; + GRPC_CHTTP2_FLOW_MOVE_STREAM("parsed", transport_global, stream_global, + outgoing_window, stream_parsing, + outgoing_window); + is_zero = stream_global->outgoing_window <= 0; + if (was_zero && !is_zero) { + grpc_chttp2_become_writable(transport_global, stream_global); + } + + stream_global->max_recv_bytes -= (uint32_t)GPR_MIN( + stream_global->max_recv_bytes, stream_parsing->received_bytes); + stream_parsing->received_bytes = 0; + + /* publish incoming stream ops */ + if (stream_global->incoming_frames.tail != NULL) { + stream_global->incoming_frames.tail->is_tail = 0; + } + if (stream_parsing->data_parser.incoming_frames.head != NULL) { + grpc_chttp2_list_add_check_read_ops(transport_global, stream_global); + } + grpc_chttp2_incoming_frame_queue_merge( + &stream_global->incoming_frames, + &stream_parsing->data_parser.incoming_frames); + if (stream_global->incoming_frames.tail != NULL) { + stream_global->incoming_frames.tail->is_tail = 1; + } + + if (!stream_global->published_initial_metadata && + stream_parsing->got_metadata_on_parse[0]) { + stream_parsing->got_metadata_on_parse[0] = 0; + stream_global->published_initial_metadata = 1; + GPR_SWAP(grpc_chttp2_incoming_metadata_buffer, + stream_parsing->metadata_buffer[0], + stream_global->received_initial_metadata); + grpc_chttp2_list_add_check_read_ops(transport_global, stream_global); + } + if (!stream_global->published_trailing_metadata && + stream_parsing->got_metadata_on_parse[1]) { + stream_parsing->got_metadata_on_parse[1] = 0; + stream_global->published_trailing_metadata = 1; + GPR_SWAP(grpc_chttp2_incoming_metadata_buffer, + stream_parsing->metadata_buffer[1], + stream_global->received_trailing_metadata); + grpc_chttp2_list_add_check_read_ops(transport_global, stream_global); + } + + if (stream_parsing->saw_rst_stream) { + if (stream_parsing->rst_stream_reason != GRPC_CHTTP2_NO_ERROR) { + grpc_status_code status_code = grpc_chttp2_http2_error_to_grpc_status( + (grpc_chttp2_error_code)stream_parsing->rst_stream_reason); + char *status_details; + gpr_slice slice_details; + gpr_asprintf(&status_details, "Received RST_STREAM err=%d", + stream_parsing->rst_stream_reason); + slice_details = gpr_slice_from_copied_string(status_details); + gpr_free(status_details); + grpc_chttp2_fake_status(exec_ctx, transport_global, stream_global, + status_code, &slice_details); + } + grpc_chttp2_mark_stream_closed(exec_ctx, transport_global, stream_global, + 1, 1); + } + + if (stream_parsing->received_close) { + grpc_chttp2_mark_stream_closed(exec_ctx, transport_global, stream_global, + 1, 0); + } + } +} + +int grpc_chttp2_perform_read(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_parsing *transport_parsing, + gpr_slice slice) { + uint8_t *beg = GPR_SLICE_START_PTR(slice); + uint8_t *end = GPR_SLICE_END_PTR(slice); + uint8_t *cur = beg; + + if (cur == end) return 1; + + switch (transport_parsing->deframe_state) { + case GRPC_DTS_CLIENT_PREFIX_0: + case GRPC_DTS_CLIENT_PREFIX_1: + case GRPC_DTS_CLIENT_PREFIX_2: + case GRPC_DTS_CLIENT_PREFIX_3: + case GRPC_DTS_CLIENT_PREFIX_4: + case GRPC_DTS_CLIENT_PREFIX_5: + case GRPC_DTS_CLIENT_PREFIX_6: + case GRPC_DTS_CLIENT_PREFIX_7: + case GRPC_DTS_CLIENT_PREFIX_8: + case GRPC_DTS_CLIENT_PREFIX_9: + case GRPC_DTS_CLIENT_PREFIX_10: + case GRPC_DTS_CLIENT_PREFIX_11: + case GRPC_DTS_CLIENT_PREFIX_12: + case GRPC_DTS_CLIENT_PREFIX_13: + case GRPC_DTS_CLIENT_PREFIX_14: + case GRPC_DTS_CLIENT_PREFIX_15: + case GRPC_DTS_CLIENT_PREFIX_16: + case GRPC_DTS_CLIENT_PREFIX_17: + case GRPC_DTS_CLIENT_PREFIX_18: + case GRPC_DTS_CLIENT_PREFIX_19: + case GRPC_DTS_CLIENT_PREFIX_20: + case GRPC_DTS_CLIENT_PREFIX_21: + case GRPC_DTS_CLIENT_PREFIX_22: + case GRPC_DTS_CLIENT_PREFIX_23: + while (cur != end && transport_parsing->deframe_state != GRPC_DTS_FH_0) { + if (*cur != GRPC_CHTTP2_CLIENT_CONNECT_STRING[transport_parsing + ->deframe_state]) { + gpr_log(GPR_INFO, + "Connect string mismatch: expected '%c' (%d) got '%c' (%d) " + "at byte %d", + GRPC_CHTTP2_CLIENT_CONNECT_STRING[transport_parsing + ->deframe_state], + (int)(uint8_t)GRPC_CHTTP2_CLIENT_CONNECT_STRING + [transport_parsing->deframe_state], + *cur, (int)*cur, transport_parsing->deframe_state); + return 0; + } + ++cur; + ++transport_parsing->deframe_state; + } + if (cur == end) { + return 1; + } + /* fallthrough */ + dts_fh_0: + case GRPC_DTS_FH_0: + GPR_ASSERT(cur < end); + transport_parsing->incoming_frame_size = ((uint32_t)*cur) << 16; + if (++cur == end) { + transport_parsing->deframe_state = GRPC_DTS_FH_1; + return 1; + } + /* fallthrough */ + case GRPC_DTS_FH_1: + GPR_ASSERT(cur < end); + transport_parsing->incoming_frame_size |= ((uint32_t)*cur) << 8; + if (++cur == end) { + transport_parsing->deframe_state = GRPC_DTS_FH_2; + return 1; + } + /* fallthrough */ + case GRPC_DTS_FH_2: + GPR_ASSERT(cur < end); + transport_parsing->incoming_frame_size |= *cur; + if (++cur == end) { + transport_parsing->deframe_state = GRPC_DTS_FH_3; + return 1; + } + /* fallthrough */ + case GRPC_DTS_FH_3: + GPR_ASSERT(cur < end); + transport_parsing->incoming_frame_type = *cur; + if (++cur == end) { + transport_parsing->deframe_state = GRPC_DTS_FH_4; + return 1; + } + /* fallthrough */ + case GRPC_DTS_FH_4: + GPR_ASSERT(cur < end); + transport_parsing->incoming_frame_flags = *cur; + if (++cur == end) { + transport_parsing->deframe_state = GRPC_DTS_FH_5; + return 1; + } + /* fallthrough */ + case GRPC_DTS_FH_5: + GPR_ASSERT(cur < end); + transport_parsing->incoming_stream_id = (((uint32_t)*cur) & 0x7f) << 24; + if (++cur == end) { + transport_parsing->deframe_state = GRPC_DTS_FH_6; + return 1; + } + /* fallthrough */ + case GRPC_DTS_FH_6: + GPR_ASSERT(cur < end); + transport_parsing->incoming_stream_id |= ((uint32_t)*cur) << 16; + if (++cur == end) { + transport_parsing->deframe_state = GRPC_DTS_FH_7; + return 1; + } + /* fallthrough */ + case GRPC_DTS_FH_7: + GPR_ASSERT(cur < end); + transport_parsing->incoming_stream_id |= ((uint32_t)*cur) << 8; + if (++cur == end) { + transport_parsing->deframe_state = GRPC_DTS_FH_8; + return 1; + } + /* fallthrough */ + case GRPC_DTS_FH_8: + GPR_ASSERT(cur < end); + transport_parsing->incoming_stream_id |= ((uint32_t)*cur); + transport_parsing->deframe_state = GRPC_DTS_FRAME; + if (!init_frame_parser(exec_ctx, transport_parsing)) { + return 0; + } + if (transport_parsing->incoming_stream_id) { + transport_parsing->last_incoming_stream_id = + transport_parsing->incoming_stream_id; + } + if (transport_parsing->incoming_frame_size == 0) { + if (!parse_frame_slice(exec_ctx, transport_parsing, gpr_empty_slice(), + 1)) { + return 0; + } + transport_parsing->incoming_stream = NULL; + if (++cur == end) { + transport_parsing->deframe_state = GRPC_DTS_FH_0; + return 1; + } + goto dts_fh_0; /* loop */ + } + if (++cur == end) { + return 1; + } + /* fallthrough */ + case GRPC_DTS_FRAME: + GPR_ASSERT(cur < end); + if ((uint32_t)(end - cur) == transport_parsing->incoming_frame_size) { + if (!parse_frame_slice(exec_ctx, transport_parsing, + gpr_slice_sub_no_ref(slice, (size_t)(cur - beg), + (size_t)(end - beg)), + 1)) { + return 0; + } + transport_parsing->deframe_state = GRPC_DTS_FH_0; + transport_parsing->incoming_stream = NULL; + return 1; + } else if ((uint32_t)(end - cur) > + transport_parsing->incoming_frame_size) { + size_t cur_offset = (size_t)(cur - beg); + if (!parse_frame_slice( + exec_ctx, transport_parsing, + gpr_slice_sub_no_ref( + slice, cur_offset, + cur_offset + transport_parsing->incoming_frame_size), + 1)) { + return 0; + } + cur += transport_parsing->incoming_frame_size; + transport_parsing->incoming_stream = NULL; + goto dts_fh_0; /* loop */ + } else { + if (!parse_frame_slice(exec_ctx, transport_parsing, + gpr_slice_sub_no_ref(slice, (size_t)(cur - beg), + (size_t)(end - beg)), + 0)) { + return 0; + } + transport_parsing->incoming_frame_size -= (uint32_t)(end - cur); + return 1; + } + GPR_UNREACHABLE_CODE(return 0); + } + + GPR_UNREACHABLE_CODE(return 0); +} + +static int init_frame_parser(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_parsing *transport_parsing) { + if (transport_parsing->expect_continuation_stream_id != 0) { + if (transport_parsing->incoming_frame_type != + GRPC_CHTTP2_FRAME_CONTINUATION) { + gpr_log(GPR_ERROR, "Expected CONTINUATION frame, got frame type %02x", + transport_parsing->incoming_frame_type); + return 0; + } + if (transport_parsing->expect_continuation_stream_id != + transport_parsing->incoming_stream_id) { + gpr_log(GPR_ERROR, + "Expected CONTINUATION frame for grpc_chttp2_stream %08x, got " + "grpc_chttp2_stream %08x", + transport_parsing->expect_continuation_stream_id, + transport_parsing->incoming_stream_id); + return 0; + } + return init_header_frame_parser(exec_ctx, transport_parsing, 1); + } + switch (transport_parsing->incoming_frame_type) { + case GRPC_CHTTP2_FRAME_DATA: + return init_data_frame_parser(exec_ctx, transport_parsing); + case GRPC_CHTTP2_FRAME_HEADER: + return init_header_frame_parser(exec_ctx, transport_parsing, 0); + case GRPC_CHTTP2_FRAME_CONTINUATION: + gpr_log(GPR_ERROR, "Unexpected CONTINUATION frame"); + return 0; + case GRPC_CHTTP2_FRAME_RST_STREAM: + return init_rst_stream_parser(exec_ctx, transport_parsing); + case GRPC_CHTTP2_FRAME_SETTINGS: + return init_settings_frame_parser(exec_ctx, transport_parsing); + case GRPC_CHTTP2_FRAME_WINDOW_UPDATE: + return init_window_update_frame_parser(exec_ctx, transport_parsing); + case GRPC_CHTTP2_FRAME_PING: + return init_ping_parser(exec_ctx, transport_parsing); + case GRPC_CHTTP2_FRAME_GOAWAY: + return init_goaway_parser(exec_ctx, transport_parsing); + default: + gpr_log(GPR_ERROR, "Unknown frame type %02x", + transport_parsing->incoming_frame_type); + return init_skip_frame_parser(exec_ctx, transport_parsing, 0); + } +} + +static grpc_chttp2_parse_error skip_parser( + grpc_exec_ctx *exec_ctx, void *parser, + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_parsing *stream_parsing, gpr_slice slice, int is_last) { + return GRPC_CHTTP2_PARSE_OK; +} + +static void skip_header(void *tp, grpc_mdelem *md) { GRPC_MDELEM_UNREF(md); } + +static int init_skip_frame_parser( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing, + int is_header) { + if (is_header) { + uint8_t is_eoh = transport_parsing->expect_continuation_stream_id != 0; + transport_parsing->parser = grpc_chttp2_header_parser_parse; + transport_parsing->parser_data = &transport_parsing->hpack_parser; + transport_parsing->hpack_parser.on_header = skip_header; + transport_parsing->hpack_parser.on_header_user_data = NULL; + transport_parsing->hpack_parser.is_boundary = is_eoh; + transport_parsing->hpack_parser.is_eof = + (uint8_t)(is_eoh ? transport_parsing->header_eof : 0); + } else { + transport_parsing->parser = skip_parser; + } + return 1; +} + +void grpc_chttp2_parsing_become_skip_parser( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing) { + init_skip_frame_parser( + exec_ctx, transport_parsing, + transport_parsing->parser == grpc_chttp2_header_parser_parse); +} + +static grpc_chttp2_parse_error update_incoming_window( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_parsing *stream_parsing) { + uint32_t incoming_frame_size = transport_parsing->incoming_frame_size; + if (incoming_frame_size > transport_parsing->incoming_window) { + gpr_log(GPR_ERROR, "frame of size %d overflows incoming window of %d", + transport_parsing->incoming_frame_size, + transport_parsing->incoming_window); + return GRPC_CHTTP2_CONNECTION_ERROR; + } + + if (incoming_frame_size > stream_parsing->incoming_window) { + gpr_log(GPR_ERROR, "frame of size %d overflows incoming window of %d", + transport_parsing->incoming_frame_size, + stream_parsing->incoming_window); + return GRPC_CHTTP2_CONNECTION_ERROR; + } + + GRPC_CHTTP2_FLOW_DEBIT_TRANSPORT("parse", transport_parsing, incoming_window, + incoming_frame_size); + GRPC_CHTTP2_FLOW_DEBIT_STREAM("parse", transport_parsing, stream_parsing, + incoming_window, incoming_frame_size); + stream_parsing->received_bytes += incoming_frame_size; + + grpc_chttp2_list_add_parsing_seen_stream(transport_parsing, stream_parsing); + + return GRPC_CHTTP2_PARSE_OK; +} + +static int init_data_frame_parser( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing) { + grpc_chttp2_stream_parsing *stream_parsing = + grpc_chttp2_parsing_lookup_stream(transport_parsing, + transport_parsing->incoming_stream_id); + grpc_chttp2_parse_error err = GRPC_CHTTP2_PARSE_OK; + if (!stream_parsing || stream_parsing->received_close) + return init_skip_frame_parser(exec_ctx, transport_parsing, 0); + if (err == GRPC_CHTTP2_PARSE_OK) { + err = update_incoming_window(exec_ctx, transport_parsing, stream_parsing); + } + if (err == GRPC_CHTTP2_PARSE_OK) { + err = grpc_chttp2_data_parser_begin_frame( + &stream_parsing->data_parser, transport_parsing->incoming_frame_flags); + } + switch (err) { + case GRPC_CHTTP2_PARSE_OK: + transport_parsing->incoming_stream = stream_parsing; + transport_parsing->parser = grpc_chttp2_data_parser_parse; + transport_parsing->parser_data = &stream_parsing->data_parser; + return 1; + case GRPC_CHTTP2_STREAM_ERROR: + stream_parsing->received_close = 1; + stream_parsing->saw_rst_stream = 1; + stream_parsing->rst_stream_reason = GRPC_CHTTP2_PROTOCOL_ERROR; + gpr_slice_buffer_add( + &transport_parsing->qbuf, + grpc_chttp2_rst_stream_create(transport_parsing->incoming_stream_id, + GRPC_CHTTP2_PROTOCOL_ERROR)); + return init_skip_frame_parser(exec_ctx, transport_parsing, 0); + case GRPC_CHTTP2_CONNECTION_ERROR: + return 0; + } + GPR_UNREACHABLE_CODE(return 0); +} + +static void free_timeout(void *p) { gpr_free(p); } + +static void on_initial_header(void *tp, grpc_mdelem *md) { + grpc_chttp2_transport_parsing *transport_parsing = tp; + grpc_chttp2_stream_parsing *stream_parsing = + transport_parsing->incoming_stream; + + GPR_TIMER_BEGIN("on_initial_header", 0); + + GPR_ASSERT(stream_parsing); + + GRPC_CHTTP2_IF_TRACING(gpr_log( + GPR_INFO, "HTTP:%d:HDR:%s: %s: %s", stream_parsing->id, + transport_parsing->is_client ? "CLI" : "SVR", + grpc_mdstr_as_c_string(md->key), grpc_mdstr_as_c_string(md->value))); + + if (md->key == GRPC_MDSTR_GRPC_STATUS && md != GRPC_MDELEM_GRPC_STATUS_0) { + /* TODO(ctiller): check for a status like " 0" */ + stream_parsing->seen_error = 1; + } + + if (md->key == GRPC_MDSTR_GRPC_TIMEOUT) { + gpr_timespec *cached_timeout = grpc_mdelem_get_user_data(md, free_timeout); + if (!cached_timeout) { + /* not already parsed: parse it now, and store the result away */ + cached_timeout = gpr_malloc(sizeof(gpr_timespec)); + if (!grpc_chttp2_decode_timeout(grpc_mdstr_as_c_string(md->value), + cached_timeout)) { + gpr_log(GPR_ERROR, "Ignoring bad timeout value '%s'", + grpc_mdstr_as_c_string(md->value)); + *cached_timeout = gpr_inf_future(GPR_TIMESPAN); + } + grpc_mdelem_set_user_data(md, free_timeout, cached_timeout); + } + grpc_chttp2_incoming_metadata_buffer_set_deadline( + &stream_parsing->metadata_buffer[0], + gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC), *cached_timeout)); + GRPC_MDELEM_UNREF(md); + } else { + grpc_chttp2_incoming_metadata_buffer_add( + &stream_parsing->metadata_buffer[0], md); + } + + grpc_chttp2_list_add_parsing_seen_stream(transport_parsing, stream_parsing); + + GPR_TIMER_END("on_initial_header", 0); +} + +static void on_trailing_header(void *tp, grpc_mdelem *md) { + grpc_chttp2_transport_parsing *transport_parsing = tp; + grpc_chttp2_stream_parsing *stream_parsing = + transport_parsing->incoming_stream; + + GPR_TIMER_BEGIN("on_trailing_header", 0); + + GPR_ASSERT(stream_parsing); + + GRPC_CHTTP2_IF_TRACING(gpr_log( + GPR_INFO, "HTTP:%d:TRL:%s: %s: %s", stream_parsing->id, + transport_parsing->is_client ? "CLI" : "SVR", + grpc_mdstr_as_c_string(md->key), grpc_mdstr_as_c_string(md->value))); + + if (md->key == GRPC_MDSTR_GRPC_STATUS && md != GRPC_MDELEM_GRPC_STATUS_0) { + /* TODO(ctiller): check for a status like " 0" */ + stream_parsing->seen_error = 1; + } + + grpc_chttp2_incoming_metadata_buffer_add(&stream_parsing->metadata_buffer[1], + md); + + grpc_chttp2_list_add_parsing_seen_stream(transport_parsing, stream_parsing); + + GPR_TIMER_END("on_trailing_header", 0); +} + +static int init_header_frame_parser( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing, + int is_continuation) { + uint8_t is_eoh = (transport_parsing->incoming_frame_flags & + GRPC_CHTTP2_DATA_FLAG_END_HEADERS) != 0; + int via_accept = 0; + grpc_chttp2_stream_parsing *stream_parsing; + + /* TODO(ctiller): when to increment header_frames_received? */ + + if (is_eoh) { + transport_parsing->expect_continuation_stream_id = 0; + } else { + transport_parsing->expect_continuation_stream_id = + transport_parsing->incoming_stream_id; + } + + if (!is_continuation) { + transport_parsing->header_eof = (transport_parsing->incoming_frame_flags & + GRPC_CHTTP2_DATA_FLAG_END_STREAM) != 0; + } + + /* could be a new grpc_chttp2_stream or an existing grpc_chttp2_stream */ + stream_parsing = grpc_chttp2_parsing_lookup_stream( + transport_parsing, transport_parsing->incoming_stream_id); + if (stream_parsing == NULL) { + if (is_continuation) { + gpr_log(GPR_ERROR, + "grpc_chttp2_stream disbanded before CONTINUATION received"); + return init_skip_frame_parser(exec_ctx, transport_parsing, 1); + } + if (transport_parsing->is_client) { + if ((transport_parsing->incoming_stream_id & 1) && + transport_parsing->incoming_stream_id < + transport_parsing->next_stream_id) { + /* this is an old (probably cancelled) grpc_chttp2_stream */ + } else { + gpr_log(GPR_ERROR, + "ignoring new grpc_chttp2_stream creation on client"); + } + return init_skip_frame_parser(exec_ctx, transport_parsing, 1); + } else if (transport_parsing->last_incoming_stream_id > + transport_parsing->incoming_stream_id) { + gpr_log(GPR_ERROR, + "ignoring out of order new grpc_chttp2_stream request on server; " + "last grpc_chttp2_stream " + "id=%d, new grpc_chttp2_stream id=%d", + transport_parsing->last_incoming_stream_id, + transport_parsing->incoming_stream_id); + return init_skip_frame_parser(exec_ctx, transport_parsing, 1); + } else if ((transport_parsing->incoming_stream_id & 1) == 0) { + gpr_log(GPR_ERROR, + "ignoring grpc_chttp2_stream with non-client generated index %d", + transport_parsing->incoming_stream_id); + return init_skip_frame_parser(exec_ctx, transport_parsing, 1); + } + stream_parsing = transport_parsing->incoming_stream = + grpc_chttp2_parsing_accept_stream( + exec_ctx, transport_parsing, transport_parsing->incoming_stream_id); + if (stream_parsing == NULL) { + gpr_log(GPR_ERROR, "grpc_chttp2_stream not accepted"); + return init_skip_frame_parser(exec_ctx, transport_parsing, 1); + } + via_accept = 1; + } else { + transport_parsing->incoming_stream = stream_parsing; + } + GPR_ASSERT(stream_parsing != NULL && (via_accept == 0 || via_accept == 1)); + if (stream_parsing->received_close) { + gpr_log(GPR_ERROR, "skipping already closed grpc_chttp2_stream header"); + transport_parsing->incoming_stream = NULL; + return init_skip_frame_parser(exec_ctx, transport_parsing, 1); + } + transport_parsing->parser = grpc_chttp2_header_parser_parse; + transport_parsing->parser_data = &transport_parsing->hpack_parser; + switch (stream_parsing->header_frames_received) { + case 0: + transport_parsing->hpack_parser.on_header = on_initial_header; + break; + case 1: + transport_parsing->hpack_parser.on_header = on_trailing_header; + break; + case 2: + gpr_log(GPR_ERROR, "too many header frames received"); + return init_skip_frame_parser(exec_ctx, transport_parsing, 1); + } + transport_parsing->hpack_parser.on_header_user_data = transport_parsing; + transport_parsing->hpack_parser.is_boundary = is_eoh; + transport_parsing->hpack_parser.is_eof = + (uint8_t)(is_eoh ? transport_parsing->header_eof : 0); + if (!is_continuation && (transport_parsing->incoming_frame_flags & + GRPC_CHTTP2_FLAG_HAS_PRIORITY)) { + grpc_chttp2_hpack_parser_set_has_priority(&transport_parsing->hpack_parser); + } + return 1; +} + +static int init_window_update_frame_parser( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing) { + int ok = GRPC_CHTTP2_PARSE_OK == grpc_chttp2_window_update_parser_begin_frame( + &transport_parsing->simple.window_update, + transport_parsing->incoming_frame_size, + transport_parsing->incoming_frame_flags); + if (transport_parsing->incoming_stream_id) { + transport_parsing->incoming_stream = grpc_chttp2_parsing_lookup_stream( + transport_parsing, transport_parsing->incoming_stream_id); + } + transport_parsing->parser = grpc_chttp2_window_update_parser_parse; + transport_parsing->parser_data = &transport_parsing->simple.window_update; + return ok; +} + +static int init_ping_parser(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_parsing *transport_parsing) { + int ok = GRPC_CHTTP2_PARSE_OK == grpc_chttp2_ping_parser_begin_frame( + &transport_parsing->simple.ping, + transport_parsing->incoming_frame_size, + transport_parsing->incoming_frame_flags); + transport_parsing->parser = grpc_chttp2_ping_parser_parse; + transport_parsing->parser_data = &transport_parsing->simple.ping; + return ok; +} + +static int init_rst_stream_parser( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing) { + int ok = GRPC_CHTTP2_PARSE_OK == grpc_chttp2_rst_stream_parser_begin_frame( + &transport_parsing->simple.rst_stream, + transport_parsing->incoming_frame_size, + transport_parsing->incoming_frame_flags); + transport_parsing->incoming_stream = grpc_chttp2_parsing_lookup_stream( + transport_parsing, transport_parsing->incoming_stream_id); + if (!transport_parsing->incoming_stream) { + return init_skip_frame_parser(exec_ctx, transport_parsing, 0); + } + transport_parsing->parser = grpc_chttp2_rst_stream_parser_parse; + transport_parsing->parser_data = &transport_parsing->simple.rst_stream; + return ok; +} + +static int init_goaway_parser( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing) { + int ok = GRPC_CHTTP2_PARSE_OK == grpc_chttp2_goaway_parser_begin_frame( + &transport_parsing->goaway_parser, + transport_parsing->incoming_frame_size, + transport_parsing->incoming_frame_flags); + transport_parsing->parser = grpc_chttp2_goaway_parser_parse; + transport_parsing->parser_data = &transport_parsing->goaway_parser; + return ok; +} + +static int init_settings_frame_parser( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing) { + int ok; + + if (transport_parsing->incoming_stream_id != 0) { + gpr_log(GPR_ERROR, "settings frame received for grpc_chttp2_stream %d", + transport_parsing->incoming_stream_id); + return 0; + } + + ok = GRPC_CHTTP2_PARSE_OK == grpc_chttp2_settings_parser_begin_frame( + &transport_parsing->simple.settings, + transport_parsing->incoming_frame_size, + transport_parsing->incoming_frame_flags, + transport_parsing->settings); + if (!ok) { + return 0; + } + if (transport_parsing->incoming_frame_flags & GRPC_CHTTP2_FLAG_ACK) { + transport_parsing->settings_ack_received = 1; + grpc_chttp2_hptbl_set_max_bytes( + &transport_parsing->hpack_parser.table, + transport_parsing->last_sent_max_table_size); + } + transport_parsing->parser = grpc_chttp2_settings_parser_parse; + transport_parsing->parser_data = &transport_parsing->simple.settings; + return ok; +} + +/* +static int is_window_update_legal(int64_t window_update, int64_t window) { + return window + window_update < MAX_WINDOW; +} +*/ + +static int parse_frame_slice(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_parsing *transport_parsing, + gpr_slice slice, int is_last) { + grpc_chttp2_stream_parsing *stream_parsing = + transport_parsing->incoming_stream; + switch (transport_parsing->parser(exec_ctx, transport_parsing->parser_data, + transport_parsing, stream_parsing, slice, + is_last)) { + case GRPC_CHTTP2_PARSE_OK: + if (stream_parsing) { + grpc_chttp2_list_add_parsing_seen_stream(transport_parsing, + stream_parsing); + } + return 1; + case GRPC_CHTTP2_STREAM_ERROR: + grpc_chttp2_parsing_become_skip_parser(exec_ctx, transport_parsing); + if (stream_parsing) { + stream_parsing->saw_rst_stream = 1; + stream_parsing->rst_stream_reason = GRPC_CHTTP2_PROTOCOL_ERROR; + gpr_slice_buffer_add( + &transport_parsing->qbuf, + grpc_chttp2_rst_stream_create(transport_parsing->incoming_stream_id, + GRPC_CHTTP2_PROTOCOL_ERROR)); + } + return 1; + case GRPC_CHTTP2_CONNECTION_ERROR: + return 0; + } + GPR_UNREACHABLE_CODE(return 0); +} diff --git a/src/core/lib/transport/chttp2/status_conversion.c b/src/core/lib/transport/chttp2/status_conversion.c new file mode 100644 index 0000000000..73dd63e720 --- /dev/null +++ b/src/core/lib/transport/chttp2/status_conversion.c @@ -0,0 +1,109 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/chttp2/status_conversion.h" + +int grpc_chttp2_grpc_status_to_http2_error(grpc_status_code status) { + switch (status) { + case GRPC_STATUS_OK: + return GRPC_CHTTP2_NO_ERROR; + case GRPC_STATUS_CANCELLED: + return GRPC_CHTTP2_CANCEL; + case GRPC_STATUS_RESOURCE_EXHAUSTED: + return GRPC_CHTTP2_ENHANCE_YOUR_CALM; + case GRPC_STATUS_PERMISSION_DENIED: + return GRPC_CHTTP2_INADEQUATE_SECURITY; + case GRPC_STATUS_UNAVAILABLE: + return GRPC_CHTTP2_REFUSED_STREAM; + default: + return GRPC_CHTTP2_INTERNAL_ERROR; + } +} + +grpc_status_code grpc_chttp2_http2_error_to_grpc_status( + grpc_chttp2_error_code error) { + switch (error) { + case GRPC_CHTTP2_NO_ERROR: + /* should never be received */ + return GRPC_STATUS_INTERNAL; + case GRPC_CHTTP2_CANCEL: + return GRPC_STATUS_CANCELLED; + case GRPC_CHTTP2_ENHANCE_YOUR_CALM: + return GRPC_STATUS_RESOURCE_EXHAUSTED; + case GRPC_CHTTP2_INADEQUATE_SECURITY: + return GRPC_STATUS_PERMISSION_DENIED; + case GRPC_CHTTP2_REFUSED_STREAM: + return GRPC_STATUS_UNAVAILABLE; + default: + return GRPC_STATUS_INTERNAL; + } +} + +grpc_status_code grpc_chttp2_http2_status_to_grpc_status(int status) { + switch (status) { + /* these HTTP2 status codes are called out explicitly in status.proto */ + case 200: + return GRPC_STATUS_OK; + case 400: + return GRPC_STATUS_INVALID_ARGUMENT; + case 401: + return GRPC_STATUS_UNAUTHENTICATED; + case 403: + return GRPC_STATUS_PERMISSION_DENIED; + case 404: + return GRPC_STATUS_NOT_FOUND; + case 409: + return GRPC_STATUS_ABORTED; + case 412: + return GRPC_STATUS_FAILED_PRECONDITION; + case 429: + return GRPC_STATUS_RESOURCE_EXHAUSTED; + case 499: + return GRPC_STATUS_CANCELLED; + case 500: + return GRPC_STATUS_UNKNOWN; + case 501: + return GRPC_STATUS_UNIMPLEMENTED; + case 503: + return GRPC_STATUS_UNAVAILABLE; + case 504: + return GRPC_STATUS_DEADLINE_EXCEEDED; + /* everything else is unknown */ + default: + return GRPC_STATUS_UNKNOWN; + } +} + +int grpc_chttp2_grpc_status_to_http2_status(grpc_status_code status) { + return 200; +} diff --git a/src/core/lib/transport/chttp2/status_conversion.h b/src/core/lib/transport/chttp2/status_conversion.h new file mode 100644 index 0000000000..241417d32e --- /dev/null +++ b/src/core/lib/transport/chttp2/status_conversion.h @@ -0,0 +1,50 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CHTTP2_STATUS_CONVERSION_H +#define GRPC_CORE_LIB_TRANSPORT_CHTTP2_STATUS_CONVERSION_H + +#include <grpc/grpc.h> +#include "src/core/lib/transport/chttp2/http2_errors.h" + +/* Conversion of grpc status codes to http2 error codes (for RST_STREAM) */ +grpc_chttp2_error_code grpc_chttp2_grpc_status_to_http2_error( + grpc_status_code status); +grpc_status_code grpc_chttp2_http2_error_to_grpc_status( + grpc_chttp2_error_code error); + +/* Conversion of HTTP status codes (:status) to grpc status codes */ +grpc_status_code grpc_chttp2_http2_status_to_grpc_status(int status); +int grpc_chttp2_grpc_status_to_http2_status(grpc_status_code status); + +#endif /* GRPC_CORE_LIB_TRANSPORT_CHTTP2_STATUS_CONVERSION_H */ diff --git a/src/core/lib/transport/chttp2/stream_lists.c b/src/core/lib/transport/chttp2/stream_lists.c new file mode 100644 index 0000000000..b51a041dc7 --- /dev/null +++ b/src/core/lib/transport/chttp2/stream_lists.c @@ -0,0 +1,442 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/chttp2/internal.h" + +#include <grpc/support/log.h> + +#define TRANSPORT_FROM_GLOBAL(tg) \ + ((grpc_chttp2_transport *)((char *)(tg)-offsetof(grpc_chttp2_transport, \ + global))) + +#define STREAM_FROM_GLOBAL(sg) \ + ((grpc_chttp2_stream *)((char *)(sg)-offsetof(grpc_chttp2_stream, global))) + +#define TRANSPORT_FROM_WRITING(tw) \ + ((grpc_chttp2_transport *)((char *)(tw)-offsetof(grpc_chttp2_transport, \ + writing))) + +#define STREAM_FROM_WRITING(sw) \ + ((grpc_chttp2_stream *)((char *)(sw)-offsetof(grpc_chttp2_stream, writing))) + +#define TRANSPORT_FROM_PARSING(tp) \ + ((grpc_chttp2_transport *)((char *)(tp)-offsetof(grpc_chttp2_transport, \ + parsing))) + +#define STREAM_FROM_PARSING(sp) \ + ((grpc_chttp2_stream *)((char *)(sp)-offsetof(grpc_chttp2_stream, parsing))) + +/* core list management */ + +static int stream_list_empty(grpc_chttp2_transport *t, + grpc_chttp2_stream_list_id id) { + return t->lists[id].head == NULL; +} + +static int stream_list_pop(grpc_chttp2_transport *t, + grpc_chttp2_stream **stream, + grpc_chttp2_stream_list_id id) { + grpc_chttp2_stream *s = t->lists[id].head; + if (s) { + grpc_chttp2_stream *new_head = s->links[id].next; + GPR_ASSERT(s->included[id]); + if (new_head) { + t->lists[id].head = new_head; + new_head->links[id].prev = NULL; + } else { + t->lists[id].head = NULL; + t->lists[id].tail = NULL; + } + s->included[id] = 0; + } + *stream = s; + return s != 0; +} + +static void stream_list_remove(grpc_chttp2_transport *t, grpc_chttp2_stream *s, + grpc_chttp2_stream_list_id id) { + GPR_ASSERT(s->included[id]); + s->included[id] = 0; + if (s->links[id].prev) { + s->links[id].prev->links[id].next = s->links[id].next; + } else { + GPR_ASSERT(t->lists[id].head == s); + t->lists[id].head = s->links[id].next; + } + if (s->links[id].next) { + s->links[id].next->links[id].prev = s->links[id].prev; + } else { + t->lists[id].tail = s->links[id].prev; + } +} + +static bool stream_list_maybe_remove(grpc_chttp2_transport *t, + grpc_chttp2_stream *s, + grpc_chttp2_stream_list_id id) { + if (s->included[id]) { + stream_list_remove(t, s, id); + return true; + } else { + return false; + } +} + +static void stream_list_add_tail(grpc_chttp2_transport *t, + grpc_chttp2_stream *s, + grpc_chttp2_stream_list_id id) { + grpc_chttp2_stream *old_tail; + GPR_ASSERT(!s->included[id]); + old_tail = t->lists[id].tail; + s->links[id].next = NULL; + s->links[id].prev = old_tail; + if (old_tail) { + old_tail->links[id].next = s; + } else { + t->lists[id].head = s; + } + t->lists[id].tail = s; + s->included[id] = 1; +} + +static bool stream_list_add(grpc_chttp2_transport *t, grpc_chttp2_stream *s, + grpc_chttp2_stream_list_id id) { + if (s->included[id]) { + return false; + } + stream_list_add_tail(t, s, id); + return true; +} + +/* wrappers for specializations */ + +bool grpc_chttp2_list_add_writable_stream( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global) { + GPR_ASSERT(stream_global->id != 0); + return stream_list_add(TRANSPORT_FROM_GLOBAL(transport_global), + STREAM_FROM_GLOBAL(stream_global), + GRPC_CHTTP2_LIST_WRITABLE); +} + +int grpc_chttp2_list_pop_writable_stream( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_transport_writing *transport_writing, + grpc_chttp2_stream_global **stream_global, + grpc_chttp2_stream_writing **stream_writing) { + grpc_chttp2_stream *stream; + int r = stream_list_pop(TRANSPORT_FROM_GLOBAL(transport_global), &stream, + GRPC_CHTTP2_LIST_WRITABLE); + if (r != 0) { + *stream_global = &stream->global; + *stream_writing = &stream->writing; + } + return r; +} + +bool grpc_chttp2_list_remove_writable_stream( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global) { + return stream_list_maybe_remove(TRANSPORT_FROM_GLOBAL(transport_global), + STREAM_FROM_GLOBAL(stream_global), + GRPC_CHTTP2_LIST_WRITABLE); +} + +void grpc_chttp2_list_add_writing_stream( + grpc_chttp2_transport_writing *transport_writing, + grpc_chttp2_stream_writing *stream_writing) { + GPR_ASSERT(stream_list_add(TRANSPORT_FROM_WRITING(transport_writing), + STREAM_FROM_WRITING(stream_writing), + GRPC_CHTTP2_LIST_WRITING)); +} + +int grpc_chttp2_list_have_writing_streams( + grpc_chttp2_transport_writing *transport_writing) { + return !stream_list_empty(TRANSPORT_FROM_WRITING(transport_writing), + GRPC_CHTTP2_LIST_WRITING); +} + +int grpc_chttp2_list_pop_writing_stream( + grpc_chttp2_transport_writing *transport_writing, + grpc_chttp2_stream_writing **stream_writing) { + grpc_chttp2_stream *stream; + int r = stream_list_pop(TRANSPORT_FROM_WRITING(transport_writing), &stream, + GRPC_CHTTP2_LIST_WRITING); + if (r != 0) { + *stream_writing = &stream->writing; + } + return r; +} + +void grpc_chttp2_list_add_written_stream( + grpc_chttp2_transport_writing *transport_writing, + grpc_chttp2_stream_writing *stream_writing) { + stream_list_add(TRANSPORT_FROM_WRITING(transport_writing), + STREAM_FROM_WRITING(stream_writing), + GRPC_CHTTP2_LIST_WRITTEN); +} + +int grpc_chttp2_list_pop_written_stream( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_transport_writing *transport_writing, + grpc_chttp2_stream_global **stream_global, + grpc_chttp2_stream_writing **stream_writing) { + grpc_chttp2_stream *stream; + int r = stream_list_pop(TRANSPORT_FROM_WRITING(transport_writing), &stream, + GRPC_CHTTP2_LIST_WRITTEN); + if (r != 0) { + *stream_global = &stream->global; + *stream_writing = &stream->writing; + } + return r; +} + +void grpc_chttp2_list_add_unannounced_incoming_window_available( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global) { + GPR_ASSERT(stream_global->id != 0); + stream_list_add(TRANSPORT_FROM_GLOBAL(transport_global), + STREAM_FROM_GLOBAL(stream_global), + GRPC_CHTTP2_LIST_UNANNOUNCED_INCOMING_WINDOW_AVAILABLE); +} + +void grpc_chttp2_list_remove_unannounced_incoming_window_available( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global) { + stream_list_maybe_remove( + TRANSPORT_FROM_GLOBAL(transport_global), + STREAM_FROM_GLOBAL(stream_global), + GRPC_CHTTP2_LIST_UNANNOUNCED_INCOMING_WINDOW_AVAILABLE); +} + +int grpc_chttp2_list_pop_unannounced_incoming_window_available( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_global **stream_global, + grpc_chttp2_stream_parsing **stream_parsing) { + grpc_chttp2_stream *stream; + int r = + stream_list_pop(TRANSPORT_FROM_GLOBAL(transport_global), &stream, + GRPC_CHTTP2_LIST_UNANNOUNCED_INCOMING_WINDOW_AVAILABLE); + if (r != 0) { + *stream_global = &stream->global; + *stream_parsing = &stream->parsing; + } + return r; +} + +void grpc_chttp2_list_add_parsing_seen_stream( + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_parsing *stream_parsing) { + stream_list_add(TRANSPORT_FROM_PARSING(transport_parsing), + STREAM_FROM_PARSING(stream_parsing), + GRPC_CHTTP2_LIST_PARSING_SEEN); +} + +int grpc_chttp2_list_pop_parsing_seen_stream( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_global **stream_global, + grpc_chttp2_stream_parsing **stream_parsing) { + grpc_chttp2_stream *stream; + int r = stream_list_pop(TRANSPORT_FROM_PARSING(transport_parsing), &stream, + GRPC_CHTTP2_LIST_PARSING_SEEN); + if (r != 0) { + *stream_global = &stream->global; + *stream_parsing = &stream->parsing; + } + return r; +} + +void grpc_chttp2_list_add_waiting_for_concurrency( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global) { + stream_list_add(TRANSPORT_FROM_GLOBAL(transport_global), + STREAM_FROM_GLOBAL(stream_global), + GRPC_CHTTP2_LIST_WAITING_FOR_CONCURRENCY); +} + +int grpc_chttp2_list_pop_waiting_for_concurrency( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global **stream_global) { + grpc_chttp2_stream *stream; + int r = stream_list_pop(TRANSPORT_FROM_GLOBAL(transport_global), &stream, + GRPC_CHTTP2_LIST_WAITING_FOR_CONCURRENCY); + if (r != 0) { + *stream_global = &stream->global; + } + return r; +} + +void grpc_chttp2_list_add_check_read_ops( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global) { + stream_list_add(TRANSPORT_FROM_GLOBAL(transport_global), + STREAM_FROM_GLOBAL(stream_global), + GRPC_CHTTP2_LIST_CHECK_READ_OPS); +} + +int grpc_chttp2_list_pop_check_read_ops( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global **stream_global) { + grpc_chttp2_stream *stream; + int r = stream_list_pop(TRANSPORT_FROM_GLOBAL(transport_global), &stream, + GRPC_CHTTP2_LIST_CHECK_READ_OPS); + if (r != 0) { + *stream_global = &stream->global; + } + return r; +} + +void grpc_chttp2_list_add_writing_stalled_by_transport( + grpc_chttp2_transport_writing *transport_writing, + grpc_chttp2_stream_writing *stream_writing) { + grpc_chttp2_stream *stream = STREAM_FROM_WRITING(stream_writing); + if (!stream->included[GRPC_CHTTP2_LIST_WRITING_STALLED_BY_TRANSPORT]) { + GRPC_CHTTP2_STREAM_REF(&stream->global, "chttp2_writing_stalled"); + } + stream_list_add(TRANSPORT_FROM_WRITING(transport_writing), stream, + GRPC_CHTTP2_LIST_WRITING_STALLED_BY_TRANSPORT); +} + +void grpc_chttp2_list_flush_writing_stalled_by_transport( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_writing *transport_writing, + bool is_window_available) { + grpc_chttp2_stream *stream; + grpc_chttp2_transport *transport = TRANSPORT_FROM_WRITING(transport_writing); + while (stream_list_pop(transport, &stream, + GRPC_CHTTP2_LIST_WRITING_STALLED_BY_TRANSPORT)) { + if (is_window_available) { + grpc_chttp2_become_writable(&transport->global, &stream->global); + } else { + grpc_chttp2_list_add_stalled_by_transport(transport_writing, + &stream->writing); + } + GRPC_CHTTP2_STREAM_UNREF(exec_ctx, &stream->global, + "chttp2_writing_stalled"); + } +} + +void grpc_chttp2_list_add_stalled_by_transport( + grpc_chttp2_transport_writing *transport_writing, + grpc_chttp2_stream_writing *stream_writing) { + stream_list_add(TRANSPORT_FROM_WRITING(transport_writing), + STREAM_FROM_WRITING(stream_writing), + GRPC_CHTTP2_LIST_STALLED_BY_TRANSPORT); +} + +int grpc_chttp2_list_pop_stalled_by_transport( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global **stream_global) { + grpc_chttp2_stream *stream; + int r = stream_list_pop(TRANSPORT_FROM_GLOBAL(transport_global), &stream, + GRPC_CHTTP2_LIST_STALLED_BY_TRANSPORT); + if (r != 0) { + *stream_global = &stream->global; + } + return r; +} + +void grpc_chttp2_list_remove_stalled_by_transport( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global) { + stream_list_maybe_remove(TRANSPORT_FROM_GLOBAL(transport_global), + STREAM_FROM_GLOBAL(stream_global), + GRPC_CHTTP2_LIST_STALLED_BY_TRANSPORT); +} + +void grpc_chttp2_list_add_closed_waiting_for_parsing( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global) { + stream_list_add(TRANSPORT_FROM_GLOBAL(transport_global), + STREAM_FROM_GLOBAL(stream_global), + GRPC_CHTTP2_LIST_CLOSED_WAITING_FOR_PARSING); +} + +int grpc_chttp2_list_pop_closed_waiting_for_parsing( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global **stream_global) { + grpc_chttp2_stream *stream; + int r = stream_list_pop(TRANSPORT_FROM_GLOBAL(transport_global), &stream, + GRPC_CHTTP2_LIST_CLOSED_WAITING_FOR_PARSING); + if (r != 0) { + *stream_global = &stream->global; + } + return r; +} + +void grpc_chttp2_list_add_closed_waiting_for_writing( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global) { + stream_list_add(TRANSPORT_FROM_GLOBAL(transport_global), + STREAM_FROM_GLOBAL(stream_global), + GRPC_CHTTP2_LIST_CLOSED_WAITING_FOR_WRITING); +} + +int grpc_chttp2_list_pop_closed_waiting_for_writing( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global **stream_global) { + grpc_chttp2_stream *stream; + int r = stream_list_pop(TRANSPORT_FROM_GLOBAL(transport_global), &stream, + GRPC_CHTTP2_LIST_CLOSED_WAITING_FOR_WRITING); + if (r != 0) { + *stream_global = &stream->global; + } + return r; +} + +void grpc_chttp2_register_stream(grpc_chttp2_transport *t, + grpc_chttp2_stream *s) { + stream_list_add_tail(t, s, GRPC_CHTTP2_LIST_ALL_STREAMS); +} + +int grpc_chttp2_unregister_stream(grpc_chttp2_transport *t, + grpc_chttp2_stream *s) { + stream_list_maybe_remove(t, s, GRPC_CHTTP2_LIST_ALL_STREAMS); + return stream_list_empty(t, GRPC_CHTTP2_LIST_ALL_STREAMS); +} + +int grpc_chttp2_has_streams(grpc_chttp2_transport *t) { + return !stream_list_empty(t, GRPC_CHTTP2_LIST_ALL_STREAMS); +} + +void grpc_chttp2_for_all_streams( + grpc_chttp2_transport_global *transport_global, void *user_data, + void (*cb)(grpc_chttp2_transport_global *transport_global, void *user_data, + grpc_chttp2_stream_global *stream_global)) { + grpc_chttp2_stream *s; + grpc_chttp2_transport *t = TRANSPORT_FROM_GLOBAL(transport_global); + for (s = t->lists[GRPC_CHTTP2_LIST_ALL_STREAMS].head; s != NULL; + s = s->links[GRPC_CHTTP2_LIST_ALL_STREAMS].next) { + cb(transport_global, user_data, &s->global); + } +} diff --git a/src/core/lib/transport/chttp2/stream_map.c b/src/core/lib/transport/chttp2/stream_map.c new file mode 100644 index 0000000000..dbbbe783bf --- /dev/null +++ b/src/core/lib/transport/chttp2/stream_map.c @@ -0,0 +1,197 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/chttp2/stream_map.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/useful.h> + +void grpc_chttp2_stream_map_init(grpc_chttp2_stream_map *map, + size_t initial_capacity) { + GPR_ASSERT(initial_capacity > 1); + map->keys = gpr_malloc(sizeof(uint32_t) * initial_capacity); + map->values = gpr_malloc(sizeof(void *) * initial_capacity); + map->count = 0; + map->free = 0; + map->capacity = initial_capacity; +} + +void grpc_chttp2_stream_map_destroy(grpc_chttp2_stream_map *map) { + gpr_free(map->keys); + gpr_free(map->values); +} + +static size_t compact(uint32_t *keys, void **values, size_t count) { + size_t i, out; + + for (i = 0, out = 0; i < count; i++) { + if (values[i]) { + keys[out] = keys[i]; + values[out] = values[i]; + out++; + } + } + + return out; +} + +void grpc_chttp2_stream_map_add(grpc_chttp2_stream_map *map, uint32_t key, + void *value) { + size_t count = map->count; + size_t capacity = map->capacity; + uint32_t *keys = map->keys; + void **values = map->values; + + GPR_ASSERT(count == 0 || keys[count - 1] < key); + GPR_ASSERT(value); + + if (count == capacity) { + if (map->free > capacity / 4) { + count = compact(keys, values, count); + map->free = 0; + } else { + /* resize when less than 25% of the table is free, because compaction + won't help much */ + map->capacity = capacity = 3 * capacity / 2; + map->keys = keys = gpr_realloc(keys, capacity * sizeof(uint32_t)); + map->values = values = gpr_realloc(values, capacity * sizeof(void *)); + } + } + + keys[count] = key; + values[count] = value; + map->count = count + 1; +} + +void grpc_chttp2_stream_map_move_into(grpc_chttp2_stream_map *src, + grpc_chttp2_stream_map *dst) { + /* if src is empty we dont need to do anything */ + if (src->count == src->free) { + return; + } + /* if dst is empty we simply need to swap */ + if (dst->count == dst->free) { + GPR_SWAP(grpc_chttp2_stream_map, *src, *dst); + return; + } + /* the first element of src must be greater than the last of dst... + * however the maps may need compacting for this property to hold */ + if (src->keys[0] <= dst->keys[dst->count - 1]) { + src->count = compact(src->keys, src->values, src->count); + src->free = 0; + dst->count = compact(dst->keys, dst->values, dst->count); + dst->free = 0; + } + GPR_ASSERT(src->keys[0] > dst->keys[dst->count - 1]); + /* if dst doesn't have capacity, resize */ + if (dst->count + src->count > dst->capacity) { + dst->capacity = GPR_MAX(dst->capacity * 3 / 2, dst->count + src->count); + dst->keys = gpr_realloc(dst->keys, dst->capacity * sizeof(uint32_t)); + dst->values = gpr_realloc(dst->values, dst->capacity * sizeof(void *)); + } + memcpy(dst->keys + dst->count, src->keys, src->count * sizeof(uint32_t)); + memcpy(dst->values + dst->count, src->values, src->count * sizeof(void *)); + dst->count += src->count; + dst->free += src->free; + src->count = 0; + src->free = 0; +} + +static void **find(grpc_chttp2_stream_map *map, uint32_t key) { + size_t min_idx = 0; + size_t max_idx = map->count; + size_t mid_idx; + uint32_t *keys = map->keys; + void **values = map->values; + uint32_t mid_key; + + if (max_idx == 0) return NULL; + + while (min_idx < max_idx) { + /* find the midpoint, avoiding overflow */ + mid_idx = min_idx + ((max_idx - min_idx) / 2); + mid_key = keys[mid_idx]; + + if (mid_key < key) { + min_idx = mid_idx + 1; + } else if (mid_key > key) { + max_idx = mid_idx; + } else /* mid_key == key */ + { + return &values[mid_idx]; + } + } + + return NULL; +} + +void *grpc_chttp2_stream_map_delete(grpc_chttp2_stream_map *map, uint32_t key) { + void **pvalue = find(map, key); + void *out = NULL; + if (pvalue != NULL) { + out = *pvalue; + *pvalue = NULL; + map->free += (out != NULL); + /* recognize complete emptyness and ensure we can skip + * defragmentation later */ + if (map->free == map->count) { + map->free = map->count = 0; + } + } + return out; +} + +void *grpc_chttp2_stream_map_find(grpc_chttp2_stream_map *map, uint32_t key) { + void **pvalue = find(map, key); + return pvalue != NULL ? *pvalue : NULL; +} + +size_t grpc_chttp2_stream_map_size(grpc_chttp2_stream_map *map) { + return map->count - map->free; +} + +void grpc_chttp2_stream_map_for_each(grpc_chttp2_stream_map *map, + void (*f)(void *user_data, uint32_t key, + void *value), + void *user_data) { + size_t i; + + for (i = 0; i < map->count; i++) { + if (map->values[i]) { + f(user_data, map->keys[i], map->values[i]); + } + } +} diff --git a/src/core/lib/transport/chttp2/stream_map.h b/src/core/lib/transport/chttp2/stream_map.h new file mode 100644 index 0000000000..1c56b18e54 --- /dev/null +++ b/src/core/lib/transport/chttp2/stream_map.h @@ -0,0 +1,84 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CHTTP2_STREAM_MAP_H +#define GRPC_CORE_LIB_TRANSPORT_CHTTP2_STREAM_MAP_H + +#include <grpc/support/port_platform.h> + +#include <stddef.h> + +/* Data structure to map a uint32_t to a data object (represented by a void*) + + Represented as a sorted array of keys, and a corresponding array of values. + Lookups are performed with binary search. + Adds are restricted to strictly higher keys than previously seen (this is + guaranteed by http2). */ +typedef struct { + uint32_t *keys; + void **values; + size_t count; + size_t free; + size_t capacity; +} grpc_chttp2_stream_map; + +void grpc_chttp2_stream_map_init(grpc_chttp2_stream_map *map, + size_t initial_capacity); +void grpc_chttp2_stream_map_destroy(grpc_chttp2_stream_map *map); + +/* Add a new key: given http2 semantics, new keys must always be greater than + existing keys - this is asserted */ +void grpc_chttp2_stream_map_add(grpc_chttp2_stream_map *map, uint32_t key, + void *value); + +/* Delete an existing key - returns the previous value of the key if it existed, + or NULL otherwise */ +void *grpc_chttp2_stream_map_delete(grpc_chttp2_stream_map *map, uint32_t key); + +/* Move all elements of src into dst */ +void grpc_chttp2_stream_map_move_into(grpc_chttp2_stream_map *src, + grpc_chttp2_stream_map *dst); + +/* Return an existing key, or NULL if it does not exist */ +void *grpc_chttp2_stream_map_find(grpc_chttp2_stream_map *map, uint32_t key); + +/* How many (populated) entries are in the stream map? */ +size_t grpc_chttp2_stream_map_size(grpc_chttp2_stream_map *map); + +/* Callback on each stream */ +void grpc_chttp2_stream_map_for_each(grpc_chttp2_stream_map *map, + void (*f)(void *user_data, uint32_t key, + void *value), + void *user_data); + +#endif /* GRPC_CORE_LIB_TRANSPORT_CHTTP2_STREAM_MAP_H */ diff --git a/src/core/lib/transport/chttp2/timeout_encoding.c b/src/core/lib/transport/chttp2/timeout_encoding.c new file mode 100644 index 0000000000..0edacaafd3 --- /dev/null +++ b/src/core/lib/transport/chttp2/timeout_encoding.c @@ -0,0 +1,188 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/chttp2/timeout_encoding.h" + +#include <stdio.h> +#include <string.h> + +#include <grpc/support/port_platform.h> +#include "src/core/lib/support/string.h" + +static int64_t round_up(int64_t x, int64_t divisor) { + return (x / divisor + (x % divisor != 0)) * divisor; +} + +/* round an integer up to the next value with three significant figures */ +static int64_t round_up_to_three_sig_figs(int64_t x) { + if (x < 1000) return x; + if (x < 10000) return round_up(x, 10); + if (x < 100000) return round_up(x, 100); + if (x < 1000000) return round_up(x, 1000); + if (x < 10000000) return round_up(x, 10000); + if (x < 100000000) return round_up(x, 100000); + if (x < 1000000000) return round_up(x, 1000000); + return round_up(x, 10000000); +} + +/* encode our minimum viable timeout value */ +static void enc_tiny(char *buffer) { memcpy(buffer, "1n", 3); } + +static void enc_ext(char *buffer, int64_t value, char ext) { + int n = int64_ttoa(value, buffer); + buffer[n] = ext; + buffer[n + 1] = 0; +} + +static void enc_seconds(char *buffer, int64_t sec) { + if (sec % 3600 == 0) { + enc_ext(buffer, sec / 3600, 'H'); + } else if (sec % 60 == 0) { + enc_ext(buffer, sec / 60, 'M'); + } else { + enc_ext(buffer, sec, 'S'); + } +} + +static void enc_nanos(char *buffer, int64_t x) { + x = round_up_to_three_sig_figs(x); + if (x < 100000) { + if (x % 1000 == 0) { + enc_ext(buffer, x / 1000, 'u'); + } else { + enc_ext(buffer, x, 'n'); + } + } else if (x < 100000000) { + if (x % 1000000 == 0) { + enc_ext(buffer, x / 1000000, 'm'); + } else { + enc_ext(buffer, x / 1000, 'u'); + } + } else if (x < 1000000000) { + enc_ext(buffer, x / 1000000, 'm'); + } else { + /* note that this is only ever called with times of less than one second, + so if we reach here the time must have been rounded up to a whole second + (and no more) */ + memcpy(buffer, "1S", 3); + } +} + +static void enc_micros(char *buffer, int64_t x) { + x = round_up_to_three_sig_figs(x); + if (x < 100000) { + if (x % 1000 == 0) { + enc_ext(buffer, x / 1000, 'm'); + } else { + enc_ext(buffer, x, 'u'); + } + } else if (x < 100000000) { + if (x % 1000000 == 0) { + enc_ext(buffer, x / 1000000, 'S'); + } else { + enc_ext(buffer, x / 1000, 'm'); + } + } else { + enc_ext(buffer, x / 1000000, 'S'); + } +} + +void grpc_chttp2_encode_timeout(gpr_timespec timeout, char *buffer) { + if (timeout.tv_sec < 0) { + enc_tiny(buffer); + } else if (timeout.tv_sec == 0) { + enc_nanos(buffer, timeout.tv_nsec); + } else if (timeout.tv_sec < 1000 && timeout.tv_nsec != 0) { + enc_micros(buffer, + (int64_t)(timeout.tv_sec * 1000000) + + (timeout.tv_nsec / 1000 + (timeout.tv_nsec % 1000 != 0))); + } else { + enc_seconds(buffer, timeout.tv_sec + (timeout.tv_nsec != 0)); + } +} + +static int is_all_whitespace(const char *p) { + while (*p == ' ') p++; + return *p == 0; +} + +int grpc_chttp2_decode_timeout(const char *buffer, gpr_timespec *timeout) { + int32_t x = 0; + const uint8_t *p = (const uint8_t *)buffer; + int have_digit = 0; + /* skip whitespace */ + for (; *p == ' '; p++) + ; + /* decode numeric part */ + for (; *p >= '0' && *p <= '9'; p++) { + int32_t digit = (int32_t)(*p - (uint8_t)'0'); + have_digit = 1; + /* spec allows max. 8 digits, but we allow values up to 1,000,000,000 */ + if (x >= (100 * 1000 * 1000)) { + if (x != (100 * 1000 * 1000) || digit != 0) { + *timeout = gpr_inf_future(GPR_TIMESPAN); + return 1; + } + } + x = x * 10 + digit; + } + if (!have_digit) return 0; + /* skip whitespace */ + for (; *p == ' '; p++) + ; + /* decode unit specifier */ + switch (*p) { + case 'n': + *timeout = gpr_time_from_nanos(x, GPR_TIMESPAN); + break; + case 'u': + *timeout = gpr_time_from_micros(x, GPR_TIMESPAN); + break; + case 'm': + *timeout = gpr_time_from_millis(x, GPR_TIMESPAN); + break; + case 'S': + *timeout = gpr_time_from_seconds(x, GPR_TIMESPAN); + break; + case 'M': + *timeout = gpr_time_from_minutes(x, GPR_TIMESPAN); + break; + case 'H': + *timeout = gpr_time_from_hours(x, GPR_TIMESPAN); + break; + default: + return 0; + } + p++; + return is_all_whitespace((const char *)p); +} diff --git a/src/core/lib/transport/chttp2/timeout_encoding.h b/src/core/lib/transport/chttp2/timeout_encoding.h new file mode 100644 index 0000000000..731beb5a37 --- /dev/null +++ b/src/core/lib/transport/chttp2/timeout_encoding.h @@ -0,0 +1,47 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CHTTP2_TIMEOUT_ENCODING_H +#define GRPC_CORE_LIB_TRANSPORT_CHTTP2_TIMEOUT_ENCODING_H + +#include <grpc/support/time.h> +#include "src/core/lib/support/string.h" + +#define GRPC_CHTTP2_TIMEOUT_ENCODE_MIN_BUFSIZE (GPR_LTOA_MIN_BUFSIZE + 1) + +/* Encode/decode timeouts to the GRPC over HTTP2 format; + encoding may round up arbitrarily */ +void grpc_chttp2_encode_timeout(gpr_timespec timeout, char *buffer); +int grpc_chttp2_decode_timeout(const char *buffer, gpr_timespec *timeout); + +#endif /* GRPC_CORE_LIB_TRANSPORT_CHTTP2_TIMEOUT_ENCODING_H */ diff --git a/src/core/lib/transport/chttp2/varint.c b/src/core/lib/transport/chttp2/varint.c new file mode 100644 index 0000000000..6dfef45362 --- /dev/null +++ b/src/core/lib/transport/chttp2/varint.c @@ -0,0 +1,65 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/chttp2/varint.h" + +uint32_t grpc_chttp2_hpack_varint_length(uint32_t tail_value) { + if (tail_value < (1 << 7)) { + return 2; + } else if (tail_value < (1 << 14)) { + return 3; + } else if (tail_value < (1 << 21)) { + return 4; + } else if (tail_value < (1 << 28)) { + return 5; + } else { + return 6; + } +} + +void grpc_chttp2_hpack_write_varint_tail(uint32_t tail_value, uint8_t* target, + uint32_t tail_length) { + switch (tail_length) { + case 5: + target[4] = (uint8_t)((tail_value >> 28) | 0x80); + case 4: + target[3] = (uint8_t)((tail_value >> 21) | 0x80); + case 3: + target[2] = (uint8_t)((tail_value >> 14) | 0x80); + case 2: + target[1] = (uint8_t)((tail_value >> 7) | 0x80); + case 1: + target[0] = (uint8_t)((tail_value) | 0x80); + } + target[tail_length - 1] &= 0x7f; +} diff --git a/src/core/lib/transport/chttp2/varint.h b/src/core/lib/transport/chttp2/varint.h new file mode 100644 index 0000000000..e4a0ae3c22 --- /dev/null +++ b/src/core/lib/transport/chttp2/varint.h @@ -0,0 +1,75 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CHTTP2_VARINT_H +#define GRPC_CORE_LIB_TRANSPORT_CHTTP2_VARINT_H + +#include <grpc/support/port_platform.h> + +/* Helpers for hpack varint encoding */ + +/* length of a value that needs varint tail encoding (it's bigger than can be + bitpacked into the opcode byte) - returned value includes the length of the + opcode byte */ +uint32_t grpc_chttp2_hpack_varint_length(uint32_t tail_value); + +void grpc_chttp2_hpack_write_varint_tail(uint32_t tail_value, uint8_t* target, + uint32_t tail_length); + +/* maximum value that can be bitpacked with the opcode if the opcode has a + prefix + of length prefix_bits */ +#define GRPC_CHTTP2_MAX_IN_PREFIX(prefix_bits) \ + ((uint32_t)((1 << (8 - (prefix_bits))) - 1)) + +/* length required to bitpack a value */ +#define GRPC_CHTTP2_VARINT_LENGTH(n, prefix_bits) \ + ((n) < GRPC_CHTTP2_MAX_IN_PREFIX(prefix_bits) \ + ? 1u \ + : grpc_chttp2_hpack_varint_length( \ + (n)-GRPC_CHTTP2_MAX_IN_PREFIX(prefix_bits))) + +#define GRPC_CHTTP2_WRITE_VARINT(n, prefix_bits, prefix_or, target, length) \ + do { \ + uint8_t* tgt = target; \ + if ((length) == 1u) { \ + (tgt)[0] = (uint8_t)((prefix_or) | (n)); \ + } else { \ + (tgt)[0] = \ + (prefix_or) | (uint8_t)GRPC_CHTTP2_MAX_IN_PREFIX(prefix_bits); \ + grpc_chttp2_hpack_write_varint_tail( \ + (n)-GRPC_CHTTP2_MAX_IN_PREFIX(prefix_bits), (tgt) + 1, (length)-1); \ + } \ + } while (0) + +#endif /* GRPC_CORE_LIB_TRANSPORT_CHTTP2_VARINT_H */ diff --git a/src/core/lib/transport/chttp2/writing.c b/src/core/lib/transport/chttp2/writing.c new file mode 100644 index 0000000000..daea331d31 --- /dev/null +++ b/src/core/lib/transport/chttp2/writing.c @@ -0,0 +1,350 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/chttp2/internal.h" + +#include <limits.h> + +#include <grpc/support/log.h> + +#include "src/core/lib/profiling/timers.h" +#include "src/core/lib/transport/chttp2/http2_errors.h" + +static void finalize_outbuf(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_writing *transport_writing); + +int grpc_chttp2_unlocking_check_writes( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global, + grpc_chttp2_transport_writing *transport_writing, int is_parsing) { + grpc_chttp2_stream_global *stream_global; + grpc_chttp2_stream_writing *stream_writing; + + GPR_TIMER_BEGIN("grpc_chttp2_unlocking_check_writes", 0); + + /* simple writes are queued to qbuf, and flushed here */ + gpr_slice_buffer_swap(&transport_global->qbuf, &transport_writing->outbuf); + GPR_ASSERT(transport_global->qbuf.count == 0); + + grpc_chttp2_hpack_compressor_set_max_table_size( + &transport_writing->hpack_compressor, + transport_global->settings[GRPC_PEER_SETTINGS] + [GRPC_CHTTP2_SETTINGS_HEADER_TABLE_SIZE]); + + if (transport_global->dirtied_local_settings && + !transport_global->sent_local_settings && !is_parsing) { + gpr_slice_buffer_add( + &transport_writing->outbuf, + grpc_chttp2_settings_create( + transport_global->settings[GRPC_SENT_SETTINGS], + transport_global->settings[GRPC_LOCAL_SETTINGS], + transport_global->force_send_settings, GRPC_CHTTP2_NUM_SETTINGS)); + transport_global->force_send_settings = 0; + transport_global->dirtied_local_settings = 0; + transport_global->sent_local_settings = 1; + } + + GRPC_CHTTP2_FLOW_MOVE_TRANSPORT("write", transport_writing, outgoing_window, + transport_global, outgoing_window); + bool is_window_available = transport_writing->outgoing_window > 0; + grpc_chttp2_list_flush_writing_stalled_by_transport( + exec_ctx, transport_writing, is_window_available); + + /* for each grpc_chttp2_stream that's become writable, frame it's data + (according to available window sizes) and add to the output buffer */ + while (grpc_chttp2_list_pop_writable_stream( + transport_global, transport_writing, &stream_global, &stream_writing)) { + bool sent_initial_metadata = stream_writing->sent_initial_metadata; + bool become_writable = false; + + stream_writing->id = stream_global->id; + stream_writing->read_closed = stream_global->read_closed; + + GRPC_CHTTP2_FLOW_MOVE_STREAM("write", transport_writing, stream_writing, + outgoing_window, stream_global, + outgoing_window); + + if (!sent_initial_metadata && stream_global->send_initial_metadata) { + stream_writing->send_initial_metadata = + stream_global->send_initial_metadata; + stream_global->send_initial_metadata = NULL; + become_writable = true; + sent_initial_metadata = true; + } + if (sent_initial_metadata) { + if (stream_global->send_message != NULL) { + gpr_slice hdr = gpr_slice_malloc(5); + uint8_t *p = GPR_SLICE_START_PTR(hdr); + uint32_t len = stream_global->send_message->length; + GPR_ASSERT(stream_writing->send_message == NULL); + p[0] = (stream_global->send_message->flags & + GRPC_WRITE_INTERNAL_COMPRESS) != 0; + p[1] = (uint8_t)(len >> 24); + p[2] = (uint8_t)(len >> 16); + p[3] = (uint8_t)(len >> 8); + p[4] = (uint8_t)(len); + gpr_slice_buffer_add(&stream_writing->flow_controlled_buffer, hdr); + if (stream_global->send_message->length > 0) { + stream_writing->send_message = stream_global->send_message; + } else { + stream_writing->send_message = NULL; + } + stream_writing->stream_fetched = 0; + stream_global->send_message = NULL; + } + if ((stream_writing->send_message != NULL || + stream_writing->flow_controlled_buffer.length > 0) && + stream_writing->outgoing_window > 0) { + if (transport_writing->outgoing_window > 0) { + become_writable = true; + } else { + grpc_chttp2_list_add_stalled_by_transport(transport_writing, + stream_writing); + } + } + if (stream_global->send_trailing_metadata) { + stream_writing->send_trailing_metadata = + stream_global->send_trailing_metadata; + stream_global->send_trailing_metadata = NULL; + become_writable = true; + } + } + + if (!stream_global->read_closed && + stream_global->unannounced_incoming_window_for_writing > 1024) { + GRPC_CHTTP2_FLOW_MOVE_STREAM("write", transport_global, stream_writing, + announce_window, stream_global, + unannounced_incoming_window_for_writing); + become_writable = true; + } + + if (become_writable) { + grpc_chttp2_list_add_writing_stream(transport_writing, stream_writing); + } else { + GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, "chttp2_writing"); + } + } + + /* if the grpc_chttp2_transport is ready to send a window update, do so here + also; 3/4 is a magic number that will likely get tuned soon */ + if (transport_global->announce_incoming_window > 0) { + uint32_t announced = (uint32_t)GPR_MIN( + transport_global->announce_incoming_window, UINT32_MAX); + GRPC_CHTTP2_FLOW_DEBIT_TRANSPORT("write", transport_global, + announce_incoming_window, announced); + gpr_slice_buffer_add(&transport_writing->outbuf, + grpc_chttp2_window_update_create(0, announced)); + } + + GPR_TIMER_END("grpc_chttp2_unlocking_check_writes", 0); + + return transport_writing->outbuf.count > 0 || + grpc_chttp2_list_have_writing_streams(transport_writing); +} + +void grpc_chttp2_perform_writes( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_writing *transport_writing, + grpc_endpoint *endpoint) { + GPR_ASSERT(transport_writing->outbuf.count > 0 || + grpc_chttp2_list_have_writing_streams(transport_writing)); + + finalize_outbuf(exec_ctx, transport_writing); + + GPR_ASSERT(endpoint); + + if (transport_writing->outbuf.count > 0) { + grpc_endpoint_write(exec_ctx, endpoint, &transport_writing->outbuf, + &transport_writing->done_cb); + } else { + grpc_exec_ctx_enqueue(exec_ctx, &transport_writing->done_cb, true, NULL); + } +} + +static void finalize_outbuf(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_writing *transport_writing) { + grpc_chttp2_stream_writing *stream_writing; + + GPR_TIMER_BEGIN("finalize_outbuf", 0); + + while ( + grpc_chttp2_list_pop_writing_stream(transport_writing, &stream_writing)) { + uint32_t max_outgoing = + (uint32_t)GPR_MIN(GRPC_CHTTP2_MAX_PAYLOAD_LENGTH, + GPR_MIN(stream_writing->outgoing_window, + transport_writing->outgoing_window)); + /* send initial metadata if it's available */ + if (stream_writing->send_initial_metadata != NULL) { + grpc_chttp2_encode_header( + &transport_writing->hpack_compressor, stream_writing->id, + stream_writing->send_initial_metadata, 0, &transport_writing->outbuf); + stream_writing->send_initial_metadata = NULL; + stream_writing->sent_initial_metadata = 1; + } + /* send any window updates */ + if (stream_writing->announce_window > 0 && + stream_writing->send_initial_metadata == NULL) { + uint32_t announce = stream_writing->announce_window; + gpr_slice_buffer_add( + &transport_writing->outbuf, + grpc_chttp2_window_update_create(stream_writing->id, + stream_writing->announce_window)); + GRPC_CHTTP2_FLOW_DEBIT_STREAM("write", transport_writing, stream_writing, + announce_window, announce); + stream_writing->announce_window = 0; + } + /* fetch any body bytes */ + while (!stream_writing->fetching && stream_writing->send_message && + stream_writing->flow_controlled_buffer.length < max_outgoing && + stream_writing->stream_fetched < + stream_writing->send_message->length) { + if (grpc_byte_stream_next(exec_ctx, stream_writing->send_message, + &stream_writing->fetching_slice, max_outgoing, + &stream_writing->finished_fetch)) { + stream_writing->stream_fetched += + GPR_SLICE_LENGTH(stream_writing->fetching_slice); + if (stream_writing->stream_fetched == + stream_writing->send_message->length) { + stream_writing->send_message = NULL; + } + gpr_slice_buffer_add(&stream_writing->flow_controlled_buffer, + stream_writing->fetching_slice); + } else { + stream_writing->fetching = 1; + } + } + /* send any body bytes */ + if (stream_writing->flow_controlled_buffer.length > 0) { + if (max_outgoing > 0) { + uint32_t send_bytes = (uint32_t)GPR_MIN( + max_outgoing, stream_writing->flow_controlled_buffer.length); + int is_last_data_frame = + stream_writing->send_message == NULL && + send_bytes == stream_writing->flow_controlled_buffer.length; + int is_last_frame = is_last_data_frame && + stream_writing->send_trailing_metadata != NULL && + grpc_metadata_batch_is_empty( + stream_writing->send_trailing_metadata); + grpc_chttp2_encode_data( + stream_writing->id, &stream_writing->flow_controlled_buffer, + send_bytes, is_last_frame, &transport_writing->outbuf); + GRPC_CHTTP2_FLOW_DEBIT_STREAM("write", transport_writing, + stream_writing, outgoing_window, + send_bytes); + GRPC_CHTTP2_FLOW_DEBIT_TRANSPORT("write", transport_writing, + outgoing_window, send_bytes); + if (is_last_frame) { + stream_writing->send_trailing_metadata = NULL; + stream_writing->sent_trailing_metadata = 1; + } + if (is_last_data_frame) { + GPR_ASSERT(stream_writing->send_message == NULL); + stream_writing->sent_message = 1; + } + } else if (transport_writing->outgoing_window == 0) { + grpc_chttp2_list_add_writing_stalled_by_transport(transport_writing, + stream_writing); + grpc_chttp2_list_add_written_stream(transport_writing, stream_writing); + } + } + /* send trailing metadata if it's available and we're ready for it */ + if (stream_writing->send_message == NULL && + stream_writing->flow_controlled_buffer.length == 0 && + stream_writing->send_trailing_metadata != NULL) { + if (grpc_metadata_batch_is_empty( + stream_writing->send_trailing_metadata)) { + grpc_chttp2_encode_data(stream_writing->id, + &stream_writing->flow_controlled_buffer, 0, 1, + &transport_writing->outbuf); + } else { + grpc_chttp2_encode_header(&transport_writing->hpack_compressor, + stream_writing->id, + stream_writing->send_trailing_metadata, 1, + &transport_writing->outbuf); + } + if (!transport_writing->is_client && !stream_writing->read_closed) { + gpr_slice_buffer_add(&transport_writing->outbuf, + grpc_chttp2_rst_stream_create( + stream_writing->id, GRPC_CHTTP2_NO_ERROR)); + } + stream_writing->send_trailing_metadata = NULL; + stream_writing->sent_trailing_metadata = 1; + } + /* if there's more to write, then loop, otherwise prepare to finish the + * write */ + if ((stream_writing->flow_controlled_buffer.length > 0 || + (stream_writing->send_message && !stream_writing->fetching)) && + stream_writing->outgoing_window > 0) { + if (transport_writing->outgoing_window > 0) { + grpc_chttp2_list_add_writing_stream(transport_writing, stream_writing); + } else { + grpc_chttp2_list_add_writing_stalled_by_transport(transport_writing, + stream_writing); + grpc_chttp2_list_add_written_stream(transport_writing, stream_writing); + } + } else { + grpc_chttp2_list_add_written_stream(transport_writing, stream_writing); + } + } + + GPR_TIMER_END("finalize_outbuf", 0); +} + +void grpc_chttp2_cleanup_writing( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global, + grpc_chttp2_transport_writing *transport_writing) { + grpc_chttp2_stream_writing *stream_writing; + grpc_chttp2_stream_global *stream_global; + + while (grpc_chttp2_list_pop_written_stream( + transport_global, transport_writing, &stream_global, &stream_writing)) { + if (stream_writing->sent_initial_metadata) { + grpc_chttp2_complete_closure_step( + exec_ctx, &stream_global->send_initial_metadata_finished, 1); + } + if (stream_writing->sent_message) { + GPR_ASSERT(stream_writing->send_message == NULL); + grpc_chttp2_complete_closure_step( + exec_ctx, &stream_global->send_message_finished, 1); + stream_writing->sent_message = 0; + } + if (stream_writing->sent_trailing_metadata) { + grpc_chttp2_complete_closure_step( + exec_ctx, &stream_global->send_trailing_metadata_finished, 1); + } + if (stream_writing->sent_trailing_metadata) { + grpc_chttp2_mark_stream_closed(exec_ctx, transport_global, stream_global, + !transport_global->is_client, 1); + } + GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, "chttp2_writing"); + } + gpr_slice_buffer_reset_and_unref(&transport_writing->outbuf); +} diff --git a/src/core/lib/transport/chttp2_transport.c b/src/core/lib/transport/chttp2_transport.c new file mode 100644 index 0000000000..7fed3d8b47 --- /dev/null +++ b/src/core/lib/transport/chttp2_transport.c @@ -0,0 +1,1785 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/chttp2_transport.h" + +#include <math.h> +#include <stdio.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/slice_buffer.h> +#include <grpc/support/string_util.h> +#include <grpc/support/useful.h> + +#include "src/core/lib/profiling/timers.h" +#include "src/core/lib/support/string.h" +#include "src/core/lib/transport/chttp2/http2_errors.h" +#include "src/core/lib/transport/chttp2/internal.h" +#include "src/core/lib/transport/chttp2/status_conversion.h" +#include "src/core/lib/transport/chttp2/timeout_encoding.h" +#include "src/core/lib/transport/static_metadata.h" +#include "src/core/lib/transport/transport_impl.h" + +#define DEFAULT_WINDOW 65535 +#define DEFAULT_CONNECTION_WINDOW_TARGET (1024 * 1024) +#define MAX_WINDOW 0x7fffffffu + +#define MAX_CLIENT_STREAM_ID 0x7fffffffu + +int grpc_http_trace = 0; +int grpc_flowctl_trace = 0; + +#define TRANSPORT_FROM_WRITING(tw) \ + ((grpc_chttp2_transport *)((char *)(tw)-offsetof(grpc_chttp2_transport, \ + writing))) + +#define TRANSPORT_FROM_PARSING(tw) \ + ((grpc_chttp2_transport *)((char *)(tw)-offsetof(grpc_chttp2_transport, \ + parsing))) + +#define TRANSPORT_FROM_GLOBAL(tg) \ + ((grpc_chttp2_transport *)((char *)(tg)-offsetof(grpc_chttp2_transport, \ + global))) + +#define STREAM_FROM_GLOBAL(sg) \ + ((grpc_chttp2_stream *)((char *)(sg)-offsetof(grpc_chttp2_stream, global))) + +#define STREAM_FROM_PARSING(sg) \ + ((grpc_chttp2_stream *)((char *)(sg)-offsetof(grpc_chttp2_stream, parsing))) + +static const grpc_transport_vtable vtable; + +static void lock(grpc_chttp2_transport *t); +static void unlock(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t); + +/* forward declarations of various callbacks that we'll build closures around */ +static void writing_action(grpc_exec_ctx *exec_ctx, void *t, + bool iomgr_success_ignored); + +/** Set a transport level setting, and push it to our peer */ +static void push_setting(grpc_chttp2_transport *t, grpc_chttp2_setting_id id, + uint32_t value); + +/** Endpoint callback to process incoming data */ +static void recv_data(grpc_exec_ctx *exec_ctx, void *tp, bool success); + +/** Start disconnection chain */ +static void drop_connection(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t); + +/** Perform a transport_op */ +static void perform_stream_op_locked( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global, grpc_transport_stream_op *op); + +/** Cancel a stream: coming from the transport API */ +static void cancel_from_api(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global, + grpc_status_code status); + +static void close_from_api(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global, + grpc_status_code status, + gpr_slice *optional_message); + +/** Add endpoint from this transport to pollset */ +static void add_to_pollset_locked(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport *t, + grpc_pollset *pollset); +static void add_to_pollset_set_locked(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport *t, + grpc_pollset_set *pollset_set); + +/** Start new streams that have been created if we can */ +static void maybe_start_some_streams( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global); + +static void connectivity_state_set( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global, + grpc_connectivity_state state, const char *reason); + +static void check_read_ops(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_global *transport_global); + +static void incoming_byte_stream_update_flow_control( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global, size_t max_size_hint, + size_t have_already); + +static void fail_pending_writes(grpc_exec_ctx *exec_ctx, + grpc_chttp2_stream_global *stream_global); + +/******************************************************************************* + * CONSTRUCTION/DESTRUCTION/REFCOUNTING + */ + +static void destruct_transport(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport *t) { + size_t i; + + gpr_mu_lock(&t->mu); + + GPR_ASSERT(t->ep == NULL); + + gpr_slice_buffer_destroy(&t->global.qbuf); + + gpr_slice_buffer_destroy(&t->writing.outbuf); + grpc_chttp2_hpack_compressor_destroy(&t->writing.hpack_compressor); + + gpr_slice_buffer_destroy(&t->parsing.qbuf); + gpr_slice_buffer_destroy(&t->read_buffer); + grpc_chttp2_hpack_parser_destroy(&t->parsing.hpack_parser); + grpc_chttp2_goaway_parser_destroy(&t->parsing.goaway_parser); + + for (i = 0; i < STREAM_LIST_COUNT; i++) { + GPR_ASSERT(t->lists[i].head == NULL); + GPR_ASSERT(t->lists[i].tail == NULL); + } + + GPR_ASSERT(grpc_chttp2_stream_map_size(&t->parsing_stream_map) == 0); + GPR_ASSERT(grpc_chttp2_stream_map_size(&t->new_stream_map) == 0); + + grpc_chttp2_stream_map_destroy(&t->parsing_stream_map); + grpc_chttp2_stream_map_destroy(&t->new_stream_map); + grpc_connectivity_state_destroy(exec_ctx, &t->channel_callback.state_tracker); + + gpr_mu_unlock(&t->mu); + gpr_mu_destroy(&t->mu); + + /* callback remaining pings: they're not allowed to call into the transpot, + and maybe they hold resources that need to be freed */ + while (t->global.pings.next != &t->global.pings) { + grpc_chttp2_outstanding_ping *ping = t->global.pings.next; + grpc_exec_ctx_enqueue(exec_ctx, ping->on_recv, false, NULL); + ping->next->prev = ping->prev; + ping->prev->next = ping->next; + gpr_free(ping); + } + + gpr_free(t->peer_string); + gpr_free(t); +} + +#ifdef REFCOUNTING_DEBUG +#define REF_TRANSPORT(t, r) ref_transport(t, r, __FILE__, __LINE__) +#define UNREF_TRANSPORT(cl, t, r) unref_transport(cl, t, r, __FILE__, __LINE__) +static void unref_transport(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t, + const char *reason, const char *file, int line) { + gpr_log(GPR_DEBUG, "chttp2:unref:%p %d->%d %s [%s:%d]", t, t->refs.count, + t->refs.count - 1, reason, file, line); + if (!gpr_unref(&t->refs)) return; + destruct_transport(exec_ctx, t); +} + +static void ref_transport(grpc_chttp2_transport *t, const char *reason, + const char *file, int line) { + gpr_log(GPR_DEBUG, "chttp2: ref:%p %d->%d %s [%s:%d]", t, t->refs.count, + t->refs.count + 1, reason, file, line); + gpr_ref(&t->refs); +} +#else +#define REF_TRANSPORT(t, r) ref_transport(t) +#define UNREF_TRANSPORT(cl, t, r) unref_transport(cl, t) +static void unref_transport(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t) { + if (!gpr_unref(&t->refs)) return; + destruct_transport(exec_ctx, t); +} + +static void ref_transport(grpc_chttp2_transport *t) { gpr_ref(&t->refs); } +#endif + +static void init_transport(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t, + const grpc_channel_args *channel_args, + grpc_endpoint *ep, uint8_t is_client) { + size_t i; + int j; + + GPR_ASSERT(strlen(GRPC_CHTTP2_CLIENT_CONNECT_STRING) == + GRPC_CHTTP2_CLIENT_CONNECT_STRLEN); + + memset(t, 0, sizeof(*t)); + + t->base.vtable = &vtable; + t->ep = ep; + /* one ref is for destroy, the other for when ep becomes NULL */ + gpr_ref_init(&t->refs, 2); + /* ref is dropped at transport close() */ + gpr_ref_init(&t->shutdown_ep_refs, 1); + gpr_mu_init(&t->mu); + t->peer_string = grpc_endpoint_get_peer(ep); + t->endpoint_reading = 1; + t->global.next_stream_id = is_client ? 1 : 2; + t->global.is_client = is_client; + t->writing.outgoing_window = DEFAULT_WINDOW; + t->parsing.incoming_window = DEFAULT_WINDOW; + t->global.stream_lookahead = DEFAULT_WINDOW; + t->global.connection_window_target = DEFAULT_CONNECTION_WINDOW_TARGET; + t->global.ping_counter = 1; + t->global.pings.next = t->global.pings.prev = &t->global.pings; + t->parsing.is_client = is_client; + t->parsing.deframe_state = + is_client ? GRPC_DTS_FH_0 : GRPC_DTS_CLIENT_PREFIX_0; + t->writing.is_client = is_client; + grpc_connectivity_state_init( + &t->channel_callback.state_tracker, GRPC_CHANNEL_READY, + is_client ? "client_transport" : "server_transport"); + + gpr_slice_buffer_init(&t->global.qbuf); + + gpr_slice_buffer_init(&t->writing.outbuf); + grpc_chttp2_hpack_compressor_init(&t->writing.hpack_compressor); + grpc_closure_init(&t->writing_action, writing_action, t); + + gpr_slice_buffer_init(&t->parsing.qbuf); + grpc_chttp2_goaway_parser_init(&t->parsing.goaway_parser); + grpc_chttp2_hpack_parser_init(&t->parsing.hpack_parser); + + grpc_closure_init(&t->writing.done_cb, grpc_chttp2_terminate_writing, + &t->writing); + grpc_closure_init(&t->recv_data, recv_data, t); + gpr_slice_buffer_init(&t->read_buffer); + + if (is_client) { + gpr_slice_buffer_add( + &t->global.qbuf, + gpr_slice_from_copied_string(GRPC_CHTTP2_CLIENT_CONNECT_STRING)); + } + /* 8 is a random stab in the dark as to a good initial size: it's small enough + that it shouldn't waste memory for infrequently used connections, yet + large enough that the exponential growth should happen nicely when it's + needed. + TODO(ctiller): tune this */ + grpc_chttp2_stream_map_init(&t->parsing_stream_map, 8); + grpc_chttp2_stream_map_init(&t->new_stream_map, 8); + + /* copy in initial settings to all setting sets */ + for (i = 0; i < GRPC_CHTTP2_NUM_SETTINGS; i++) { + t->parsing.settings[i] = grpc_chttp2_settings_parameters[i].default_value; + for (j = 0; j < GRPC_NUM_SETTING_SETS; j++) { + t->global.settings[j][i] = + grpc_chttp2_settings_parameters[i].default_value; + } + } + t->global.dirtied_local_settings = 1; + /* Hack: it's common for implementations to assume 65536 bytes initial send + window -- this should by rights be 0 */ + t->global.force_send_settings = 1 << GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + t->global.sent_local_settings = 0; + + /* configure http2 the way we like it */ + if (is_client) { + push_setting(t, GRPC_CHTTP2_SETTINGS_ENABLE_PUSH, 0); + push_setting(t, GRPC_CHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 0); + } + push_setting(t, GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, DEFAULT_WINDOW); + + if (channel_args) { + for (i = 0; i < channel_args->num_args; i++) { + if (0 == + strcmp(channel_args->args[i].key, GRPC_ARG_MAX_CONCURRENT_STREAMS)) { + if (is_client) { + gpr_log(GPR_ERROR, "%s: is ignored on the client", + GRPC_ARG_MAX_CONCURRENT_STREAMS); + } else if (channel_args->args[i].type != GRPC_ARG_INTEGER) { + gpr_log(GPR_ERROR, "%s: must be an integer", + GRPC_ARG_MAX_CONCURRENT_STREAMS); + } else { + push_setting(t, GRPC_CHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, + (uint32_t)channel_args->args[i].value.integer); + } + } else if (0 == strcmp(channel_args->args[i].key, + GRPC_ARG_HTTP2_INITIAL_SEQUENCE_NUMBER)) { + if (channel_args->args[i].type != GRPC_ARG_INTEGER) { + gpr_log(GPR_ERROR, "%s: must be an integer", + GRPC_ARG_HTTP2_INITIAL_SEQUENCE_NUMBER); + } else if ((t->global.next_stream_id & 1) != + (channel_args->args[i].value.integer & 1)) { + gpr_log(GPR_ERROR, "%s: low bit must be %d on %s", + GRPC_ARG_HTTP2_INITIAL_SEQUENCE_NUMBER, + t->global.next_stream_id & 1, + is_client ? "client" : "server"); + } else { + t->global.next_stream_id = + (uint32_t)channel_args->args[i].value.integer; + } + } else if (0 == strcmp(channel_args->args[i].key, + GRPC_ARG_HTTP2_STREAM_LOOKAHEAD_BYTES)) { + if (channel_args->args[i].type != GRPC_ARG_INTEGER) { + gpr_log(GPR_ERROR, "%s: must be an integer", + GRPC_ARG_HTTP2_STREAM_LOOKAHEAD_BYTES); + } else if (channel_args->args[i].value.integer <= 5) { + gpr_log(GPR_ERROR, "%s: must be at least 5", + GRPC_ARG_HTTP2_STREAM_LOOKAHEAD_BYTES); + } else { + t->global.stream_lookahead = + (uint32_t)channel_args->args[i].value.integer; + } + } else if (0 == strcmp(channel_args->args[i].key, + GRPC_ARG_HTTP2_HPACK_TABLE_SIZE_DECODER)) { + if (channel_args->args[i].type != GRPC_ARG_INTEGER) { + gpr_log(GPR_ERROR, "%s: must be an integer", + GRPC_ARG_HTTP2_HPACK_TABLE_SIZE_DECODER); + } else if (channel_args->args[i].value.integer < 0) { + gpr_log(GPR_ERROR, "%s: must be non-negative", + GRPC_ARG_HTTP2_HPACK_TABLE_SIZE_DECODER); + } else { + push_setting(t, GRPC_CHTTP2_SETTINGS_HEADER_TABLE_SIZE, + (uint32_t)channel_args->args[i].value.integer); + } + } else if (0 == strcmp(channel_args->args[i].key, + GRPC_ARG_HTTP2_HPACK_TABLE_SIZE_ENCODER)) { + if (channel_args->args[i].type != GRPC_ARG_INTEGER) { + gpr_log(GPR_ERROR, "%s: must be an integer", + GRPC_ARG_HTTP2_HPACK_TABLE_SIZE_ENCODER); + } else if (channel_args->args[i].value.integer < 0) { + gpr_log(GPR_ERROR, "%s: must be non-negative", + GRPC_ARG_HTTP2_HPACK_TABLE_SIZE_ENCODER); + } else { + grpc_chttp2_hpack_compressor_set_max_usable_size( + &t->writing.hpack_compressor, + (uint32_t)channel_args->args[i].value.integer); + } + } + } + } +} + +static void destroy_transport(grpc_exec_ctx *exec_ctx, grpc_transport *gt) { + grpc_chttp2_transport *t = (grpc_chttp2_transport *)gt; + + lock(t); + t->destroying = 1; + drop_connection(exec_ctx, t); + unlock(exec_ctx, t); + + UNREF_TRANSPORT(exec_ctx, t, "destroy"); +} + +/** block grpc_endpoint_shutdown being called until a paired + allow_endpoint_shutdown is made */ +static void prevent_endpoint_shutdown(grpc_chttp2_transport *t) { + GPR_ASSERT(t->ep); + gpr_ref(&t->shutdown_ep_refs); +} + +static void allow_endpoint_shutdown_locked(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport *t) { + if (gpr_unref(&t->shutdown_ep_refs)) { + if (t->ep) { + grpc_endpoint_shutdown(exec_ctx, t->ep); + } + } +} + +static void allow_endpoint_shutdown_unlocked(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport *t) { + if (gpr_unref(&t->shutdown_ep_refs)) { + gpr_mu_lock(&t->mu); + if (t->ep) { + grpc_endpoint_shutdown(exec_ctx, t->ep); + } + gpr_mu_unlock(&t->mu); + } +} + +static void destroy_endpoint(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport *t) { + grpc_endpoint_destroy(exec_ctx, t->ep); + t->ep = NULL; + /* safe because we'll still have the ref for write */ + UNREF_TRANSPORT(exec_ctx, t, "disconnect"); +} + +static void close_transport_locked(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport *t) { + if (!t->closed) { + t->closed = 1; + connectivity_state_set(exec_ctx, &t->global, GRPC_CHANNEL_FATAL_FAILURE, + "close_transport"); + if (t->ep) { + allow_endpoint_shutdown_locked(exec_ctx, t); + } + + /* flush writable stream list to avoid dangling references */ + grpc_chttp2_stream_global *stream_global; + grpc_chttp2_stream_writing *stream_writing; + while (grpc_chttp2_list_pop_writable_stream( + &t->global, &t->writing, &stream_global, &stream_writing)) { + GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, "chttp2_writing"); + } + } +} + +#ifdef GRPC_STREAM_REFCOUNT_DEBUG +void grpc_chttp2_stream_ref(grpc_chttp2_stream_global *stream_global, + const char *reason) { + grpc_stream_ref(STREAM_FROM_GLOBAL(stream_global)->refcount, reason); +} +void grpc_chttp2_stream_unref(grpc_exec_ctx *exec_ctx, + grpc_chttp2_stream_global *stream_global, + const char *reason) { + grpc_stream_unref(exec_ctx, STREAM_FROM_GLOBAL(stream_global)->refcount, + reason); +} +#else +void grpc_chttp2_stream_ref(grpc_chttp2_stream_global *stream_global) { + grpc_stream_ref(STREAM_FROM_GLOBAL(stream_global)->refcount); +} +void grpc_chttp2_stream_unref(grpc_exec_ctx *exec_ctx, + grpc_chttp2_stream_global *stream_global) { + grpc_stream_unref(exec_ctx, STREAM_FROM_GLOBAL(stream_global)->refcount); +} +#endif + +static int init_stream(grpc_exec_ctx *exec_ctx, grpc_transport *gt, + grpc_stream *gs, grpc_stream_refcount *refcount, + const void *server_data) { + grpc_chttp2_transport *t = (grpc_chttp2_transport *)gt; + grpc_chttp2_stream *s = (grpc_chttp2_stream *)gs; + + memset(s, 0, sizeof(*s)); + + s->refcount = refcount; + GRPC_CHTTP2_STREAM_REF(&s->global, "chttp2"); + + grpc_chttp2_incoming_metadata_buffer_init(&s->parsing.metadata_buffer[0]); + grpc_chttp2_incoming_metadata_buffer_init(&s->parsing.metadata_buffer[1]); + grpc_chttp2_incoming_metadata_buffer_init( + &s->global.received_initial_metadata); + grpc_chttp2_incoming_metadata_buffer_init( + &s->global.received_trailing_metadata); + grpc_chttp2_data_parser_init(&s->parsing.data_parser); + gpr_slice_buffer_init(&s->writing.flow_controlled_buffer); + + REF_TRANSPORT(t, "stream"); + + lock(t); + grpc_chttp2_register_stream(t, s); + if (server_data) { + GPR_ASSERT(t->parsing_active); + s->global.id = (uint32_t)(uintptr_t)server_data; + s->parsing.id = s->global.id; + s->global.outgoing_window = + t->global.settings[GRPC_PEER_SETTINGS] + [GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE]; + s->parsing.incoming_window = s->global.max_recv_bytes = + t->global.settings[GRPC_SENT_SETTINGS] + [GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE]; + *t->accepting_stream = s; + grpc_chttp2_stream_map_add(&t->parsing_stream_map, s->global.id, s); + s->global.in_stream_map = 1; + } + unlock(exec_ctx, t); + + return 0; +} + +static void destroy_stream(grpc_exec_ctx *exec_ctx, grpc_transport *gt, + grpc_stream *gs) { + grpc_chttp2_transport *t = (grpc_chttp2_transport *)gt; + grpc_chttp2_stream *s = (grpc_chttp2_stream *)gs; + int i; + grpc_byte_stream *bs; + + GPR_TIMER_BEGIN("destroy_stream", 0); + + gpr_mu_lock(&t->mu); + + GPR_ASSERT((s->global.write_closed && s->global.read_closed) || + s->global.id == 0); + GPR_ASSERT(!s->global.in_stream_map); + if (grpc_chttp2_unregister_stream(t, s) && t->global.sent_goaway) { + close_transport_locked(exec_ctx, t); + } + if (!t->parsing_active && s->global.id) { + GPR_ASSERT(grpc_chttp2_stream_map_find(&t->parsing_stream_map, + s->global.id) == NULL); + } + + grpc_chttp2_list_remove_unannounced_incoming_window_available(&t->global, + &s->global); + grpc_chttp2_list_remove_stalled_by_transport(&t->global, &s->global); + + gpr_mu_unlock(&t->mu); + + for (i = 0; i < STREAM_LIST_COUNT; i++) { + if (s->included[i]) { + gpr_log(GPR_ERROR, "%s stream %d still included in list %d", + t->global.is_client ? "client" : "server", s->global.id, i); + abort(); + } + } + + while ( + (bs = grpc_chttp2_incoming_frame_queue_pop(&s->global.incoming_frames))) { + grpc_byte_stream_destroy(exec_ctx, bs); + } + + GPR_ASSERT(s->global.send_initial_metadata_finished == NULL); + GPR_ASSERT(s->global.send_message_finished == NULL); + GPR_ASSERT(s->global.send_trailing_metadata_finished == NULL); + GPR_ASSERT(s->global.recv_initial_metadata_ready == NULL); + GPR_ASSERT(s->global.recv_message_ready == NULL); + GPR_ASSERT(s->global.recv_trailing_metadata_finished == NULL); + grpc_chttp2_data_parser_destroy(exec_ctx, &s->parsing.data_parser); + grpc_chttp2_incoming_metadata_buffer_destroy(&s->parsing.metadata_buffer[0]); + grpc_chttp2_incoming_metadata_buffer_destroy(&s->parsing.metadata_buffer[1]); + grpc_chttp2_incoming_metadata_buffer_destroy( + &s->global.received_initial_metadata); + grpc_chttp2_incoming_metadata_buffer_destroy( + &s->global.received_trailing_metadata); + gpr_slice_buffer_destroy(&s->writing.flow_controlled_buffer); + + UNREF_TRANSPORT(exec_ctx, t, "stream"); + + GPR_TIMER_END("destroy_stream", 0); +} + +grpc_chttp2_stream_parsing *grpc_chttp2_parsing_lookup_stream( + grpc_chttp2_transport_parsing *transport_parsing, uint32_t id) { + grpc_chttp2_transport *t = TRANSPORT_FROM_PARSING(transport_parsing); + grpc_chttp2_stream *s = + grpc_chttp2_stream_map_find(&t->parsing_stream_map, id); + return s ? &s->parsing : NULL; +} + +grpc_chttp2_stream_parsing *grpc_chttp2_parsing_accept_stream( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing, + uint32_t id) { + grpc_chttp2_stream *accepting; + grpc_chttp2_transport *t = TRANSPORT_FROM_PARSING(transport_parsing); + GPR_ASSERT(t->accepting_stream == NULL); + t->accepting_stream = &accepting; + t->channel_callback.accept_stream(exec_ctx, + t->channel_callback.accept_stream_user_data, + &t->base, (void *)(uintptr_t)id); + t->accepting_stream = NULL; + return &accepting->parsing; +} + +/******************************************************************************* + * LOCK MANAGEMENT + */ + +/* We take a grpc_chttp2_transport-global lock in response to calls coming in + from above, + and in response to data being received from below. New data to be written + is always queued, as are callbacks to process data. During unlock() we + check our todo lists and initiate callbacks and flush writes. */ + +static void lock(grpc_chttp2_transport *t) { gpr_mu_lock(&t->mu); } + +static void unlock(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t) { + GPR_TIMER_BEGIN("unlock", 0); + if (!t->writing_active && !t->closed && + grpc_chttp2_unlocking_check_writes(exec_ctx, &t->global, &t->writing, + t->parsing_active)) { + t->writing_active = 1; + REF_TRANSPORT(t, "writing"); + grpc_exec_ctx_enqueue(exec_ctx, &t->writing_action, true, NULL); + prevent_endpoint_shutdown(t); + } + check_read_ops(exec_ctx, &t->global); + + gpr_mu_unlock(&t->mu); + GPR_TIMER_END("unlock", 0); +} + +/******************************************************************************* + * OUTPUT PROCESSING + */ + +void grpc_chttp2_become_writable(grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global) { + if (!TRANSPORT_FROM_GLOBAL(transport_global)->closed && + grpc_chttp2_list_add_writable_stream(transport_global, stream_global)) { + GRPC_CHTTP2_STREAM_REF(stream_global, "chttp2_writing"); + } +} + +static void push_setting(grpc_chttp2_transport *t, grpc_chttp2_setting_id id, + uint32_t value) { + const grpc_chttp2_setting_parameters *sp = + &grpc_chttp2_settings_parameters[id]; + uint32_t use_value = GPR_CLAMP(value, sp->min_value, sp->max_value); + if (use_value != value) { + gpr_log(GPR_INFO, "Requested parameter %s clamped from %d to %d", sp->name, + value, use_value); + } + if (use_value != t->global.settings[GRPC_LOCAL_SETTINGS][id]) { + t->global.settings[GRPC_LOCAL_SETTINGS][id] = use_value; + t->global.dirtied_local_settings = 1; + } +} + +void grpc_chttp2_terminate_writing(grpc_exec_ctx *exec_ctx, + void *transport_writing_ptr, bool success) { + grpc_chttp2_transport_writing *transport_writing = transport_writing_ptr; + grpc_chttp2_transport *t = TRANSPORT_FROM_WRITING(transport_writing); + grpc_chttp2_stream_global *stream_global; + + GPR_TIMER_BEGIN("grpc_chttp2_terminate_writing", 0); + + lock(t); + + allow_endpoint_shutdown_locked(exec_ctx, t); + + if (!success) { + drop_connection(exec_ctx, t); + } + + grpc_chttp2_cleanup_writing(exec_ctx, &t->global, &t->writing); + + while (grpc_chttp2_list_pop_closed_waiting_for_writing(&t->global, + &stream_global)) { + fail_pending_writes(exec_ctx, stream_global); + GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, "finish_writes"); + } + + /* leave the writing flag up on shutdown to prevent further writes in unlock() + from starting */ + t->writing_active = 0; + if (t->ep && !t->endpoint_reading) { + destroy_endpoint(exec_ctx, t); + } + + unlock(exec_ctx, t); + + UNREF_TRANSPORT(exec_ctx, t, "writing"); + + GPR_TIMER_END("grpc_chttp2_terminate_writing", 0); +} + +static void writing_action(grpc_exec_ctx *exec_ctx, void *gt, + bool iomgr_success_ignored) { + grpc_chttp2_transport *t = gt; + GPR_TIMER_BEGIN("writing_action", 0); + grpc_chttp2_perform_writes(exec_ctx, &t->writing, t->ep); + GPR_TIMER_END("writing_action", 0); +} + +void grpc_chttp2_add_incoming_goaway( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global, + uint32_t goaway_error, gpr_slice goaway_text) { + char *msg = gpr_dump_slice(goaway_text, GPR_DUMP_HEX | GPR_DUMP_ASCII); + gpr_log(GPR_DEBUG, "got goaway [%d]: %s", goaway_error, msg); + gpr_free(msg); + gpr_slice_unref(goaway_text); + transport_global->seen_goaway = 1; + connectivity_state_set(exec_ctx, transport_global, GRPC_CHANNEL_FATAL_FAILURE, + "got_goaway"); +} + +static void maybe_start_some_streams( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global) { + grpc_chttp2_stream_global *stream_global; + uint32_t stream_incoming_window; + /* start streams where we have free grpc_chttp2_stream ids and free + * concurrency */ + while (transport_global->next_stream_id <= MAX_CLIENT_STREAM_ID && + transport_global->concurrent_stream_count < + transport_global + ->settings[GRPC_PEER_SETTINGS] + [GRPC_CHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS] && + grpc_chttp2_list_pop_waiting_for_concurrency(transport_global, + &stream_global)) { + /* safe since we can't (legally) be parsing this stream yet */ + grpc_chttp2_stream_parsing *stream_parsing = + &STREAM_FROM_GLOBAL(stream_global)->parsing; + GRPC_CHTTP2_IF_TRACING(gpr_log( + GPR_DEBUG, "HTTP:%s: Allocating new grpc_chttp2_stream %p to id %d", + transport_global->is_client ? "CLI" : "SVR", stream_global, + transport_global->next_stream_id)); + + GPR_ASSERT(stream_global->id == 0); + stream_global->id = stream_parsing->id = transport_global->next_stream_id; + transport_global->next_stream_id += 2; + + if (transport_global->next_stream_id >= MAX_CLIENT_STREAM_ID) { + connectivity_state_set(exec_ctx, transport_global, + GRPC_CHANNEL_TRANSIENT_FAILURE, + "no_more_stream_ids"); + } + + stream_global->outgoing_window = + transport_global->settings[GRPC_PEER_SETTINGS] + [GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE]; + stream_parsing->incoming_window = stream_incoming_window = + transport_global->settings[GRPC_SENT_SETTINGS] + [GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE]; + stream_global->max_recv_bytes = + GPR_MAX(stream_incoming_window, stream_global->max_recv_bytes); + grpc_chttp2_stream_map_add( + &TRANSPORT_FROM_GLOBAL(transport_global)->new_stream_map, + stream_global->id, STREAM_FROM_GLOBAL(stream_global)); + stream_global->in_stream_map = 1; + transport_global->concurrent_stream_count++; + grpc_chttp2_become_writable(transport_global, stream_global); + } + /* cancel out streams that will never be started */ + while (transport_global->next_stream_id >= MAX_CLIENT_STREAM_ID && + grpc_chttp2_list_pop_waiting_for_concurrency(transport_global, + &stream_global)) { + cancel_from_api(exec_ctx, transport_global, stream_global, + GRPC_STATUS_UNAVAILABLE); + } +} + +static grpc_closure *add_closure_barrier(grpc_closure *closure) { + closure->final_data += 2; + return closure; +} + +void grpc_chttp2_complete_closure_step(grpc_exec_ctx *exec_ctx, + grpc_closure **pclosure, int success) { + grpc_closure *closure = *pclosure; + if (closure == NULL) { + return; + } + closure->final_data -= 2; + if (!success) { + closure->final_data |= 1; + } + if (closure->final_data < 2) { + grpc_exec_ctx_enqueue(exec_ctx, closure, closure->final_data == 0, NULL); + } + *pclosure = NULL; +} + +static int contains_non_ok_status( + grpc_chttp2_transport_global *transport_global, + grpc_metadata_batch *batch) { + grpc_linked_mdelem *l; + for (l = batch->list.head; l; l = l->next) { + if (l->md->key == GRPC_MDSTR_GRPC_STATUS && + l->md != GRPC_MDELEM_GRPC_STATUS_0) { + return 1; + } + } + return 0; +} + +static void do_nothing(grpc_exec_ctx *exec_ctx, void *arg, bool success) {} + +static void perform_stream_op_locked( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global, grpc_transport_stream_op *op) { + grpc_closure *on_complete; + + GPR_TIMER_BEGIN("perform_stream_op_locked", 0); + + on_complete = op->on_complete; + if (on_complete == NULL) { + on_complete = grpc_closure_create(do_nothing, NULL); + } + /* use final_data as a barrier until enqueue time; the inital counter is + dropped at the end of this function */ + on_complete->final_data = 2; + + if (op->cancel_with_status != GRPC_STATUS_OK) { + cancel_from_api(exec_ctx, transport_global, stream_global, + op->cancel_with_status); + } + + if (op->close_with_status != GRPC_STATUS_OK) { + close_from_api(exec_ctx, transport_global, stream_global, + op->close_with_status, op->optional_close_message); + } + + if (op->send_initial_metadata != NULL) { + GPR_ASSERT(stream_global->send_initial_metadata_finished == NULL); + stream_global->send_initial_metadata_finished = + add_closure_barrier(on_complete); + stream_global->send_initial_metadata = op->send_initial_metadata; + if (contains_non_ok_status(transport_global, op->send_initial_metadata)) { + stream_global->seen_error = 1; + grpc_chttp2_list_add_check_read_ops(transport_global, stream_global); + } + if (!stream_global->write_closed) { + if (transport_global->is_client) { + GPR_ASSERT(stream_global->id == 0); + grpc_chttp2_list_add_waiting_for_concurrency(transport_global, + stream_global); + maybe_start_some_streams(exec_ctx, transport_global); + } else { + GPR_ASSERT(stream_global->id != 0); + grpc_chttp2_become_writable(transport_global, stream_global); + } + } else { + grpc_chttp2_complete_closure_step( + exec_ctx, &stream_global->send_initial_metadata_finished, 0); + } + } + + if (op->send_message != NULL) { + GPR_ASSERT(stream_global->send_message_finished == NULL); + GPR_ASSERT(stream_global->send_message == NULL); + stream_global->send_message_finished = add_closure_barrier(on_complete); + if (stream_global->write_closed) { + grpc_chttp2_complete_closure_step( + exec_ctx, &stream_global->send_message_finished, 0); + } else { + stream_global->send_message = op->send_message; + if (stream_global->id != 0) { + grpc_chttp2_become_writable(transport_global, stream_global); + } + } + } + + if (op->send_trailing_metadata != NULL) { + GPR_ASSERT(stream_global->send_trailing_metadata_finished == NULL); + stream_global->send_trailing_metadata_finished = + add_closure_barrier(on_complete); + stream_global->send_trailing_metadata = op->send_trailing_metadata; + if (contains_non_ok_status(transport_global, op->send_trailing_metadata)) { + stream_global->seen_error = 1; + grpc_chttp2_list_add_check_read_ops(transport_global, stream_global); + } + if (stream_global->write_closed) { + grpc_chttp2_complete_closure_step( + exec_ctx, &stream_global->send_trailing_metadata_finished, + grpc_metadata_batch_is_empty(op->send_trailing_metadata)); + } else if (stream_global->id != 0) { + /* TODO(ctiller): check if there's flow control for any outstanding + bytes before going writable */ + grpc_chttp2_become_writable(transport_global, stream_global); + } + } + + if (op->recv_initial_metadata != NULL) { + GPR_ASSERT(stream_global->recv_initial_metadata_ready == NULL); + stream_global->recv_initial_metadata_ready = + op->recv_initial_metadata_ready; + stream_global->recv_initial_metadata = op->recv_initial_metadata; + grpc_chttp2_list_add_check_read_ops(transport_global, stream_global); + } + + if (op->recv_message != NULL) { + GPR_ASSERT(stream_global->recv_message_ready == NULL); + stream_global->recv_message_ready = op->recv_message_ready; + stream_global->recv_message = op->recv_message; + if (stream_global->id != 0 && + (stream_global->incoming_frames.head == NULL || + stream_global->incoming_frames.head->is_tail)) { + incoming_byte_stream_update_flow_control( + transport_global, stream_global, transport_global->stream_lookahead, + 0); + } + grpc_chttp2_list_add_check_read_ops(transport_global, stream_global); + } + + if (op->recv_trailing_metadata != NULL) { + GPR_ASSERT(stream_global->recv_trailing_metadata_finished == NULL); + stream_global->recv_trailing_metadata_finished = + add_closure_barrier(on_complete); + stream_global->recv_trailing_metadata = op->recv_trailing_metadata; + grpc_chttp2_list_add_check_read_ops(transport_global, stream_global); + } + + grpc_chttp2_complete_closure_step(exec_ctx, &on_complete, 1); + + GPR_TIMER_END("perform_stream_op_locked", 0); +} + +static void perform_stream_op(grpc_exec_ctx *exec_ctx, grpc_transport *gt, + grpc_stream *gs, grpc_transport_stream_op *op) { + grpc_chttp2_transport *t = (grpc_chttp2_transport *)gt; + grpc_chttp2_stream *s = (grpc_chttp2_stream *)gs; + + lock(t); + perform_stream_op_locked(exec_ctx, &t->global, &s->global, op); + unlock(exec_ctx, t); +} + +static void send_ping_locked(grpc_chttp2_transport *t, grpc_closure *on_recv) { + grpc_chttp2_outstanding_ping *p = gpr_malloc(sizeof(*p)); + p->next = &t->global.pings; + p->prev = p->next->prev; + p->prev->next = p->next->prev = p; + p->id[0] = (uint8_t)((t->global.ping_counter >> 56) & 0xff); + p->id[1] = (uint8_t)((t->global.ping_counter >> 48) & 0xff); + p->id[2] = (uint8_t)((t->global.ping_counter >> 40) & 0xff); + p->id[3] = (uint8_t)((t->global.ping_counter >> 32) & 0xff); + p->id[4] = (uint8_t)((t->global.ping_counter >> 24) & 0xff); + p->id[5] = (uint8_t)((t->global.ping_counter >> 16) & 0xff); + p->id[6] = (uint8_t)((t->global.ping_counter >> 8) & 0xff); + p->id[7] = (uint8_t)(t->global.ping_counter & 0xff); + p->on_recv = on_recv; + gpr_slice_buffer_add(&t->global.qbuf, grpc_chttp2_ping_create(0, p->id)); +} + +void grpc_chttp2_ack_ping(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_parsing *transport_parsing, + const uint8_t *opaque_8bytes) { + grpc_chttp2_outstanding_ping *ping; + grpc_chttp2_transport *t = TRANSPORT_FROM_PARSING(transport_parsing); + grpc_chttp2_transport_global *transport_global = &t->global; + lock(t); + for (ping = transport_global->pings.next; ping != &transport_global->pings; + ping = ping->next) { + if (0 == memcmp(opaque_8bytes, ping->id, 8)) { + grpc_exec_ctx_enqueue(exec_ctx, ping->on_recv, true, NULL); + ping->next->prev = ping->prev; + ping->prev->next = ping->next; + gpr_free(ping); + break; + } + } + unlock(exec_ctx, t); +} + +static void perform_transport_op_locked(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport *t, + grpc_transport_op *op) { + bool close_transport = false; + + grpc_exec_ctx_enqueue(exec_ctx, op->on_consumed, true, NULL); + + if (op->on_connectivity_state_change != NULL) { + grpc_connectivity_state_notify_on_state_change( + exec_ctx, &t->channel_callback.state_tracker, op->connectivity_state, + op->on_connectivity_state_change); + } + + if (op->send_goaway) { + t->global.sent_goaway = 1; + grpc_chttp2_goaway_append( + t->global.last_incoming_stream_id, + (uint32_t)grpc_chttp2_grpc_status_to_http2_error(op->goaway_status), + gpr_slice_ref(*op->goaway_message), &t->global.qbuf); + close_transport = !grpc_chttp2_has_streams(t); + } + + if (op->set_accept_stream) { + t->channel_callback.accept_stream = op->set_accept_stream_fn; + t->channel_callback.accept_stream_user_data = + op->set_accept_stream_user_data; + } + + if (op->bind_pollset) { + add_to_pollset_locked(exec_ctx, t, op->bind_pollset); + } + + if (op->bind_pollset_set) { + add_to_pollset_set_locked(exec_ctx, t, op->bind_pollset_set); + } + + if (op->send_ping) { + send_ping_locked(t, op->send_ping); + } + + if (op->disconnect) { + close_transport_locked(exec_ctx, t); + } + + if (close_transport) { + close_transport_locked(exec_ctx, t); + } +} + +static void perform_transport_op(grpc_exec_ctx *exec_ctx, grpc_transport *gt, + grpc_transport_op *op) { + grpc_chttp2_transport *t = (grpc_chttp2_transport *)gt; + + lock(t); + + /* If there's a set_accept_stream ensure that we're not parsing + to avoid changing things out from underneath */ + if (t->parsing_active && op->set_accept_stream) { + GPR_ASSERT(t->post_parsing_op == NULL); + t->post_parsing_op = gpr_malloc(sizeof(*op)); + memcpy(t->post_parsing_op, op, sizeof(*op)); + } else { + perform_transport_op_locked(exec_ctx, t, op); + } + + unlock(exec_ctx, t); +} + +/******************************************************************************* + * INPUT PROCESSING + */ + +static void check_read_ops(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_global *transport_global) { + grpc_chttp2_stream_global *stream_global; + grpc_byte_stream *bs; + while ( + grpc_chttp2_list_pop_check_read_ops(transport_global, &stream_global)) { + if (stream_global->recv_initial_metadata_ready != NULL && + stream_global->published_initial_metadata) { + grpc_chttp2_incoming_metadata_buffer_publish( + &stream_global->received_initial_metadata, + stream_global->recv_initial_metadata); + grpc_exec_ctx_enqueue( + exec_ctx, stream_global->recv_initial_metadata_ready, true, NULL); + stream_global->recv_initial_metadata_ready = NULL; + } + if (stream_global->recv_message_ready != NULL) { + while (stream_global->seen_error && + (bs = grpc_chttp2_incoming_frame_queue_pop( + &stream_global->incoming_frames)) != NULL) { + grpc_byte_stream_destroy(exec_ctx, bs); + } + if (stream_global->incoming_frames.head != NULL) { + *stream_global->recv_message = grpc_chttp2_incoming_frame_queue_pop( + &stream_global->incoming_frames); + GPR_ASSERT(*stream_global->recv_message != NULL); + grpc_exec_ctx_enqueue(exec_ctx, stream_global->recv_message_ready, true, + NULL); + stream_global->recv_message_ready = NULL; + } else if (stream_global->published_trailing_metadata) { + *stream_global->recv_message = NULL; + grpc_exec_ctx_enqueue(exec_ctx, stream_global->recv_message_ready, true, + NULL); + stream_global->recv_message_ready = NULL; + } + } + if (stream_global->recv_trailing_metadata_finished != NULL && + stream_global->read_closed && stream_global->write_closed) { + while (stream_global->seen_error && + (bs = grpc_chttp2_incoming_frame_queue_pop( + &stream_global->incoming_frames)) != NULL) { + grpc_byte_stream_destroy(exec_ctx, bs); + } + if (stream_global->incoming_frames.head == NULL) { + grpc_chttp2_incoming_metadata_buffer_publish( + &stream_global->received_trailing_metadata, + stream_global->recv_trailing_metadata); + grpc_chttp2_complete_closure_step( + exec_ctx, &stream_global->recv_trailing_metadata_finished, 1); + } + } + } +} + +static void remove_stream(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t, + uint32_t id) { + size_t new_stream_count; + grpc_chttp2_stream *s = + grpc_chttp2_stream_map_delete(&t->parsing_stream_map, id); + if (!s) { + s = grpc_chttp2_stream_map_delete(&t->new_stream_map, id); + } + GPR_ASSERT(s); + s->global.in_stream_map = 0; + if (t->parsing.incoming_stream == &s->parsing) { + t->parsing.incoming_stream = NULL; + grpc_chttp2_parsing_become_skip_parser(exec_ctx, &t->parsing); + } + if (s->parsing.data_parser.parsing_frame != NULL) { + grpc_chttp2_incoming_byte_stream_finished( + exec_ctx, s->parsing.data_parser.parsing_frame, 0, 0); + s->parsing.data_parser.parsing_frame = NULL; + } + + if (grpc_chttp2_unregister_stream(t, s) && t->global.sent_goaway) { + close_transport_locked(exec_ctx, t); + } + if (grpc_chttp2_list_remove_writable_stream(&t->global, &s->global)) { + GRPC_CHTTP2_STREAM_UNREF(exec_ctx, &s->global, "chttp2_writing"); + } + + new_stream_count = grpc_chttp2_stream_map_size(&t->parsing_stream_map) + + grpc_chttp2_stream_map_size(&t->new_stream_map); + GPR_ASSERT(new_stream_count <= UINT32_MAX); + if (new_stream_count != t->global.concurrent_stream_count) { + t->global.concurrent_stream_count = (uint32_t)new_stream_count; + maybe_start_some_streams(exec_ctx, &t->global); + } +} + +static void cancel_from_api(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global, + grpc_status_code status) { + if (stream_global->id != 0) { + gpr_slice_buffer_add( + &transport_global->qbuf, + grpc_chttp2_rst_stream_create( + stream_global->id, + (uint32_t)grpc_chttp2_grpc_status_to_http2_error(status))); + } + grpc_chttp2_fake_status(exec_ctx, transport_global, stream_global, status, + NULL); + grpc_chttp2_mark_stream_closed(exec_ctx, transport_global, stream_global, 1, + 1); +} + +void grpc_chttp2_fake_status(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global, + grpc_status_code status, gpr_slice *slice) { + if (status != GRPC_STATUS_OK) { + stream_global->seen_error = 1; + grpc_chttp2_list_add_check_read_ops(transport_global, stream_global); + } + /* stream_global->recv_trailing_metadata_finished gives us a + last chance replacement: we've received trailing metadata, + but something more important has become available to signal + to the upper layers - drop what we've got, and then publish + what we want - which is safe because we haven't told anyone + about the metadata yet */ + if (!stream_global->published_trailing_metadata || + stream_global->recv_trailing_metadata_finished != NULL) { + char status_string[GPR_LTOA_MIN_BUFSIZE]; + gpr_ltoa(status, status_string); + grpc_chttp2_incoming_metadata_buffer_add( + &stream_global->received_trailing_metadata, + grpc_mdelem_from_metadata_strings( + GRPC_MDSTR_GRPC_STATUS, grpc_mdstr_from_string(status_string))); + if (slice) { + grpc_chttp2_incoming_metadata_buffer_add( + &stream_global->received_trailing_metadata, + grpc_mdelem_from_metadata_strings( + GRPC_MDSTR_GRPC_MESSAGE, + grpc_mdstr_from_slice(gpr_slice_ref(*slice)))); + } + stream_global->published_trailing_metadata = 1; + grpc_chttp2_list_add_check_read_ops(transport_global, stream_global); + } + if (slice) { + gpr_slice_unref(*slice); + } +} + +static void fail_pending_writes(grpc_exec_ctx *exec_ctx, + grpc_chttp2_stream_global *stream_global) { + grpc_chttp2_complete_closure_step( + exec_ctx, &stream_global->send_initial_metadata_finished, 0); + grpc_chttp2_complete_closure_step( + exec_ctx, &stream_global->send_trailing_metadata_finished, 0); + grpc_chttp2_complete_closure_step(exec_ctx, + &stream_global->send_message_finished, 0); +} + +void grpc_chttp2_mark_stream_closed( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global, int close_reads, + int close_writes) { + if (stream_global->read_closed && stream_global->write_closed) { + /* already closed */ + return; + } + grpc_chttp2_list_add_check_read_ops(transport_global, stream_global); + if (close_reads && !stream_global->read_closed) { + stream_global->read_closed = 1; + stream_global->published_initial_metadata = 1; + stream_global->published_trailing_metadata = 1; + } + if (close_writes && !stream_global->write_closed) { + stream_global->write_closed = 1; + if (TRANSPORT_FROM_GLOBAL(transport_global)->writing_active) { + GRPC_CHTTP2_STREAM_REF(stream_global, "finish_writes"); + grpc_chttp2_list_add_closed_waiting_for_writing(transport_global, + stream_global); + } else { + fail_pending_writes(exec_ctx, stream_global); + } + } + if (stream_global->read_closed && stream_global->write_closed) { + if (stream_global->id != 0 && + TRANSPORT_FROM_GLOBAL(transport_global)->parsing_active) { + grpc_chttp2_list_add_closed_waiting_for_parsing(transport_global, + stream_global); + } else { + if (stream_global->id != 0) { + remove_stream(exec_ctx, TRANSPORT_FROM_GLOBAL(transport_global), + stream_global->id); + } + GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, "chttp2"); + } + } +} + +static void close_from_api(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global, + grpc_status_code status, + gpr_slice *optional_message) { + gpr_slice hdr; + gpr_slice status_hdr; + gpr_slice message_pfx; + uint8_t *p; + uint32_t len = 0; + + GPR_ASSERT(status >= 0 && (int)status < 100); + + GPR_ASSERT(stream_global->id != 0); + + /* Hand roll a header block. + This is unnecessarily ugly - at some point we should find a more elegant + solution. + It's complicated by the fact that our send machinery would be dead by the + time we got around to sending this, so instead we ignore HPACK compression + and just write the uncompressed bytes onto the wire. */ + status_hdr = gpr_slice_malloc(15 + (status >= 10)); + p = GPR_SLICE_START_PTR(status_hdr); + *p++ = 0x40; /* literal header */ + *p++ = 11; /* len(grpc-status) */ + *p++ = 'g'; + *p++ = 'r'; + *p++ = 'p'; + *p++ = 'c'; + *p++ = '-'; + *p++ = 's'; + *p++ = 't'; + *p++ = 'a'; + *p++ = 't'; + *p++ = 'u'; + *p++ = 's'; + if (status < 10) { + *p++ = 1; + *p++ = (uint8_t)('0' + status); + } else { + *p++ = 2; + *p++ = (uint8_t)('0' + (status / 10)); + *p++ = (uint8_t)('0' + (status % 10)); + } + GPR_ASSERT(p == GPR_SLICE_END_PTR(status_hdr)); + len += (uint32_t)GPR_SLICE_LENGTH(status_hdr); + + if (optional_message) { + GPR_ASSERT(GPR_SLICE_LENGTH(*optional_message) < 127); + message_pfx = gpr_slice_malloc(15); + p = GPR_SLICE_START_PTR(message_pfx); + *p++ = 0x40; + *p++ = 12; /* len(grpc-message) */ + *p++ = 'g'; + *p++ = 'r'; + *p++ = 'p'; + *p++ = 'c'; + *p++ = '-'; + *p++ = 'm'; + *p++ = 'e'; + *p++ = 's'; + *p++ = 's'; + *p++ = 'a'; + *p++ = 'g'; + *p++ = 'e'; + *p++ = (uint8_t)GPR_SLICE_LENGTH(*optional_message); + GPR_ASSERT(p == GPR_SLICE_END_PTR(message_pfx)); + len += (uint32_t)GPR_SLICE_LENGTH(message_pfx); + len += (uint32_t)GPR_SLICE_LENGTH(*optional_message); + } + + hdr = gpr_slice_malloc(9); + p = GPR_SLICE_START_PTR(hdr); + *p++ = (uint8_t)(len >> 16); + *p++ = (uint8_t)(len >> 8); + *p++ = (uint8_t)(len); + *p++ = GRPC_CHTTP2_FRAME_HEADER; + *p++ = GRPC_CHTTP2_DATA_FLAG_END_STREAM | GRPC_CHTTP2_DATA_FLAG_END_HEADERS; + *p++ = (uint8_t)(stream_global->id >> 24); + *p++ = (uint8_t)(stream_global->id >> 16); + *p++ = (uint8_t)(stream_global->id >> 8); + *p++ = (uint8_t)(stream_global->id); + GPR_ASSERT(p == GPR_SLICE_END_PTR(hdr)); + + gpr_slice_buffer_add(&transport_global->qbuf, hdr); + gpr_slice_buffer_add(&transport_global->qbuf, status_hdr); + if (optional_message) { + gpr_slice_buffer_add(&transport_global->qbuf, message_pfx); + gpr_slice_buffer_add(&transport_global->qbuf, + gpr_slice_ref(*optional_message)); + } + + gpr_slice_buffer_add( + &transport_global->qbuf, + grpc_chttp2_rst_stream_create(stream_global->id, GRPC_CHTTP2_NO_ERROR)); + + if (optional_message) { + gpr_slice_ref(*optional_message); + } + grpc_chttp2_fake_status(exec_ctx, transport_global, stream_global, status, + optional_message); + grpc_chttp2_mark_stream_closed(exec_ctx, transport_global, stream_global, 1, + 1); +} + +static void cancel_stream_cb(grpc_chttp2_transport_global *transport_global, + void *user_data, + grpc_chttp2_stream_global *stream_global) { + cancel_from_api(user_data, transport_global, stream_global, + GRPC_STATUS_UNAVAILABLE); +} + +static void end_all_the_calls(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport *t) { + grpc_chttp2_for_all_streams(&t->global, exec_ctx, cancel_stream_cb); +} + +static void drop_connection(grpc_exec_ctx *exec_ctx, grpc_chttp2_transport *t) { + close_transport_locked(exec_ctx, t); + end_all_the_calls(exec_ctx, t); +} + +/** update window from a settings change */ +static void update_global_window(void *args, uint32_t id, void *stream) { + grpc_chttp2_transport *t = args; + grpc_chttp2_stream *s = stream; + grpc_chttp2_transport_global *transport_global = &t->global; + grpc_chttp2_stream_global *stream_global = &s->global; + int was_zero; + int is_zero; + int64_t initial_window_update = t->parsing.initial_window_update; + + was_zero = stream_global->outgoing_window <= 0; + GRPC_CHTTP2_FLOW_CREDIT_STREAM("settings", transport_global, stream_global, + outgoing_window, initial_window_update); + is_zero = stream_global->outgoing_window <= 0; + + if (was_zero && !is_zero) { + grpc_chttp2_become_writable(transport_global, stream_global); + } +} + +static void read_error_locked(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport *t) { + t->endpoint_reading = 0; + if (!t->writing_active && t->ep) { + destroy_endpoint(exec_ctx, t); + } +} + +/* tcp read callback */ +static void recv_data(grpc_exec_ctx *exec_ctx, void *tp, bool success) { + size_t i; + int keep_reading = 0; + grpc_chttp2_transport *t = tp; + grpc_chttp2_transport_global *transport_global = &t->global; + grpc_chttp2_transport_parsing *transport_parsing = &t->parsing; + grpc_chttp2_stream_global *stream_global; + + GPR_TIMER_BEGIN("recv_data", 0); + + lock(t); + i = 0; + GPR_ASSERT(!t->parsing_active); + if (!t->closed) { + t->parsing_active = 1; + /* merge stream lists */ + grpc_chttp2_stream_map_move_into(&t->new_stream_map, + &t->parsing_stream_map); + grpc_chttp2_prepare_to_read(transport_global, transport_parsing); + gpr_mu_unlock(&t->mu); + GPR_TIMER_BEGIN("recv_data.parse", 0); + for (; i < t->read_buffer.count && + grpc_chttp2_perform_read(exec_ctx, transport_parsing, + t->read_buffer.slices[i]); + i++) + ; + GPR_TIMER_END("recv_data.parse", 0); + gpr_mu_lock(&t->mu); + /* copy parsing qbuf to global qbuf */ + gpr_slice_buffer_move_into(&t->parsing.qbuf, &t->global.qbuf); + if (i != t->read_buffer.count) { + unlock(exec_ctx, t); + lock(t); + drop_connection(exec_ctx, t); + } + /* merge stream lists */ + grpc_chttp2_stream_map_move_into(&t->new_stream_map, + &t->parsing_stream_map); + transport_global->concurrent_stream_count = + (uint32_t)grpc_chttp2_stream_map_size(&t->parsing_stream_map); + if (transport_parsing->initial_window_update != 0) { + grpc_chttp2_stream_map_for_each(&t->parsing_stream_map, + update_global_window, t); + transport_parsing->initial_window_update = 0; + } + /* handle higher level things */ + grpc_chttp2_publish_reads(exec_ctx, transport_global, transport_parsing); + t->parsing_active = 0; + /* handle delayed transport ops (if there is one) */ + if (t->post_parsing_op) { + grpc_transport_op *op = t->post_parsing_op; + t->post_parsing_op = NULL; + perform_transport_op_locked(exec_ctx, t, op); + gpr_free(op); + } + /* if a stream is in the stream map, and gets cancelled, we need to ensure + * we are not parsing before continuing the cancellation to keep things in + * a sane state */ + while (grpc_chttp2_list_pop_closed_waiting_for_parsing(transport_global, + &stream_global)) { + GPR_ASSERT(stream_global->in_stream_map); + GPR_ASSERT(stream_global->write_closed); + GPR_ASSERT(stream_global->read_closed); + remove_stream(exec_ctx, t, stream_global->id); + GRPC_CHTTP2_STREAM_UNREF(exec_ctx, stream_global, "chttp2"); + } + } + if (!success || i != t->read_buffer.count || t->closed) { + drop_connection(exec_ctx, t); + read_error_locked(exec_ctx, t); + } else if (!t->closed) { + keep_reading = 1; + REF_TRANSPORT(t, "keep_reading"); + prevent_endpoint_shutdown(t); + } + gpr_slice_buffer_reset_and_unref(&t->read_buffer); + unlock(exec_ctx, t); + + if (keep_reading) { + grpc_endpoint_read(exec_ctx, t->ep, &t->read_buffer, &t->recv_data); + allow_endpoint_shutdown_unlocked(exec_ctx, t); + UNREF_TRANSPORT(exec_ctx, t, "keep_reading"); + } else { + UNREF_TRANSPORT(exec_ctx, t, "recv_data"); + } + + GPR_TIMER_END("recv_data", 0); +} + +/******************************************************************************* + * CALLBACK LOOP + */ + +static void connectivity_state_set( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_global *transport_global, + grpc_connectivity_state state, const char *reason) { + GRPC_CHTTP2_IF_TRACING( + gpr_log(GPR_DEBUG, "set connectivity_state=%d", state)); + grpc_connectivity_state_set( + exec_ctx, + &TRANSPORT_FROM_GLOBAL(transport_global)->channel_callback.state_tracker, + state, reason); +} + +/******************************************************************************* + * POLLSET STUFF + */ + +static void add_to_pollset_locked(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport *t, + grpc_pollset *pollset) { + if (t->ep) { + grpc_endpoint_add_to_pollset(exec_ctx, t->ep, pollset); + } +} + +static void add_to_pollset_set_locked(grpc_exec_ctx *exec_ctx, + grpc_chttp2_transport *t, + grpc_pollset_set *pollset_set) { + if (t->ep) { + grpc_endpoint_add_to_pollset_set(exec_ctx, t->ep, pollset_set); + } +} + +static void set_pollset(grpc_exec_ctx *exec_ctx, grpc_transport *gt, + grpc_stream *gs, grpc_pollset *pollset) { + grpc_chttp2_transport *t = (grpc_chttp2_transport *)gt; + lock(t); + add_to_pollset_locked(exec_ctx, t, pollset); + unlock(exec_ctx, t); +} + +/******************************************************************************* + * BYTE STREAM + */ + +static void incoming_byte_stream_update_flow_control( + grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global, size_t max_size_hint, + size_t have_already) { + uint32_t max_recv_bytes; + + /* clamp max recv hint to an allowable size */ + if (max_size_hint >= UINT32_MAX - transport_global->stream_lookahead) { + max_recv_bytes = UINT32_MAX - transport_global->stream_lookahead; + } else { + max_recv_bytes = (uint32_t)max_size_hint; + } + + /* account for bytes already received but unknown to higher layers */ + if (max_recv_bytes >= have_already) { + max_recv_bytes -= (uint32_t)have_already; + } else { + max_recv_bytes = 0; + } + + /* add some small lookahead to keep pipelines flowing */ + GPR_ASSERT(max_recv_bytes <= UINT32_MAX - transport_global->stream_lookahead); + max_recv_bytes += transport_global->stream_lookahead; + if (stream_global->max_recv_bytes < max_recv_bytes) { + uint32_t add_max_recv_bytes = + max_recv_bytes - stream_global->max_recv_bytes; + GRPC_CHTTP2_FLOW_CREDIT_STREAM("op", transport_global, stream_global, + max_recv_bytes, add_max_recv_bytes); + GRPC_CHTTP2_FLOW_CREDIT_STREAM("op", transport_global, stream_global, + unannounced_incoming_window_for_parse, + add_max_recv_bytes); + GRPC_CHTTP2_FLOW_CREDIT_STREAM("op", transport_global, stream_global, + unannounced_incoming_window_for_writing, + add_max_recv_bytes); + grpc_chttp2_list_add_unannounced_incoming_window_available(transport_global, + stream_global); + grpc_chttp2_become_writable(transport_global, stream_global); + } +} + +static int incoming_byte_stream_next(grpc_exec_ctx *exec_ctx, + grpc_byte_stream *byte_stream, + gpr_slice *slice, size_t max_size_hint, + grpc_closure *on_complete) { + grpc_chttp2_incoming_byte_stream *bs = + (grpc_chttp2_incoming_byte_stream *)byte_stream; + grpc_chttp2_transport_global *transport_global = &bs->transport->global; + grpc_chttp2_stream_global *stream_global = &bs->stream->global; + + lock(bs->transport); + if (bs->is_tail) { + incoming_byte_stream_update_flow_control(transport_global, stream_global, + max_size_hint, bs->slices.length); + } + if (bs->slices.count > 0) { + *slice = gpr_slice_buffer_take_first(&bs->slices); + unlock(exec_ctx, bs->transport); + return 1; + } else if (bs->failed) { + grpc_exec_ctx_enqueue(exec_ctx, on_complete, false, NULL); + unlock(exec_ctx, bs->transport); + return 0; + } else { + bs->on_next = on_complete; + bs->next = slice; + unlock(exec_ctx, bs->transport); + return 0; + } +} + +static void incoming_byte_stream_unref(grpc_chttp2_incoming_byte_stream *bs) { + if (gpr_unref(&bs->refs)) { + gpr_slice_buffer_destroy(&bs->slices); + gpr_free(bs); + } +} + +static void incoming_byte_stream_destroy(grpc_exec_ctx *exec_ctx, + grpc_byte_stream *byte_stream) { + incoming_byte_stream_unref((grpc_chttp2_incoming_byte_stream *)byte_stream); +} + +void grpc_chttp2_incoming_byte_stream_push(grpc_exec_ctx *exec_ctx, + grpc_chttp2_incoming_byte_stream *bs, + gpr_slice slice) { + gpr_mu_lock(&bs->transport->mu); + if (bs->on_next != NULL) { + *bs->next = slice; + grpc_exec_ctx_enqueue(exec_ctx, bs->on_next, true, NULL); + bs->on_next = NULL; + } else { + gpr_slice_buffer_add(&bs->slices, slice); + } + gpr_mu_unlock(&bs->transport->mu); +} + +void grpc_chttp2_incoming_byte_stream_finished( + grpc_exec_ctx *exec_ctx, grpc_chttp2_incoming_byte_stream *bs, int success, + int from_parsing_thread) { + if (!success) { + if (from_parsing_thread) { + gpr_mu_lock(&bs->transport->mu); + } + grpc_exec_ctx_enqueue(exec_ctx, bs->on_next, false, NULL); + bs->on_next = NULL; + bs->failed = 1; + if (from_parsing_thread) { + gpr_mu_unlock(&bs->transport->mu); + } + } else { +#ifndef NDEBUG + if (from_parsing_thread) { + gpr_mu_lock(&bs->transport->mu); + } + GPR_ASSERT(bs->on_next == NULL); + if (from_parsing_thread) { + gpr_mu_unlock(&bs->transport->mu); + } +#endif + } + incoming_byte_stream_unref(bs); +} + +grpc_chttp2_incoming_byte_stream *grpc_chttp2_incoming_byte_stream_create( + grpc_exec_ctx *exec_ctx, grpc_chttp2_transport_parsing *transport_parsing, + grpc_chttp2_stream_parsing *stream_parsing, uint32_t frame_size, + uint32_t flags, grpc_chttp2_incoming_frame_queue *add_to_queue) { + grpc_chttp2_incoming_byte_stream *incoming_byte_stream = + gpr_malloc(sizeof(*incoming_byte_stream)); + incoming_byte_stream->base.length = frame_size; + incoming_byte_stream->base.flags = flags; + incoming_byte_stream->base.next = incoming_byte_stream_next; + incoming_byte_stream->base.destroy = incoming_byte_stream_destroy; + gpr_ref_init(&incoming_byte_stream->refs, 2); + incoming_byte_stream->next_message = NULL; + incoming_byte_stream->transport = TRANSPORT_FROM_PARSING(transport_parsing); + incoming_byte_stream->stream = STREAM_FROM_PARSING(stream_parsing); + gpr_slice_buffer_init(&incoming_byte_stream->slices); + incoming_byte_stream->on_next = NULL; + incoming_byte_stream->is_tail = 1; + incoming_byte_stream->failed = 0; + if (add_to_queue->head == NULL) { + add_to_queue->head = incoming_byte_stream; + } else { + add_to_queue->tail->is_tail = 0; + add_to_queue->tail->next_message = incoming_byte_stream; + } + add_to_queue->tail = incoming_byte_stream; + return incoming_byte_stream; +} + +/******************************************************************************* + * TRACING + */ + +static char *format_flowctl_context_var(const char *context, const char *var, + int64_t val, uint32_t id, + char **scope) { + char *underscore_pos; + char *result; + if (context == NULL) { + *scope = NULL; + gpr_asprintf(&result, "%s(%lld)", var, val); + return result; + } + underscore_pos = strchr(context, '_'); + *scope = gpr_strdup(context); + (*scope)[underscore_pos - context] = 0; + if (id != 0) { + char *tmp = *scope; + gpr_asprintf(scope, "%s[%d]", tmp, id); + gpr_free(tmp); + } + gpr_asprintf(&result, "%s.%s(%lld)", underscore_pos + 1, var, val); + return result; +} + +static int samestr(char *a, char *b) { + if (a == NULL) { + return b == NULL; + } + if (b == NULL) { + return 0; + } + return 0 == strcmp(a, b); +} + +void grpc_chttp2_flowctl_trace(const char *file, int line, const char *phase, + grpc_chttp2_flowctl_op op, const char *context1, + const char *var1, const char *context2, + const char *var2, int is_client, + uint32_t stream_id, int64_t val1, int64_t val2) { + char *scope1; + char *scope2; + char *label1 = + format_flowctl_context_var(context1, var1, val1, stream_id, &scope1); + char *label2 = + format_flowctl_context_var(context2, var2, val2, stream_id, &scope2); + char *clisvr = is_client ? "client" : "server"; + char *prefix; + + gpr_asprintf(&prefix, "FLOW % 8s: %s % 11s ", phase, clisvr, scope1); + + switch (op) { + case GRPC_CHTTP2_FLOWCTL_MOVE: + GPR_ASSERT(samestr(scope1, scope2)); + if (val2 != 0) { + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, + "%sMOVE % 40s <- % 40s giving %d", prefix, label1, label2, + val1 + val2); + } + break; + case GRPC_CHTTP2_FLOWCTL_CREDIT: + GPR_ASSERT(val2 >= 0); + if (val2 != 0) { + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, + "%sCREDIT % 40s by % 40s giving %d", prefix, label1, label2, + val1 + val2); + } + break; + case GRPC_CHTTP2_FLOWCTL_DEBIT: + GPR_ASSERT(val2 >= 0); + if (val2 != 0) { + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, + "%sDEBIT % 40s by % 40s giving %d", prefix, label1, label2, + val1 - val2); + } + break; + } + + gpr_free(scope1); + gpr_free(scope2); + gpr_free(label1); + gpr_free(label2); + gpr_free(prefix); +} + +/******************************************************************************* + * INTEGRATION GLUE + */ + +static char *chttp2_get_peer(grpc_exec_ctx *exec_ctx, grpc_transport *t) { + return gpr_strdup(((grpc_chttp2_transport *)t)->peer_string); +} + +static const grpc_transport_vtable vtable = {sizeof(grpc_chttp2_stream), + "chttp2", + init_stream, + set_pollset, + perform_stream_op, + perform_transport_op, + destroy_stream, + destroy_transport, + chttp2_get_peer}; + +grpc_transport *grpc_create_chttp2_transport( + grpc_exec_ctx *exec_ctx, const grpc_channel_args *channel_args, + grpc_endpoint *ep, int is_client) { + grpc_chttp2_transport *t = gpr_malloc(sizeof(grpc_chttp2_transport)); + init_transport(exec_ctx, t, channel_args, ep, is_client != 0); + return &t->base; +} + +void grpc_chttp2_transport_start_reading(grpc_exec_ctx *exec_ctx, + grpc_transport *transport, + gpr_slice *slices, size_t nslices) { + grpc_chttp2_transport *t = (grpc_chttp2_transport *)transport; + REF_TRANSPORT(t, "recv_data"); /* matches unref inside recv_data */ + gpr_slice_buffer_addn(&t->read_buffer, slices, nslices); + recv_data(exec_ctx, t, 1); +} diff --git a/src/core/lib/transport/chttp2_transport.h b/src/core/lib/transport/chttp2_transport.h new file mode 100644 index 0000000000..5008cab7f8 --- /dev/null +++ b/src/core/lib/transport/chttp2_transport.h @@ -0,0 +1,51 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CHTTP2_TRANSPORT_H +#define GRPC_CORE_LIB_TRANSPORT_CHTTP2_TRANSPORT_H + +#include "src/core/lib/iomgr/endpoint.h" +#include "src/core/lib/transport/transport.h" + +extern int grpc_http_trace; +extern int grpc_flowctl_trace; + +grpc_transport *grpc_create_chttp2_transport( + grpc_exec_ctx *exec_ctx, const grpc_channel_args *channel_args, + grpc_endpoint *ep, int is_client); + +void grpc_chttp2_transport_start_reading(grpc_exec_ctx *exec_ctx, + grpc_transport *transport, + gpr_slice *slices, size_t nslices); + +#endif /* GRPC_CORE_LIB_TRANSPORT_CHTTP2_TRANSPORT_H */ diff --git a/src/core/lib/transport/connectivity_state.c b/src/core/lib/transport/connectivity_state.c new file mode 100644 index 0000000000..123eab8b36 --- /dev/null +++ b/src/core/lib/transport/connectivity_state.c @@ -0,0 +1,164 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/connectivity_state.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> + +int grpc_connectivity_state_trace = 0; + +const char *grpc_connectivity_state_name(grpc_connectivity_state state) { + switch (state) { + case GRPC_CHANNEL_IDLE: + return "IDLE"; + case GRPC_CHANNEL_CONNECTING: + return "CONNECTING"; + case GRPC_CHANNEL_READY: + return "READY"; + case GRPC_CHANNEL_TRANSIENT_FAILURE: + return "TRANSIENT_FAILURE"; + case GRPC_CHANNEL_FATAL_FAILURE: + return "FATAL_FAILURE"; + } + GPR_UNREACHABLE_CODE(return "UNKNOWN"); +} + +void grpc_connectivity_state_init(grpc_connectivity_state_tracker *tracker, + grpc_connectivity_state init_state, + const char *name) { + tracker->current_state = init_state; + tracker->watchers = NULL; + tracker->name = gpr_strdup(name); +} + +void grpc_connectivity_state_destroy(grpc_exec_ctx *exec_ctx, + grpc_connectivity_state_tracker *tracker) { + int success; + grpc_connectivity_state_watcher *w; + while ((w = tracker->watchers)) { + tracker->watchers = w->next; + + if (GRPC_CHANNEL_FATAL_FAILURE != *w->current) { + *w->current = GRPC_CHANNEL_FATAL_FAILURE; + success = 1; + } else { + success = 0; + } + grpc_exec_ctx_enqueue(exec_ctx, w->notify, success, NULL); + gpr_free(w); + } + gpr_free(tracker->name); +} + +grpc_connectivity_state grpc_connectivity_state_check( + grpc_connectivity_state_tracker *tracker) { + if (grpc_connectivity_state_trace) { + gpr_log(GPR_DEBUG, "CONWATCH: %p %s: get %s", tracker, tracker->name, + grpc_connectivity_state_name(tracker->current_state)); + } + return tracker->current_state; +} + +int grpc_connectivity_state_notify_on_state_change( + grpc_exec_ctx *exec_ctx, grpc_connectivity_state_tracker *tracker, + grpc_connectivity_state *current, grpc_closure *notify) { + if (grpc_connectivity_state_trace) { + if (current == NULL) { + gpr_log(GPR_DEBUG, "CONWATCH: %p %s: unsubscribe notify=%p", tracker, + tracker->name, notify); + } else { + gpr_log(GPR_DEBUG, "CONWATCH: %p %s: from %s [cur=%s] notify=%p", tracker, + tracker->name, grpc_connectivity_state_name(*current), + grpc_connectivity_state_name(tracker->current_state), notify); + } + } + if (current == NULL) { + grpc_connectivity_state_watcher *w = tracker->watchers; + if (w != NULL && w->notify == notify) { + grpc_exec_ctx_enqueue(exec_ctx, notify, false, NULL); + tracker->watchers = w->next; + gpr_free(w); + return 0; + } + while (w != NULL) { + grpc_connectivity_state_watcher *rm_candidate = w->next; + if (rm_candidate != NULL && rm_candidate->notify == notify) { + grpc_exec_ctx_enqueue(exec_ctx, notify, false, NULL); + w->next = w->next->next; + gpr_free(rm_candidate); + return 0; + } + w = w->next; + } + return 0; + } else { + if (tracker->current_state != *current) { + *current = tracker->current_state; + grpc_exec_ctx_enqueue(exec_ctx, notify, true, NULL); + } else { + grpc_connectivity_state_watcher *w = gpr_malloc(sizeof(*w)); + w->current = current; + w->notify = notify; + w->next = tracker->watchers; + tracker->watchers = w; + } + return tracker->current_state == GRPC_CHANNEL_IDLE; + } +} + +void grpc_connectivity_state_set(grpc_exec_ctx *exec_ctx, + grpc_connectivity_state_tracker *tracker, + grpc_connectivity_state state, + const char *reason) { + grpc_connectivity_state_watcher *w; + if (grpc_connectivity_state_trace) { + gpr_log(GPR_DEBUG, "SET: %p %s: %s --> %s [%s]", tracker, tracker->name, + grpc_connectivity_state_name(tracker->current_state), + grpc_connectivity_state_name(state), reason); + } + if (tracker->current_state == state) { + return; + } + GPR_ASSERT(tracker->current_state != GRPC_CHANNEL_FATAL_FAILURE); + tracker->current_state = state; + while ((w = tracker->watchers) != NULL) { + *w->current = tracker->current_state; + tracker->watchers = w->next; + grpc_exec_ctx_enqueue(exec_ctx, w->notify, true, NULL); + gpr_free(w); + } +} diff --git a/src/core/lib/transport/connectivity_state.h b/src/core/lib/transport/connectivity_state.h new file mode 100644 index 0000000000..6f92132438 --- /dev/null +++ b/src/core/lib/transport/connectivity_state.h @@ -0,0 +1,85 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_CONNECTIVITY_STATE_H +#define GRPC_CORE_LIB_TRANSPORT_CONNECTIVITY_STATE_H + +#include <grpc/grpc.h> +#include "src/core/lib/iomgr/exec_ctx.h" + +typedef struct grpc_connectivity_state_watcher { + /** we keep watchers in a linked list */ + struct grpc_connectivity_state_watcher *next; + /** closure to notify on change */ + grpc_closure *notify; + /** the current state as believed by the watcher */ + grpc_connectivity_state *current; +} grpc_connectivity_state_watcher; + +typedef struct { + /** current connectivity state */ + grpc_connectivity_state current_state; + /** all our watchers */ + grpc_connectivity_state_watcher *watchers; + /** a name to help debugging */ + char *name; +} grpc_connectivity_state_tracker; + +extern int grpc_connectivity_state_trace; + +const char *grpc_connectivity_state_name(grpc_connectivity_state state); + +void grpc_connectivity_state_init(grpc_connectivity_state_tracker *tracker, + grpc_connectivity_state init_state, + const char *name); +void grpc_connectivity_state_destroy(grpc_exec_ctx *exec_ctx, + grpc_connectivity_state_tracker *tracker); + +/** Set connectivity state; not thread safe; access must be serialized with an + * external lock */ +void grpc_connectivity_state_set(grpc_exec_ctx *exec_ctx, + grpc_connectivity_state_tracker *tracker, + grpc_connectivity_state state, + const char *reason); + +grpc_connectivity_state grpc_connectivity_state_check( + grpc_connectivity_state_tracker *tracker); + +/** Return 1 if the channel should start connecting, 0 otherwise. + If current==NULL cancel notify if it is already queued (success==0 in that + case) */ +int grpc_connectivity_state_notify_on_state_change( + grpc_exec_ctx *exec_ctx, grpc_connectivity_state_tracker *tracker, + grpc_connectivity_state *current, grpc_closure *notify); + +#endif /* GRPC_CORE_LIB_TRANSPORT_CONNECTIVITY_STATE_H */ diff --git a/src/core/lib/transport/metadata.c b/src/core/lib/transport/metadata.c new file mode 100644 index 0000000000..7605f09991 --- /dev/null +++ b/src/core/lib/transport/metadata.c @@ -0,0 +1,698 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/metadata.h" + +#include <assert.h> +#include <stddef.h> +#include <string.h> + +#include <grpc/compression.h> +#include <grpc/support/alloc.h> +#include <grpc/support/atm.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> +#include <grpc/support/time.h> + +#include "src/core/lib/iomgr/iomgr_internal.h" +#include "src/core/lib/profiling/timers.h" +#include "src/core/lib/support/murmur_hash.h" +#include "src/core/lib/support/string.h" +#include "src/core/lib/transport/chttp2/bin_encoder.h" +#include "src/core/lib/transport/static_metadata.h" + +/* There are two kinds of mdelem and mdstr instances. + * Static instances are declared in static_metadata.{h,c} and + * are initialized by grpc_mdctx_global_init(). + * Dynamic instances are stored in hash tables on grpc_mdctx, and are backed + * by internal_string and internal_element structures. + * Internal helper functions here-in (is_mdstr_static, is_mdelem_static) are + * used to determine which kind of element a pointer refers to. + */ + +#define INITIAL_STRTAB_CAPACITY 4 +#define INITIAL_MDTAB_CAPACITY 4 + +#ifdef GRPC_METADATA_REFCOUNT_DEBUG +#define DEBUG_ARGS , const char *file, int line +#define FWD_DEBUG_ARGS , file, line +#define REF_MD_LOCKED(shard, s) ref_md_locked((shard), (s), __FILE__, __LINE__) +#else +#define DEBUG_ARGS +#define FWD_DEBUG_ARGS +#define REF_MD_LOCKED(shard, s) ref_md_locked((shard), (s)) +#endif + +#define TABLE_IDX(hash, log2_shards, capacity) \ + (((hash) >> (log2_shards)) % (capacity)) +#define SHARD_IDX(hash, log2_shards) ((hash) & ((1 << (log2_shards)) - 1)) + +typedef void (*destroy_user_data_func)(void *user_data); + +/* Shadow structure for grpc_mdstr for non-static values */ +typedef struct internal_string { + /* must be byte compatible with grpc_mdstr */ + gpr_slice slice; + uint32_t hash; + + /* private only data */ + gpr_atm refcnt; + + uint8_t has_base64_and_huffman_encoded; + gpr_slice_refcount refcount; + + gpr_slice base64_and_huffman; + + struct internal_string *bucket_next; +} internal_string; + +/* Shadow structure for grpc_mdelem for non-static elements */ +typedef struct internal_metadata { + /* must be byte compatible with grpc_mdelem */ + internal_string *key; + internal_string *value; + + /* private only data */ + gpr_atm refcnt; + + gpr_mu mu_user_data; + gpr_atm destroy_user_data; + gpr_atm user_data; + + struct internal_metadata *bucket_next; +} internal_metadata; + +typedef struct strtab_shard { + gpr_mu mu; + internal_string **strs; + size_t count; + size_t capacity; +} strtab_shard; + +typedef struct mdtab_shard { + gpr_mu mu; + internal_metadata **elems; + size_t count; + size_t capacity; + size_t free; +} mdtab_shard; + +#define LOG2_STRTAB_SHARD_COUNT 5 +#define LOG2_MDTAB_SHARD_COUNT 4 +#define STRTAB_SHARD_COUNT ((size_t)(1 << LOG2_STRTAB_SHARD_COUNT)) +#define MDTAB_SHARD_COUNT ((size_t)(1 << LOG2_MDTAB_SHARD_COUNT)) + +/* hash seed: decided at initialization time */ +static uint32_t g_hash_seed; +static int g_forced_hash_seed = 0; + +/* linearly probed hash tables for static element lookup */ +static grpc_mdstr *g_static_strtab[GRPC_STATIC_MDSTR_COUNT * 2]; +static grpc_mdelem *g_static_mdtab[GRPC_STATIC_MDELEM_COUNT * 2]; +static size_t g_static_strtab_maxprobe; +static size_t g_static_mdtab_maxprobe; + +static strtab_shard g_strtab_shard[STRTAB_SHARD_COUNT]; +static mdtab_shard g_mdtab_shard[MDTAB_SHARD_COUNT]; + +static void gc_mdtab(mdtab_shard *shard); + +void grpc_test_only_set_metadata_hash_seed(uint32_t seed) { + g_hash_seed = seed; + g_forced_hash_seed = 1; +} + +void grpc_mdctx_global_init(void) { + size_t i, j; + if (!g_forced_hash_seed) { + g_hash_seed = (uint32_t)gpr_now(GPR_CLOCK_REALTIME).tv_nsec; + } + g_static_strtab_maxprobe = 0; + g_static_mdtab_maxprobe = 0; + /* build static tables */ + memset(g_static_mdtab, 0, sizeof(g_static_mdtab)); + memset(g_static_strtab, 0, sizeof(g_static_strtab)); + for (i = 0; i < GRPC_STATIC_MDSTR_COUNT; i++) { + grpc_mdstr *elem = &grpc_static_mdstr_table[i]; + const char *str = grpc_static_metadata_strings[i]; + uint32_t hash = gpr_murmur_hash3(str, strlen(str), g_hash_seed); + *(gpr_slice *)&elem->slice = gpr_slice_from_static_string(str); + *(uint32_t *)&elem->hash = hash; + for (j = 0;; j++) { + size_t idx = (hash + j) % GPR_ARRAY_SIZE(g_static_strtab); + if (g_static_strtab[idx] == NULL) { + g_static_strtab[idx] = &grpc_static_mdstr_table[i]; + break; + } + } + if (j > g_static_strtab_maxprobe) { + g_static_strtab_maxprobe = j; + } + } + for (i = 0; i < GRPC_STATIC_MDELEM_COUNT; i++) { + grpc_mdelem *elem = &grpc_static_mdelem_table[i]; + grpc_mdstr *key = + &grpc_static_mdstr_table[grpc_static_metadata_elem_indices[2 * i + 0]]; + grpc_mdstr *value = + &grpc_static_mdstr_table[grpc_static_metadata_elem_indices[2 * i + 1]]; + uint32_t hash = GRPC_MDSTR_KV_HASH(key->hash, value->hash); + *(grpc_mdstr **)&elem->key = key; + *(grpc_mdstr **)&elem->value = value; + for (j = 0;; j++) { + size_t idx = (hash + j) % GPR_ARRAY_SIZE(g_static_mdtab); + if (g_static_mdtab[idx] == NULL) { + g_static_mdtab[idx] = elem; + break; + } + } + if (j > g_static_mdtab_maxprobe) { + g_static_mdtab_maxprobe = j; + } + } + /* initialize shards */ + for (i = 0; i < STRTAB_SHARD_COUNT; i++) { + strtab_shard *shard = &g_strtab_shard[i]; + gpr_mu_init(&shard->mu); + shard->count = 0; + shard->capacity = INITIAL_STRTAB_CAPACITY; + shard->strs = gpr_malloc(sizeof(*shard->strs) * shard->capacity); + memset(shard->strs, 0, sizeof(*shard->strs) * shard->capacity); + } + for (i = 0; i < MDTAB_SHARD_COUNT; i++) { + mdtab_shard *shard = &g_mdtab_shard[i]; + gpr_mu_init(&shard->mu); + shard->count = 0; + shard->free = 0; + shard->capacity = INITIAL_MDTAB_CAPACITY; + shard->elems = gpr_malloc(sizeof(*shard->elems) * shard->capacity); + memset(shard->elems, 0, sizeof(*shard->elems) * shard->capacity); + } +} + +void grpc_mdctx_global_shutdown(void) { + size_t i; + for (i = 0; i < MDTAB_SHARD_COUNT; i++) { + mdtab_shard *shard = &g_mdtab_shard[i]; + gpr_mu_destroy(&shard->mu); + gc_mdtab(shard); + /* TODO(ctiller): GPR_ASSERT(shard->count == 0); */ + if (shard->count != 0) { + gpr_log(GPR_DEBUG, "WARNING: %d metadata elements were leaked", + shard->count); + if (grpc_iomgr_abort_on_leaks()) { + abort(); + } + } + gpr_free(shard->elems); + } + for (i = 0; i < STRTAB_SHARD_COUNT; i++) { + strtab_shard *shard = &g_strtab_shard[i]; + gpr_mu_destroy(&shard->mu); + /* TODO(ctiller): GPR_ASSERT(shard->count == 0); */ + if (shard->count != 0) { + gpr_log(GPR_DEBUG, "WARNING: %d metadata strings were leaked", + shard->count); + if (grpc_iomgr_abort_on_leaks()) { + abort(); + } + } + gpr_free(shard->strs); + } +} + +static int is_mdstr_static(grpc_mdstr *s) { + return s >= &grpc_static_mdstr_table[0] && + s < &grpc_static_mdstr_table[GRPC_STATIC_MDSTR_COUNT]; +} + +static int is_mdelem_static(grpc_mdelem *e) { + return e >= &grpc_static_mdelem_table[0] && + e < &grpc_static_mdelem_table[GRPC_STATIC_MDELEM_COUNT]; +} + +static void ref_md_locked(mdtab_shard *shard, + internal_metadata *md DEBUG_ARGS) { +#ifdef GRPC_METADATA_REFCOUNT_DEBUG + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, + "ELM REF:%p:%d->%d: '%s' = '%s'", md, + gpr_atm_no_barrier_load(&md->refcnt), + gpr_atm_no_barrier_load(&md->refcnt) + 1, + grpc_mdstr_as_c_string((grpc_mdstr *)md->key), + grpc_mdstr_as_c_string((grpc_mdstr *)md->value)); +#endif + if (0 == gpr_atm_no_barrier_fetch_add(&md->refcnt, 2)) { + shard->free--; + } else { + GPR_ASSERT(1 != gpr_atm_no_barrier_fetch_add(&md->refcnt, -1)); + } +} + +static void grow_strtab(strtab_shard *shard) { + size_t capacity = shard->capacity * 2; + size_t i; + internal_string **strtab; + internal_string *s, *next; + + GPR_TIMER_BEGIN("grow_strtab", 0); + + strtab = gpr_malloc(sizeof(internal_string *) * capacity); + memset(strtab, 0, sizeof(internal_string *) * capacity); + + for (i = 0; i < shard->capacity; i++) { + for (s = shard->strs[i]; s; s = next) { + size_t idx = TABLE_IDX(s->hash, LOG2_STRTAB_SHARD_COUNT, capacity); + next = s->bucket_next; + s->bucket_next = strtab[idx]; + strtab[idx] = s; + } + } + + gpr_free(shard->strs); + shard->strs = strtab; + shard->capacity = capacity; + + GPR_TIMER_END("grow_strtab", 0); +} + +static void internal_destroy_string(strtab_shard *shard, internal_string *is) { + internal_string **prev_next; + internal_string *cur; + GPR_TIMER_BEGIN("internal_destroy_string", 0); + if (is->has_base64_and_huffman_encoded) { + gpr_slice_unref(is->base64_and_huffman); + } + for (prev_next = &shard->strs[TABLE_IDX(is->hash, LOG2_STRTAB_SHARD_COUNT, + shard->capacity)], + cur = *prev_next; + cur != is; prev_next = &cur->bucket_next, cur = cur->bucket_next) + ; + *prev_next = cur->bucket_next; + shard->count--; + gpr_free(is); + GPR_TIMER_END("internal_destroy_string", 0); +} + +static void slice_ref(void *p) { + internal_string *is = + (internal_string *)((char *)p - offsetof(internal_string, refcount)); + GRPC_MDSTR_REF((grpc_mdstr *)(is)); +} + +static void slice_unref(void *p) { + internal_string *is = + (internal_string *)((char *)p - offsetof(internal_string, refcount)); + GRPC_MDSTR_UNREF((grpc_mdstr *)(is)); +} + +grpc_mdstr *grpc_mdstr_from_string(const char *str) { + return grpc_mdstr_from_buffer((const uint8_t *)str, strlen(str)); +} + +grpc_mdstr *grpc_mdstr_from_slice(gpr_slice slice) { + grpc_mdstr *result = grpc_mdstr_from_buffer(GPR_SLICE_START_PTR(slice), + GPR_SLICE_LENGTH(slice)); + gpr_slice_unref(slice); + return result; +} + +grpc_mdstr *grpc_mdstr_from_buffer(const uint8_t *buf, size_t length) { + uint32_t hash = gpr_murmur_hash3(buf, length, g_hash_seed); + internal_string *s; + strtab_shard *shard = + &g_strtab_shard[SHARD_IDX(hash, LOG2_STRTAB_SHARD_COUNT)]; + size_t i; + size_t idx; + + GPR_TIMER_BEGIN("grpc_mdstr_from_buffer", 0); + + /* search for a static string */ + for (i = 0; i <= g_static_strtab_maxprobe; i++) { + grpc_mdstr *ss; + idx = (hash + i) % GPR_ARRAY_SIZE(g_static_strtab); + ss = g_static_strtab[idx]; + if (ss == NULL) break; + if (ss->hash == hash && GPR_SLICE_LENGTH(ss->slice) == length && + 0 == memcmp(buf, GPR_SLICE_START_PTR(ss->slice), length)) { + GPR_TIMER_END("grpc_mdstr_from_buffer", 0); + return ss; + } + } + + gpr_mu_lock(&shard->mu); + + /* search for an existing string */ + idx = TABLE_IDX(hash, LOG2_STRTAB_SHARD_COUNT, shard->capacity); + for (s = shard->strs[idx]; s; s = s->bucket_next) { + if (s->hash == hash && GPR_SLICE_LENGTH(s->slice) == length && + 0 == memcmp(buf, GPR_SLICE_START_PTR(s->slice), length)) { + GRPC_MDSTR_REF((grpc_mdstr *)s); + gpr_mu_unlock(&shard->mu); + GPR_TIMER_END("grpc_mdstr_from_buffer", 0); + return (grpc_mdstr *)s; + } + } + + /* not found: create a new string */ + if (length + 1 < GPR_SLICE_INLINED_SIZE) { + /* string data goes directly into the slice */ + s = gpr_malloc(sizeof(internal_string)); + gpr_atm_rel_store(&s->refcnt, 2); + s->slice.refcount = NULL; + memcpy(s->slice.data.inlined.bytes, buf, length); + s->slice.data.inlined.bytes[length] = 0; + s->slice.data.inlined.length = (uint8_t)length; + } else { + /* string data goes after the internal_string header, and we +1 for null + terminator */ + s = gpr_malloc(sizeof(internal_string) + length + 1); + gpr_atm_rel_store(&s->refcnt, 2); + s->refcount.ref = slice_ref; + s->refcount.unref = slice_unref; + s->slice.refcount = &s->refcount; + s->slice.data.refcounted.bytes = (uint8_t *)(s + 1); + s->slice.data.refcounted.length = length; + memcpy(s->slice.data.refcounted.bytes, buf, length); + /* add a null terminator for cheap c string conversion when desired */ + s->slice.data.refcounted.bytes[length] = 0; + } + s->has_base64_and_huffman_encoded = 0; + s->hash = hash; + s->bucket_next = shard->strs[idx]; + shard->strs[idx] = s; + + shard->count++; + + if (shard->count > shard->capacity * 2) { + grow_strtab(shard); + } + + gpr_mu_unlock(&shard->mu); + GPR_TIMER_END("grpc_mdstr_from_buffer", 0); + + return (grpc_mdstr *)s; +} + +static void gc_mdtab(mdtab_shard *shard) { + size_t i; + internal_metadata **prev_next; + internal_metadata *md, *next; + + GPR_TIMER_BEGIN("gc_mdtab", 0); + for (i = 0; i < shard->capacity; i++) { + prev_next = &shard->elems[i]; + for (md = shard->elems[i]; md; md = next) { + void *user_data = (void *)gpr_atm_no_barrier_load(&md->user_data); + next = md->bucket_next; + if (gpr_atm_acq_load(&md->refcnt) == 0) { + GRPC_MDSTR_UNREF((grpc_mdstr *)md->key); + GRPC_MDSTR_UNREF((grpc_mdstr *)md->value); + if (md->user_data) { + ((destroy_user_data_func)gpr_atm_no_barrier_load( + &md->destroy_user_data))(user_data); + } + gpr_free(md); + *prev_next = next; + shard->free--; + shard->count--; + } else { + prev_next = &md->bucket_next; + } + } + } + GPR_TIMER_END("gc_mdtab", 0); +} + +static void grow_mdtab(mdtab_shard *shard) { + size_t capacity = shard->capacity * 2; + size_t i; + internal_metadata **mdtab; + internal_metadata *md, *next; + uint32_t hash; + + GPR_TIMER_BEGIN("grow_mdtab", 0); + + mdtab = gpr_malloc(sizeof(internal_metadata *) * capacity); + memset(mdtab, 0, sizeof(internal_metadata *) * capacity); + + for (i = 0; i < shard->capacity; i++) { + for (md = shard->elems[i]; md; md = next) { + size_t idx; + hash = GRPC_MDSTR_KV_HASH(md->key->hash, md->value->hash); + next = md->bucket_next; + idx = TABLE_IDX(hash, LOG2_MDTAB_SHARD_COUNT, capacity); + md->bucket_next = mdtab[idx]; + mdtab[idx] = md; + } + } + + gpr_free(shard->elems); + shard->elems = mdtab; + shard->capacity = capacity; + + GPR_TIMER_END("grow_mdtab", 0); +} + +static void rehash_mdtab(mdtab_shard *shard) { + if (shard->free > shard->capacity / 4) { + gc_mdtab(shard); + } else { + grow_mdtab(shard); + } +} + +grpc_mdelem *grpc_mdelem_from_metadata_strings(grpc_mdstr *mkey, + grpc_mdstr *mvalue) { + internal_string *key = (internal_string *)mkey; + internal_string *value = (internal_string *)mvalue; + uint32_t hash = GRPC_MDSTR_KV_HASH(mkey->hash, mvalue->hash); + internal_metadata *md; + mdtab_shard *shard = &g_mdtab_shard[SHARD_IDX(hash, LOG2_MDTAB_SHARD_COUNT)]; + size_t i; + size_t idx; + + GPR_TIMER_BEGIN("grpc_mdelem_from_metadata_strings", 0); + + if (is_mdstr_static(mkey) && is_mdstr_static(mvalue)) { + for (i = 0; i <= g_static_mdtab_maxprobe; i++) { + grpc_mdelem *smd; + idx = (hash + i) % GPR_ARRAY_SIZE(g_static_mdtab); + smd = g_static_mdtab[idx]; + if (smd == NULL) break; + if (smd->key == mkey && smd->value == mvalue) { + GPR_TIMER_END("grpc_mdelem_from_metadata_strings", 0); + return smd; + } + } + } + + gpr_mu_lock(&shard->mu); + + idx = TABLE_IDX(hash, LOG2_MDTAB_SHARD_COUNT, shard->capacity); + /* search for an existing pair */ + for (md = shard->elems[idx]; md; md = md->bucket_next) { + if (md->key == key && md->value == value) { + REF_MD_LOCKED(shard, md); + GRPC_MDSTR_UNREF((grpc_mdstr *)key); + GRPC_MDSTR_UNREF((grpc_mdstr *)value); + gpr_mu_unlock(&shard->mu); + GPR_TIMER_END("grpc_mdelem_from_metadata_strings", 0); + return (grpc_mdelem *)md; + } + } + + /* not found: create a new pair */ + md = gpr_malloc(sizeof(internal_metadata)); + gpr_atm_rel_store(&md->refcnt, 2); + md->key = key; + md->value = value; + md->user_data = 0; + md->destroy_user_data = 0; + md->bucket_next = shard->elems[idx]; + shard->elems[idx] = md; + gpr_mu_init(&md->mu_user_data); +#ifdef GRPC_METADATA_REFCOUNT_DEBUG + gpr_log(GPR_DEBUG, "ELM NEW:%p:%d: '%s' = '%s'", md, + gpr_atm_no_barrier_load(&md->refcnt), + grpc_mdstr_as_c_string((grpc_mdstr *)md->key), + grpc_mdstr_as_c_string((grpc_mdstr *)md->value)); +#endif + shard->count++; + + if (shard->count > shard->capacity * 2) { + rehash_mdtab(shard); + } + + gpr_mu_unlock(&shard->mu); + + GPR_TIMER_END("grpc_mdelem_from_metadata_strings", 0); + + return (grpc_mdelem *)md; +} + +grpc_mdelem *grpc_mdelem_from_strings(const char *key, const char *value) { + return grpc_mdelem_from_metadata_strings(grpc_mdstr_from_string(key), + grpc_mdstr_from_string(value)); +} + +grpc_mdelem *grpc_mdelem_from_slices(gpr_slice key, gpr_slice value) { + return grpc_mdelem_from_metadata_strings(grpc_mdstr_from_slice(key), + grpc_mdstr_from_slice(value)); +} + +grpc_mdelem *grpc_mdelem_from_string_and_buffer(const char *key, + const uint8_t *value, + size_t value_length) { + return grpc_mdelem_from_metadata_strings( + grpc_mdstr_from_string(key), grpc_mdstr_from_buffer(value, value_length)); +} + +grpc_mdelem *grpc_mdelem_ref(grpc_mdelem *gmd DEBUG_ARGS) { + internal_metadata *md = (internal_metadata *)gmd; + if (is_mdelem_static(gmd)) return gmd; +#ifdef GRPC_METADATA_REFCOUNT_DEBUG + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, + "ELM REF:%p:%d->%d: '%s' = '%s'", md, + gpr_atm_no_barrier_load(&md->refcnt), + gpr_atm_no_barrier_load(&md->refcnt) + 1, + grpc_mdstr_as_c_string((grpc_mdstr *)md->key), + grpc_mdstr_as_c_string((grpc_mdstr *)md->value)); +#endif + /* we can assume the ref count is >= 1 as the application is calling + this function - meaning that no adjustment to mdtab_free is necessary, + simplifying the logic here to be just an atomic increment */ + /* use C assert to have this removed in opt builds */ + assert(gpr_atm_no_barrier_load(&md->refcnt) >= 2); + gpr_atm_no_barrier_fetch_add(&md->refcnt, 1); + return gmd; +} + +void grpc_mdelem_unref(grpc_mdelem *gmd DEBUG_ARGS) { + internal_metadata *md = (internal_metadata *)gmd; + if (!md) return; + if (is_mdelem_static(gmd)) return; +#ifdef GRPC_METADATA_REFCOUNT_DEBUG + gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG, + "ELM UNREF:%p:%d->%d: '%s' = '%s'", md, + gpr_atm_no_barrier_load(&md->refcnt), + gpr_atm_no_barrier_load(&md->refcnt) - 1, + grpc_mdstr_as_c_string((grpc_mdstr *)md->key), + grpc_mdstr_as_c_string((grpc_mdstr *)md->value)); +#endif + if (2 == gpr_atm_full_fetch_add(&md->refcnt, -1)) { + uint32_t hash = GRPC_MDSTR_KV_HASH(md->key->hash, md->value->hash); + mdtab_shard *shard = + &g_mdtab_shard[SHARD_IDX(hash, LOG2_MDTAB_SHARD_COUNT)]; + GPR_TIMER_BEGIN("grpc_mdelem_unref.to_zero", 0); + gpr_mu_lock(&shard->mu); + if (1 == gpr_atm_no_barrier_load(&md->refcnt)) { + shard->free++; + gpr_atm_no_barrier_store(&md->refcnt, 0); + } + gpr_mu_unlock(&shard->mu); + GPR_TIMER_END("grpc_mdelem_unref.to_zero", 0); + } +} + +const char *grpc_mdstr_as_c_string(grpc_mdstr *s) { + return (const char *)GPR_SLICE_START_PTR(s->slice); +} + +grpc_mdstr *grpc_mdstr_ref(grpc_mdstr *gs DEBUG_ARGS) { + internal_string *s = (internal_string *)gs; + if (is_mdstr_static(gs)) return gs; + GPR_ASSERT(gpr_atm_full_fetch_add(&s->refcnt, 1) != 0); + return gs; +} + +void grpc_mdstr_unref(grpc_mdstr *gs DEBUG_ARGS) { + internal_string *s = (internal_string *)gs; + if (is_mdstr_static(gs)) return; + if (2 == gpr_atm_full_fetch_add(&s->refcnt, -1)) { + strtab_shard *shard = + &g_strtab_shard[SHARD_IDX(s->hash, LOG2_STRTAB_SHARD_COUNT)]; + gpr_mu_lock(&shard->mu); + if (1 == gpr_atm_no_barrier_load(&s->refcnt)) { + internal_destroy_string(shard, s); + } + gpr_mu_unlock(&shard->mu); + } +} + +void *grpc_mdelem_get_user_data(grpc_mdelem *md, void (*destroy_func)(void *)) { + internal_metadata *im = (internal_metadata *)md; + void *result; + if (is_mdelem_static(md)) { + return (void *)grpc_static_mdelem_user_data[md - grpc_static_mdelem_table]; + } + if (gpr_atm_acq_load(&im->destroy_user_data) == (gpr_atm)destroy_func) { + return (void *)gpr_atm_no_barrier_load(&im->user_data); + } else { + return NULL; + } + return result; +} + +void grpc_mdelem_set_user_data(grpc_mdelem *md, void (*destroy_func)(void *), + void *user_data) { + internal_metadata *im = (internal_metadata *)md; + GPR_ASSERT(!is_mdelem_static(md)); + GPR_ASSERT((user_data == NULL) == (destroy_func == NULL)); + gpr_mu_lock(&im->mu_user_data); + if (gpr_atm_no_barrier_load(&im->destroy_user_data)) { + /* user data can only be set once */ + gpr_mu_unlock(&im->mu_user_data); + if (destroy_func != NULL) { + destroy_func(user_data); + } + return; + } + gpr_atm_no_barrier_store(&im->user_data, (gpr_atm)user_data); + gpr_atm_rel_store(&im->destroy_user_data, (gpr_atm)destroy_func); + gpr_mu_unlock(&im->mu_user_data); +} + +gpr_slice grpc_mdstr_as_base64_encoded_and_huffman_compressed(grpc_mdstr *gs) { + internal_string *s = (internal_string *)gs; + gpr_slice slice; + strtab_shard *shard = + &g_strtab_shard[SHARD_IDX(s->hash, LOG2_STRTAB_SHARD_COUNT)]; + gpr_mu_lock(&shard->mu); + if (!s->has_base64_and_huffman_encoded) { + s->base64_and_huffman = + grpc_chttp2_base64_encode_and_huffman_compress(s->slice); + s->has_base64_and_huffman_encoded = 1; + } + slice = s->base64_and_huffman; + gpr_mu_unlock(&shard->mu); + return slice; +} diff --git a/src/core/lib/transport/metadata.h b/src/core/lib/transport/metadata.h new file mode 100644 index 0000000000..d72ec9accc --- /dev/null +++ b/src/core/lib/transport/metadata.h @@ -0,0 +1,156 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_METADATA_H +#define GRPC_CORE_LIB_TRANSPORT_METADATA_H + +#include <grpc/support/slice.h> +#include <grpc/support/useful.h> + +/* This file provides a mechanism for tracking metadata through the grpc stack. + It's not intended for consumption outside of the library. + + Metadata is tracked in the context of a grpc_mdctx. For the time being there + is one of these per-channel, avoiding cross channel interference with memory + use and lock contention. + + The context tracks unique strings (grpc_mdstr) and pairs of strings + (grpc_mdelem). Any of these objects can be checked for equality by comparing + their pointers. These objects are reference counted. + + grpc_mdelem can additionally store a (non-NULL) user data pointer. This + pointer is intended to be used to cache semantic meaning of a metadata + element. For example, an OAuth token may cache the credentials it represents + and the time at which it expires in the mdelem user data. + + Combining this metadata cache and the hpack compression table allows us to + simply lookup complete preparsed objects quickly, incurring a few atomic + ops per metadata element on the fast path. + + grpc_mdelem instances MAY live longer than their refcount implies, and are + garbage collected periodically, meaning cached data can easily outlive a + single request. + + STATIC METADATA: in static_metadata.h we declare a set of static metadata. + These mdelems and mdstrs are available via pre-declared code generated macros + and are available to code anywhere between grpc_init() and grpc_shutdown(). + They are not refcounted, but can be passed to _ref and _unref functions + declared here - in which case those functions are effectively no-ops. */ + +/* Forward declarations */ +typedef struct grpc_mdstr grpc_mdstr; +typedef struct grpc_mdelem grpc_mdelem; + +/* if changing this, make identical changes in internal_string in metadata.c */ +struct grpc_mdstr { + const gpr_slice slice; + const uint32_t hash; + /* there is a private part to this in metadata.c */ +}; + +/* if changing this, make identical changes in internal_metadata in + metadata.c */ +struct grpc_mdelem { + grpc_mdstr *const key; + grpc_mdstr *const value; + /* there is a private part to this in metadata.c */ +}; + +void grpc_test_only_set_metadata_hash_seed(uint32_t seed); + +/* Constructors for grpc_mdstr instances; take a variety of data types that + clients may have handy */ +grpc_mdstr *grpc_mdstr_from_string(const char *str); +/* Unrefs the slice. */ +grpc_mdstr *grpc_mdstr_from_slice(gpr_slice slice); +grpc_mdstr *grpc_mdstr_from_buffer(const uint8_t *str, size_t length); + +/* Returns a borrowed slice from the mdstr with its contents base64 encoded + and huffman compressed */ +gpr_slice grpc_mdstr_as_base64_encoded_and_huffman_compressed(grpc_mdstr *str); + +/* Constructors for grpc_mdelem instances; take a variety of data types that + clients may have handy */ +grpc_mdelem *grpc_mdelem_from_metadata_strings(grpc_mdstr *key, + grpc_mdstr *value); +grpc_mdelem *grpc_mdelem_from_strings(const char *key, const char *value); +/* Unrefs the slices. */ +grpc_mdelem *grpc_mdelem_from_slices(gpr_slice key, gpr_slice value); +grpc_mdelem *grpc_mdelem_from_string_and_buffer(const char *key, + const uint8_t *value, + size_t value_length); + +/* Mutator and accessor for grpc_mdelem user data. The destructor function + is used as a type tag and is checked during user_data fetch. */ +void *grpc_mdelem_get_user_data(grpc_mdelem *md, + void (*if_destroy_func)(void *)); +void grpc_mdelem_set_user_data(grpc_mdelem *md, void (*destroy_func)(void *), + void *user_data); + +/* Reference counting */ +#ifdef GRPC_METADATA_REFCOUNT_DEBUG +#define GRPC_MDSTR_REF(s) grpc_mdstr_ref((s), __FILE__, __LINE__) +#define GRPC_MDSTR_UNREF(s) grpc_mdstr_unref((s), __FILE__, __LINE__) +#define GRPC_MDELEM_REF(s) grpc_mdelem_ref((s), __FILE__, __LINE__) +#define GRPC_MDELEM_UNREF(s) grpc_mdelem_unref((s), __FILE__, __LINE__) +grpc_mdstr *grpc_mdstr_ref(grpc_mdstr *s, const char *file, int line); +void grpc_mdstr_unref(grpc_mdstr *s, const char *file, int line); +grpc_mdelem *grpc_mdelem_ref(grpc_mdelem *md, const char *file, int line); +void grpc_mdelem_unref(grpc_mdelem *md, const char *file, int line); +#else +#define GRPC_MDSTR_REF(s) grpc_mdstr_ref((s)) +#define GRPC_MDSTR_UNREF(s) grpc_mdstr_unref((s)) +#define GRPC_MDELEM_REF(s) grpc_mdelem_ref((s)) +#define GRPC_MDELEM_UNREF(s) grpc_mdelem_unref((s)) +grpc_mdstr *grpc_mdstr_ref(grpc_mdstr *s); +void grpc_mdstr_unref(grpc_mdstr *s); +grpc_mdelem *grpc_mdelem_ref(grpc_mdelem *md); +void grpc_mdelem_unref(grpc_mdelem *md); +#endif + +/* Recover a char* from a grpc_mdstr. The returned string is null terminated. + Does not promise that the returned string has no embedded nulls however. */ +const char *grpc_mdstr_as_c_string(grpc_mdstr *s); + +#define GRPC_MDSTR_LENGTH(s) (GPR_SLICE_LENGTH(s->slice)) + +int grpc_mdstr_is_legal_header(grpc_mdstr *s); +int grpc_mdstr_is_legal_nonbin_header(grpc_mdstr *s); +int grpc_mdstr_is_bin_suffixed(grpc_mdstr *s); + +#define GRPC_MDSTR_KV_HASH(k_hash, v_hash) (GPR_ROTL((k_hash), 2) ^ (v_hash)) + +void grpc_mdctx_global_init(void); +void grpc_mdctx_global_shutdown(void); + +#endif /* GRPC_CORE_LIB_TRANSPORT_METADATA_H */ diff --git a/src/core/lib/transport/metadata_batch.c b/src/core/lib/transport/metadata_batch.c new file mode 100644 index 0000000000..bb79b8fa96 --- /dev/null +++ b/src/core/lib/transport/metadata_batch.c @@ -0,0 +1,194 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/metadata_batch.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +#include "src/core/lib/profiling/timers.h" + +static void assert_valid_list(grpc_mdelem_list *list) { +#ifndef NDEBUG + grpc_linked_mdelem *l; + + GPR_ASSERT((list->head == NULL) == (list->tail == NULL)); + if (!list->head) return; + GPR_ASSERT(list->head->prev == NULL); + GPR_ASSERT(list->tail->next == NULL); + GPR_ASSERT((list->head == list->tail) == (list->head->next == NULL)); + + for (l = list->head; l; l = l->next) { + GPR_ASSERT(l->md); + GPR_ASSERT((l->prev == NULL) == (l == list->head)); + GPR_ASSERT((l->next == NULL) == (l == list->tail)); + if (l->next) GPR_ASSERT(l->next->prev == l); + if (l->prev) GPR_ASSERT(l->prev->next == l); + } +#endif /* NDEBUG */ +} + +#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) { + batch->list.head = batch->list.tail = NULL; + batch->deadline = gpr_inf_future(GPR_CLOCK_REALTIME); +} + +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); + } +} + +void grpc_metadata_batch_add_head(grpc_metadata_batch *batch, + grpc_linked_mdelem *storage, + grpc_mdelem *elem_to_add) { + GPR_ASSERT(elem_to_add); + storage->md = elem_to_add; + 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(storage->md); + storage->prev = NULL; + storage->next = list->head; + if (list->head != NULL) { + list->head->prev = storage; + } else { + list->tail = storage; + } + list->head = storage; + assert_valid_list(list); +} + +void grpc_metadata_batch_link_head(grpc_metadata_batch *batch, + grpc_linked_mdelem *storage) { + link_head(&batch->list, storage); +} + +void grpc_metadata_batch_add_tail(grpc_metadata_batch *batch, + grpc_linked_mdelem *storage, + grpc_mdelem *elem_to_add) { + GPR_ASSERT(elem_to_add); + storage->md = elem_to_add; + 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(storage->md); + storage->prev = list->tail; + storage->next = NULL; + storage->reserved = NULL; + if (list->tail != NULL) { + list->tail->next = storage; + } else { + list->head = storage; + } + list->tail = storage; + assert_valid_list(list); +} + +void grpc_metadata_batch_link_tail(grpc_metadata_batch *batch, + grpc_linked_mdelem *storage) { + link_tail(&batch->list, storage); +} + +void grpc_metadata_batch_move(grpc_metadata_batch *dst, + grpc_metadata_batch *src) { + *dst = *src; + memset(src, 0, sizeof(grpc_metadata_batch)); +} + +void grpc_metadata_batch_filter(grpc_metadata_batch *batch, + grpc_mdelem *(*filter)(void *user_data, + grpc_mdelem *elem), + void *user_data) { + grpc_linked_mdelem *l; + grpc_linked_mdelem *next; + + GPR_TIMER_BEGIN("grpc_metadata_batch_filter", 0); + + assert_valid_list(&batch->list); + for (l = batch->list.head; l; l = next) { + grpc_mdelem *orig = l->md; + grpc_mdelem *filt = filter(user_data, orig); + next = l->next; + if (filt == NULL) { + if (l->prev) { + l->prev->next = l->next; + } + if (l->next) { + l->next->prev = l->prev; + } + if (batch->list.head == l) { + batch->list.head = l->next; + } + if (batch->list.tail == l) { + batch->list.tail = l->prev; + } + assert_valid_list(&batch->list); + GRPC_MDELEM_UNREF(l->md); + } else if (filt != orig) { + GRPC_MDELEM_UNREF(orig); + l->md = filt; + } + } + assert_valid_list(&batch->list); + + GPR_TIMER_END("grpc_metadata_batch_filter", 0); +} + +static grpc_mdelem *no_metadata_for_you(void *user_data, grpc_mdelem *elem) { + return NULL; +} + +void grpc_metadata_batch_clear(grpc_metadata_batch *batch) { + batch->deadline = gpr_inf_future(GPR_CLOCK_REALTIME); + grpc_metadata_batch_filter(batch, no_metadata_for_you, NULL); +} + +int grpc_metadata_batch_is_empty(grpc_metadata_batch *batch) { + return batch->list.head == NULL && + gpr_time_cmp(gpr_inf_future(batch->deadline.clock_type), + batch->deadline) == 0; +} diff --git a/src/core/lib/transport/metadata_batch.h b/src/core/lib/transport/metadata_batch.h new file mode 100644 index 0000000000..f1d4726989 --- /dev/null +++ b/src/core/lib/transport/metadata_batch.h @@ -0,0 +1,125 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_METADATA_BATCH_H +#define GRPC_CORE_LIB_TRANSPORT_METADATA_BATCH_H + +#include <grpc/grpc.h> +#include <grpc/support/port_platform.h> +#include <grpc/support/slice.h> +#include <grpc/support/time.h> +#include "src/core/lib/transport/metadata.h" + +typedef struct grpc_linked_mdelem { + grpc_mdelem *md; + struct grpc_linked_mdelem *next; + struct grpc_linked_mdelem *prev; + void *reserved; +} grpc_linked_mdelem; + +typedef struct grpc_mdelem_list { + grpc_linked_mdelem *head; + grpc_linked_mdelem *tail; +} grpc_mdelem_list; + +typedef struct grpc_metadata_batch { + /** Metadata elements in this batch */ + grpc_mdelem_list list; + /** Used to calculate grpc-timeout at the point of sending, + or gpr_inf_future if this batch does not need to send a + grpc-timeout */ + gpr_timespec deadline; +} grpc_metadata_batch; + +void grpc_metadata_batch_init(grpc_metadata_batch *batch); +void grpc_metadata_batch_destroy(grpc_metadata_batch *batch); +void grpc_metadata_batch_clear(grpc_metadata_batch *batch); +int grpc_metadata_batch_is_empty(grpc_metadata_batch *batch); + +/** Moves the metadata information from \a src to \a dst. Upon return, \a src is + * zeroed. */ +void grpc_metadata_batch_move(grpc_metadata_batch *dst, + grpc_metadata_batch *src); + +/** Add \a storage to the beginning of \a batch. storage->md is + assumed to be valid. + \a storage is owned by the caller and must survive for the + lifetime of batch. This usually means it should be around + for the lifetime of the call. */ +void grpc_metadata_batch_link_head(grpc_metadata_batch *batch, + grpc_linked_mdelem *storage); +/** Add \a storage to the end of \a batch. storage->md is + assumed to be valid. + \a storage is owned by the caller and must survive for the + lifetime of batch. This usually means it should be around + for the lifetime of the call. */ +void grpc_metadata_batch_link_tail(grpc_metadata_batch *batch, + grpc_linked_mdelem *storage); + +/** Add \a elem_to_add as the first element in \a batch, using + \a storage as backing storage for the linked list element. + \a storage is owned by the caller and must survive for the + lifetime of batch. This usually means it should be around + for the lifetime of the call. + Takes ownership of \a elem_to_add */ +void grpc_metadata_batch_add_head(grpc_metadata_batch *batch, + grpc_linked_mdelem *storage, + grpc_mdelem *elem_to_add); +/** Add \a elem_to_add as the last element in \a batch, using + \a storage as backing storage for the linked list element. + \a storage is owned by the caller and must survive for the + lifetime of batch. This usually means it should be around + for the lifetime of the call. + Takes ownership of \a elem_to_add */ +void grpc_metadata_batch_add_tail(grpc_metadata_batch *batch, + grpc_linked_mdelem *storage, + grpc_mdelem *elem_to_add); + +/** For each element in \a batch, execute \a filter. + The return value from \a filter will be substituted for the + grpc_mdelem passed to \a filter. If \a filter returns NULL, + the element will be moved to the garbage list. */ +void grpc_metadata_batch_filter(grpc_metadata_batch *batch, + grpc_mdelem *(*filter)(void *user_data, + grpc_mdelem *elem), + void *user_data); + +#ifndef NDEBUG +void grpc_metadata_batch_assert_ok(grpc_metadata_batch *comd); +#else +#define grpc_metadata_batch_assert_ok(comd) \ + do { \ + } while (0) +#endif + +#endif /* GRPC_CORE_LIB_TRANSPORT_METADATA_BATCH_H */ diff --git a/src/core/lib/transport/static_metadata.c b/src/core/lib/transport/static_metadata.c new file mode 100644 index 0000000000..eda277b3dc --- /dev/null +++ b/src/core/lib/transport/static_metadata.c @@ -0,0 +1,160 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* + * WARNING: Auto-generated code. + * + * To make changes to this file, change + * tools/codegen/core/gen_static_metadata.py, + * and then re-run it. + * + * See metadata.h for an explanation of the interface here, and metadata.c for + * an + * explanation of what's going on. + */ + +#include "src/core/lib/transport/static_metadata.h" + +grpc_mdstr grpc_static_mdstr_table[GRPC_STATIC_MDSTR_COUNT]; + +grpc_mdelem grpc_static_mdelem_table[GRPC_STATIC_MDELEM_COUNT]; +uintptr_t grpc_static_mdelem_user_data[GRPC_STATIC_MDELEM_COUNT] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 4, 8, 6, 2, 4, 8, 6, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +const uint8_t grpc_static_metadata_elem_indices[GRPC_STATIC_MDELEM_COUNT * 2] = + {11, 35, 10, 35, 12, 35, 12, 49, 13, 35, 14, 35, 15, 35, 16, 35, 17, 35, + 19, 35, 20, 35, 21, 35, 24, 35, 25, 35, 26, 35, 27, 35, 28, 35, 29, 35, + 30, 18, 30, 35, 31, 35, 32, 35, 36, 35, 37, 35, 38, 35, 39, 35, 42, 33, + 42, 34, 42, 48, 42, 53, 42, 54, 42, 55, 42, 56, 43, 33, 43, 48, 43, 53, + 46, 0, 46, 1, 46, 2, 50, 35, 57, 35, 58, 35, 59, 35, 60, 35, 61, 35, + 62, 35, 63, 35, 64, 35, 65, 35, 66, 40, 66, 68, 67, 78, 67, 79, 69, 35, + 70, 35, 71, 35, 72, 35, 73, 35, 74, 35, 75, 41, 75, 51, 75, 52, 76, 35, + 77, 35, 80, 3, 80, 4, 80, 5, 80, 6, 80, 7, 80, 8, 80, 9, 81, 35, + 82, 83, 84, 35, 85, 35, 86, 35, 87, 35, 88, 35}; + +const char *const grpc_static_metadata_strings[GRPC_STATIC_MDSTR_COUNT] = { + "0", + "1", + "2", + "200", + "204", + "206", + "304", + "400", + "404", + "500", + "accept", + "accept-charset", + "accept-encoding", + "accept-language", + "accept-ranges", + "access-control-allow-origin", + "age", + "allow", + "application/grpc", + ":authority", + "authorization", + "cache-control", + "census-bin", + "census-binary-bin", + "content-disposition", + "content-encoding", + "content-language", + "content-length", + "content-location", + "content-range", + "content-type", + "cookie", + "date", + "deflate", + "deflate,gzip", + "", + "etag", + "expect", + "expires", + "from", + "GET", + "grpc", + "grpc-accept-encoding", + "grpc-encoding", + "grpc-internal-encoding-request", + "grpc-message", + "grpc-status", + "grpc-timeout", + "gzip", + "gzip, deflate", + "host", + "http", + "https", + "identity", + "identity,deflate", + "identity,deflate,gzip", + "identity,gzip", + "if-match", + "if-modified-since", + "if-none-match", + "if-range", + "if-unmodified-since", + "last-modified", + "link", + "location", + "max-forwards", + ":method", + ":path", + "POST", + "proxy-authenticate", + "proxy-authorization", + "range", + "referer", + "refresh", + "retry-after", + ":scheme", + "server", + "set-cookie", + "/", + "/index.html", + ":status", + "strict-transport-security", + "te", + "trailers", + "transfer-encoding", + "user-agent", + "vary", + "via", + "www-authenticate"}; + +const uint8_t grpc_static_accept_encoding_metadata[8] = {0, 29, 26, 30, + 28, 32, 27, 31}; diff --git a/src/core/lib/transport/static_metadata.h b/src/core/lib/transport/static_metadata.h new file mode 100644 index 0000000000..aff136a6d2 --- /dev/null +++ b/src/core/lib/transport/static_metadata.h @@ -0,0 +1,408 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* + * WARNING: Auto-generated code. + * + * To make changes to this file, change + * tools/codegen/core/gen_static_metadata.py, + * and then re-run it. + * + * See metadata.h for an explanation of the interface here, and metadata.c for + * an + * explanation of what's going on. + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_STATIC_METADATA_H +#define GRPC_CORE_LIB_TRANSPORT_STATIC_METADATA_H + +#include "src/core/lib/transport/metadata.h" + +#define GRPC_STATIC_MDSTR_COUNT 89 +extern grpc_mdstr grpc_static_mdstr_table[GRPC_STATIC_MDSTR_COUNT]; +/* "0" */ +#define GRPC_MDSTR_0 (&grpc_static_mdstr_table[0]) +/* "1" */ +#define GRPC_MDSTR_1 (&grpc_static_mdstr_table[1]) +/* "2" */ +#define GRPC_MDSTR_2 (&grpc_static_mdstr_table[2]) +/* "200" */ +#define GRPC_MDSTR_200 (&grpc_static_mdstr_table[3]) +/* "204" */ +#define GRPC_MDSTR_204 (&grpc_static_mdstr_table[4]) +/* "206" */ +#define GRPC_MDSTR_206 (&grpc_static_mdstr_table[5]) +/* "304" */ +#define GRPC_MDSTR_304 (&grpc_static_mdstr_table[6]) +/* "400" */ +#define GRPC_MDSTR_400 (&grpc_static_mdstr_table[7]) +/* "404" */ +#define GRPC_MDSTR_404 (&grpc_static_mdstr_table[8]) +/* "500" */ +#define GRPC_MDSTR_500 (&grpc_static_mdstr_table[9]) +/* "accept" */ +#define GRPC_MDSTR_ACCEPT (&grpc_static_mdstr_table[10]) +/* "accept-charset" */ +#define GRPC_MDSTR_ACCEPT_CHARSET (&grpc_static_mdstr_table[11]) +/* "accept-encoding" */ +#define GRPC_MDSTR_ACCEPT_ENCODING (&grpc_static_mdstr_table[12]) +/* "accept-language" */ +#define GRPC_MDSTR_ACCEPT_LANGUAGE (&grpc_static_mdstr_table[13]) +/* "accept-ranges" */ +#define GRPC_MDSTR_ACCEPT_RANGES (&grpc_static_mdstr_table[14]) +/* "access-control-allow-origin" */ +#define GRPC_MDSTR_ACCESS_CONTROL_ALLOW_ORIGIN (&grpc_static_mdstr_table[15]) +/* "age" */ +#define GRPC_MDSTR_AGE (&grpc_static_mdstr_table[16]) +/* "allow" */ +#define GRPC_MDSTR_ALLOW (&grpc_static_mdstr_table[17]) +/* "application/grpc" */ +#define GRPC_MDSTR_APPLICATION_SLASH_GRPC (&grpc_static_mdstr_table[18]) +/* ":authority" */ +#define GRPC_MDSTR_AUTHORITY (&grpc_static_mdstr_table[19]) +/* "authorization" */ +#define GRPC_MDSTR_AUTHORIZATION (&grpc_static_mdstr_table[20]) +/* "cache-control" */ +#define GRPC_MDSTR_CACHE_CONTROL (&grpc_static_mdstr_table[21]) +/* "census-bin" */ +#define GRPC_MDSTR_CENSUS_BIN (&grpc_static_mdstr_table[22]) +/* "census-binary-bin" */ +#define GRPC_MDSTR_CENSUS_BINARY_BIN (&grpc_static_mdstr_table[23]) +/* "content-disposition" */ +#define GRPC_MDSTR_CONTENT_DISPOSITION (&grpc_static_mdstr_table[24]) +/* "content-encoding" */ +#define GRPC_MDSTR_CONTENT_ENCODING (&grpc_static_mdstr_table[25]) +/* "content-language" */ +#define GRPC_MDSTR_CONTENT_LANGUAGE (&grpc_static_mdstr_table[26]) +/* "content-length" */ +#define GRPC_MDSTR_CONTENT_LENGTH (&grpc_static_mdstr_table[27]) +/* "content-location" */ +#define GRPC_MDSTR_CONTENT_LOCATION (&grpc_static_mdstr_table[28]) +/* "content-range" */ +#define GRPC_MDSTR_CONTENT_RANGE (&grpc_static_mdstr_table[29]) +/* "content-type" */ +#define GRPC_MDSTR_CONTENT_TYPE (&grpc_static_mdstr_table[30]) +/* "cookie" */ +#define GRPC_MDSTR_COOKIE (&grpc_static_mdstr_table[31]) +/* "date" */ +#define GRPC_MDSTR_DATE (&grpc_static_mdstr_table[32]) +/* "deflate" */ +#define GRPC_MDSTR_DEFLATE (&grpc_static_mdstr_table[33]) +/* "deflate,gzip" */ +#define GRPC_MDSTR_DEFLATE_COMMA_GZIP (&grpc_static_mdstr_table[34]) +/* "" */ +#define GRPC_MDSTR_EMPTY (&grpc_static_mdstr_table[35]) +/* "etag" */ +#define GRPC_MDSTR_ETAG (&grpc_static_mdstr_table[36]) +/* "expect" */ +#define GRPC_MDSTR_EXPECT (&grpc_static_mdstr_table[37]) +/* "expires" */ +#define GRPC_MDSTR_EXPIRES (&grpc_static_mdstr_table[38]) +/* "from" */ +#define GRPC_MDSTR_FROM (&grpc_static_mdstr_table[39]) +/* "GET" */ +#define GRPC_MDSTR_GET (&grpc_static_mdstr_table[40]) +/* "grpc" */ +#define GRPC_MDSTR_GRPC (&grpc_static_mdstr_table[41]) +/* "grpc-accept-encoding" */ +#define GRPC_MDSTR_GRPC_ACCEPT_ENCODING (&grpc_static_mdstr_table[42]) +/* "grpc-encoding" */ +#define GRPC_MDSTR_GRPC_ENCODING (&grpc_static_mdstr_table[43]) +/* "grpc-internal-encoding-request" */ +#define GRPC_MDSTR_GRPC_INTERNAL_ENCODING_REQUEST (&grpc_static_mdstr_table[44]) +/* "grpc-message" */ +#define GRPC_MDSTR_GRPC_MESSAGE (&grpc_static_mdstr_table[45]) +/* "grpc-status" */ +#define GRPC_MDSTR_GRPC_STATUS (&grpc_static_mdstr_table[46]) +/* "grpc-timeout" */ +#define GRPC_MDSTR_GRPC_TIMEOUT (&grpc_static_mdstr_table[47]) +/* "gzip" */ +#define GRPC_MDSTR_GZIP (&grpc_static_mdstr_table[48]) +/* "gzip, deflate" */ +#define GRPC_MDSTR_GZIP_COMMA_DEFLATE (&grpc_static_mdstr_table[49]) +/* "host" */ +#define GRPC_MDSTR_HOST (&grpc_static_mdstr_table[50]) +/* "http" */ +#define GRPC_MDSTR_HTTP (&grpc_static_mdstr_table[51]) +/* "https" */ +#define GRPC_MDSTR_HTTPS (&grpc_static_mdstr_table[52]) +/* "identity" */ +#define GRPC_MDSTR_IDENTITY (&grpc_static_mdstr_table[53]) +/* "identity,deflate" */ +#define GRPC_MDSTR_IDENTITY_COMMA_DEFLATE (&grpc_static_mdstr_table[54]) +/* "identity,deflate,gzip" */ +#define GRPC_MDSTR_IDENTITY_COMMA_DEFLATE_COMMA_GZIP \ + (&grpc_static_mdstr_table[55]) +/* "identity,gzip" */ +#define GRPC_MDSTR_IDENTITY_COMMA_GZIP (&grpc_static_mdstr_table[56]) +/* "if-match" */ +#define GRPC_MDSTR_IF_MATCH (&grpc_static_mdstr_table[57]) +/* "if-modified-since" */ +#define GRPC_MDSTR_IF_MODIFIED_SINCE (&grpc_static_mdstr_table[58]) +/* "if-none-match" */ +#define GRPC_MDSTR_IF_NONE_MATCH (&grpc_static_mdstr_table[59]) +/* "if-range" */ +#define GRPC_MDSTR_IF_RANGE (&grpc_static_mdstr_table[60]) +/* "if-unmodified-since" */ +#define GRPC_MDSTR_IF_UNMODIFIED_SINCE (&grpc_static_mdstr_table[61]) +/* "last-modified" */ +#define GRPC_MDSTR_LAST_MODIFIED (&grpc_static_mdstr_table[62]) +/* "link" */ +#define GRPC_MDSTR_LINK (&grpc_static_mdstr_table[63]) +/* "location" */ +#define GRPC_MDSTR_LOCATION (&grpc_static_mdstr_table[64]) +/* "max-forwards" */ +#define GRPC_MDSTR_MAX_FORWARDS (&grpc_static_mdstr_table[65]) +/* ":method" */ +#define GRPC_MDSTR_METHOD (&grpc_static_mdstr_table[66]) +/* ":path" */ +#define GRPC_MDSTR_PATH (&grpc_static_mdstr_table[67]) +/* "POST" */ +#define GRPC_MDSTR_POST (&grpc_static_mdstr_table[68]) +/* "proxy-authenticate" */ +#define GRPC_MDSTR_PROXY_AUTHENTICATE (&grpc_static_mdstr_table[69]) +/* "proxy-authorization" */ +#define GRPC_MDSTR_PROXY_AUTHORIZATION (&grpc_static_mdstr_table[70]) +/* "range" */ +#define GRPC_MDSTR_RANGE (&grpc_static_mdstr_table[71]) +/* "referer" */ +#define GRPC_MDSTR_REFERER (&grpc_static_mdstr_table[72]) +/* "refresh" */ +#define GRPC_MDSTR_REFRESH (&grpc_static_mdstr_table[73]) +/* "retry-after" */ +#define GRPC_MDSTR_RETRY_AFTER (&grpc_static_mdstr_table[74]) +/* ":scheme" */ +#define GRPC_MDSTR_SCHEME (&grpc_static_mdstr_table[75]) +/* "server" */ +#define GRPC_MDSTR_SERVER (&grpc_static_mdstr_table[76]) +/* "set-cookie" */ +#define GRPC_MDSTR_SET_COOKIE (&grpc_static_mdstr_table[77]) +/* "/" */ +#define GRPC_MDSTR_SLASH (&grpc_static_mdstr_table[78]) +/* "/index.html" */ +#define GRPC_MDSTR_SLASH_INDEX_DOT_HTML (&grpc_static_mdstr_table[79]) +/* ":status" */ +#define GRPC_MDSTR_STATUS (&grpc_static_mdstr_table[80]) +/* "strict-transport-security" */ +#define GRPC_MDSTR_STRICT_TRANSPORT_SECURITY (&grpc_static_mdstr_table[81]) +/* "te" */ +#define GRPC_MDSTR_TE (&grpc_static_mdstr_table[82]) +/* "trailers" */ +#define GRPC_MDSTR_TRAILERS (&grpc_static_mdstr_table[83]) +/* "transfer-encoding" */ +#define GRPC_MDSTR_TRANSFER_ENCODING (&grpc_static_mdstr_table[84]) +/* "user-agent" */ +#define GRPC_MDSTR_USER_AGENT (&grpc_static_mdstr_table[85]) +/* "vary" */ +#define GRPC_MDSTR_VARY (&grpc_static_mdstr_table[86]) +/* "via" */ +#define GRPC_MDSTR_VIA (&grpc_static_mdstr_table[87]) +/* "www-authenticate" */ +#define GRPC_MDSTR_WWW_AUTHENTICATE (&grpc_static_mdstr_table[88]) + +#define GRPC_STATIC_MDELEM_COUNT 78 +extern grpc_mdelem grpc_static_mdelem_table[GRPC_STATIC_MDELEM_COUNT]; +extern uintptr_t grpc_static_mdelem_user_data[GRPC_STATIC_MDELEM_COUNT]; +/* "accept-charset": "" */ +#define GRPC_MDELEM_ACCEPT_CHARSET_EMPTY (&grpc_static_mdelem_table[0]) +/* "accept": "" */ +#define GRPC_MDELEM_ACCEPT_EMPTY (&grpc_static_mdelem_table[1]) +/* "accept-encoding": "" */ +#define GRPC_MDELEM_ACCEPT_ENCODING_EMPTY (&grpc_static_mdelem_table[2]) +/* "accept-encoding": "gzip, deflate" */ +#define GRPC_MDELEM_ACCEPT_ENCODING_GZIP_COMMA_DEFLATE \ + (&grpc_static_mdelem_table[3]) +/* "accept-language": "" */ +#define GRPC_MDELEM_ACCEPT_LANGUAGE_EMPTY (&grpc_static_mdelem_table[4]) +/* "accept-ranges": "" */ +#define GRPC_MDELEM_ACCEPT_RANGES_EMPTY (&grpc_static_mdelem_table[5]) +/* "access-control-allow-origin": "" */ +#define GRPC_MDELEM_ACCESS_CONTROL_ALLOW_ORIGIN_EMPTY \ + (&grpc_static_mdelem_table[6]) +/* "age": "" */ +#define GRPC_MDELEM_AGE_EMPTY (&grpc_static_mdelem_table[7]) +/* "allow": "" */ +#define GRPC_MDELEM_ALLOW_EMPTY (&grpc_static_mdelem_table[8]) +/* ":authority": "" */ +#define GRPC_MDELEM_AUTHORITY_EMPTY (&grpc_static_mdelem_table[9]) +/* "authorization": "" */ +#define GRPC_MDELEM_AUTHORIZATION_EMPTY (&grpc_static_mdelem_table[10]) +/* "cache-control": "" */ +#define GRPC_MDELEM_CACHE_CONTROL_EMPTY (&grpc_static_mdelem_table[11]) +/* "content-disposition": "" */ +#define GRPC_MDELEM_CONTENT_DISPOSITION_EMPTY (&grpc_static_mdelem_table[12]) +/* "content-encoding": "" */ +#define GRPC_MDELEM_CONTENT_ENCODING_EMPTY (&grpc_static_mdelem_table[13]) +/* "content-language": "" */ +#define GRPC_MDELEM_CONTENT_LANGUAGE_EMPTY (&grpc_static_mdelem_table[14]) +/* "content-length": "" */ +#define GRPC_MDELEM_CONTENT_LENGTH_EMPTY (&grpc_static_mdelem_table[15]) +/* "content-location": "" */ +#define GRPC_MDELEM_CONTENT_LOCATION_EMPTY (&grpc_static_mdelem_table[16]) +/* "content-range": "" */ +#define GRPC_MDELEM_CONTENT_RANGE_EMPTY (&grpc_static_mdelem_table[17]) +/* "content-type": "application/grpc" */ +#define GRPC_MDELEM_CONTENT_TYPE_APPLICATION_SLASH_GRPC \ + (&grpc_static_mdelem_table[18]) +/* "content-type": "" */ +#define GRPC_MDELEM_CONTENT_TYPE_EMPTY (&grpc_static_mdelem_table[19]) +/* "cookie": "" */ +#define GRPC_MDELEM_COOKIE_EMPTY (&grpc_static_mdelem_table[20]) +/* "date": "" */ +#define GRPC_MDELEM_DATE_EMPTY (&grpc_static_mdelem_table[21]) +/* "etag": "" */ +#define GRPC_MDELEM_ETAG_EMPTY (&grpc_static_mdelem_table[22]) +/* "expect": "" */ +#define GRPC_MDELEM_EXPECT_EMPTY (&grpc_static_mdelem_table[23]) +/* "expires": "" */ +#define GRPC_MDELEM_EXPIRES_EMPTY (&grpc_static_mdelem_table[24]) +/* "from": "" */ +#define GRPC_MDELEM_FROM_EMPTY (&grpc_static_mdelem_table[25]) +/* "grpc-accept-encoding": "deflate" */ +#define GRPC_MDELEM_GRPC_ACCEPT_ENCODING_DEFLATE (&grpc_static_mdelem_table[26]) +/* "grpc-accept-encoding": "deflate,gzip" */ +#define GRPC_MDELEM_GRPC_ACCEPT_ENCODING_DEFLATE_COMMA_GZIP \ + (&grpc_static_mdelem_table[27]) +/* "grpc-accept-encoding": "gzip" */ +#define GRPC_MDELEM_GRPC_ACCEPT_ENCODING_GZIP (&grpc_static_mdelem_table[28]) +/* "grpc-accept-encoding": "identity" */ +#define GRPC_MDELEM_GRPC_ACCEPT_ENCODING_IDENTITY \ + (&grpc_static_mdelem_table[29]) +/* "grpc-accept-encoding": "identity,deflate" */ +#define GRPC_MDELEM_GRPC_ACCEPT_ENCODING_IDENTITY_COMMA_DEFLATE \ + (&grpc_static_mdelem_table[30]) +/* "grpc-accept-encoding": "identity,deflate,gzip" */ +#define GRPC_MDELEM_GRPC_ACCEPT_ENCODING_IDENTITY_COMMA_DEFLATE_COMMA_GZIP \ + (&grpc_static_mdelem_table[31]) +/* "grpc-accept-encoding": "identity,gzip" */ +#define GRPC_MDELEM_GRPC_ACCEPT_ENCODING_IDENTITY_COMMA_GZIP \ + (&grpc_static_mdelem_table[32]) +/* "grpc-encoding": "deflate" */ +#define GRPC_MDELEM_GRPC_ENCODING_DEFLATE (&grpc_static_mdelem_table[33]) +/* "grpc-encoding": "gzip" */ +#define GRPC_MDELEM_GRPC_ENCODING_GZIP (&grpc_static_mdelem_table[34]) +/* "grpc-encoding": "identity" */ +#define GRPC_MDELEM_GRPC_ENCODING_IDENTITY (&grpc_static_mdelem_table[35]) +/* "grpc-status": "0" */ +#define GRPC_MDELEM_GRPC_STATUS_0 (&grpc_static_mdelem_table[36]) +/* "grpc-status": "1" */ +#define GRPC_MDELEM_GRPC_STATUS_1 (&grpc_static_mdelem_table[37]) +/* "grpc-status": "2" */ +#define GRPC_MDELEM_GRPC_STATUS_2 (&grpc_static_mdelem_table[38]) +/* "host": "" */ +#define GRPC_MDELEM_HOST_EMPTY (&grpc_static_mdelem_table[39]) +/* "if-match": "" */ +#define GRPC_MDELEM_IF_MATCH_EMPTY (&grpc_static_mdelem_table[40]) +/* "if-modified-since": "" */ +#define GRPC_MDELEM_IF_MODIFIED_SINCE_EMPTY (&grpc_static_mdelem_table[41]) +/* "if-none-match": "" */ +#define GRPC_MDELEM_IF_NONE_MATCH_EMPTY (&grpc_static_mdelem_table[42]) +/* "if-range": "" */ +#define GRPC_MDELEM_IF_RANGE_EMPTY (&grpc_static_mdelem_table[43]) +/* "if-unmodified-since": "" */ +#define GRPC_MDELEM_IF_UNMODIFIED_SINCE_EMPTY (&grpc_static_mdelem_table[44]) +/* "last-modified": "" */ +#define GRPC_MDELEM_LAST_MODIFIED_EMPTY (&grpc_static_mdelem_table[45]) +/* "link": "" */ +#define GRPC_MDELEM_LINK_EMPTY (&grpc_static_mdelem_table[46]) +/* "location": "" */ +#define GRPC_MDELEM_LOCATION_EMPTY (&grpc_static_mdelem_table[47]) +/* "max-forwards": "" */ +#define GRPC_MDELEM_MAX_FORWARDS_EMPTY (&grpc_static_mdelem_table[48]) +/* ":method": "GET" */ +#define GRPC_MDELEM_METHOD_GET (&grpc_static_mdelem_table[49]) +/* ":method": "POST" */ +#define GRPC_MDELEM_METHOD_POST (&grpc_static_mdelem_table[50]) +/* ":path": "/" */ +#define GRPC_MDELEM_PATH_SLASH (&grpc_static_mdelem_table[51]) +/* ":path": "/index.html" */ +#define GRPC_MDELEM_PATH_SLASH_INDEX_DOT_HTML (&grpc_static_mdelem_table[52]) +/* "proxy-authenticate": "" */ +#define GRPC_MDELEM_PROXY_AUTHENTICATE_EMPTY (&grpc_static_mdelem_table[53]) +/* "proxy-authorization": "" */ +#define GRPC_MDELEM_PROXY_AUTHORIZATION_EMPTY (&grpc_static_mdelem_table[54]) +/* "range": "" */ +#define GRPC_MDELEM_RANGE_EMPTY (&grpc_static_mdelem_table[55]) +/* "referer": "" */ +#define GRPC_MDELEM_REFERER_EMPTY (&grpc_static_mdelem_table[56]) +/* "refresh": "" */ +#define GRPC_MDELEM_REFRESH_EMPTY (&grpc_static_mdelem_table[57]) +/* "retry-after": "" */ +#define GRPC_MDELEM_RETRY_AFTER_EMPTY (&grpc_static_mdelem_table[58]) +/* ":scheme": "grpc" */ +#define GRPC_MDELEM_SCHEME_GRPC (&grpc_static_mdelem_table[59]) +/* ":scheme": "http" */ +#define GRPC_MDELEM_SCHEME_HTTP (&grpc_static_mdelem_table[60]) +/* ":scheme": "https" */ +#define GRPC_MDELEM_SCHEME_HTTPS (&grpc_static_mdelem_table[61]) +/* "server": "" */ +#define GRPC_MDELEM_SERVER_EMPTY (&grpc_static_mdelem_table[62]) +/* "set-cookie": "" */ +#define GRPC_MDELEM_SET_COOKIE_EMPTY (&grpc_static_mdelem_table[63]) +/* ":status": "200" */ +#define GRPC_MDELEM_STATUS_200 (&grpc_static_mdelem_table[64]) +/* ":status": "204" */ +#define GRPC_MDELEM_STATUS_204 (&grpc_static_mdelem_table[65]) +/* ":status": "206" */ +#define GRPC_MDELEM_STATUS_206 (&grpc_static_mdelem_table[66]) +/* ":status": "304" */ +#define GRPC_MDELEM_STATUS_304 (&grpc_static_mdelem_table[67]) +/* ":status": "400" */ +#define GRPC_MDELEM_STATUS_400 (&grpc_static_mdelem_table[68]) +/* ":status": "404" */ +#define GRPC_MDELEM_STATUS_404 (&grpc_static_mdelem_table[69]) +/* ":status": "500" */ +#define GRPC_MDELEM_STATUS_500 (&grpc_static_mdelem_table[70]) +/* "strict-transport-security": "" */ +#define GRPC_MDELEM_STRICT_TRANSPORT_SECURITY_EMPTY \ + (&grpc_static_mdelem_table[71]) +/* "te": "trailers" */ +#define GRPC_MDELEM_TE_TRAILERS (&grpc_static_mdelem_table[72]) +/* "transfer-encoding": "" */ +#define GRPC_MDELEM_TRANSFER_ENCODING_EMPTY (&grpc_static_mdelem_table[73]) +/* "user-agent": "" */ +#define GRPC_MDELEM_USER_AGENT_EMPTY (&grpc_static_mdelem_table[74]) +/* "vary": "" */ +#define GRPC_MDELEM_VARY_EMPTY (&grpc_static_mdelem_table[75]) +/* "via": "" */ +#define GRPC_MDELEM_VIA_EMPTY (&grpc_static_mdelem_table[76]) +/* "www-authenticate": "" */ +#define GRPC_MDELEM_WWW_AUTHENTICATE_EMPTY (&grpc_static_mdelem_table[77]) + +extern const uint8_t + grpc_static_metadata_elem_indices[GRPC_STATIC_MDELEM_COUNT * 2]; +extern const char *const grpc_static_metadata_strings[GRPC_STATIC_MDSTR_COUNT]; +extern const uint8_t grpc_static_accept_encoding_metadata[8]; +#define GRPC_MDELEM_ACCEPT_ENCODING_FOR_ALGORITHMS(algs) \ + (&grpc_static_mdelem_table[grpc_static_accept_encoding_metadata[(algs)]]) +#endif /* GRPC_CORE_LIB_TRANSPORT_STATIC_METADATA_H */ diff --git a/src/core/lib/transport/transport.c b/src/core/lib/transport/transport.c new file mode 100644 index 0000000000..18256aae5e --- /dev/null +++ b/src/core/lib/transport/transport.c @@ -0,0 +1,184 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/transport/transport.h" +#include <grpc/support/alloc.h> +#include <grpc/support/atm.h> +#include <grpc/support/log.h> +#include "src/core/lib/transport/transport_impl.h" + +#ifdef GRPC_STREAM_REFCOUNT_DEBUG +void grpc_stream_ref(grpc_stream_refcount *refcount, const char *reason) { + gpr_atm val = gpr_atm_no_barrier_load(&refcount->refs.count); + gpr_log(GPR_DEBUG, "%s %p:%p REF %d->%d %s", refcount->object_type, + refcount, refcount->destroy.cb_arg, val, val + 1, reason); +#else +void grpc_stream_ref(grpc_stream_refcount *refcount) { +#endif + gpr_ref_non_zero(&refcount->refs); +} + +#ifdef GRPC_STREAM_REFCOUNT_DEBUG +void grpc_stream_unref(grpc_exec_ctx *exec_ctx, grpc_stream_refcount *refcount, + const char *reason) { + gpr_atm val = gpr_atm_no_barrier_load(&refcount->refs.count); + gpr_log(GPR_DEBUG, "%s %p:%p UNREF %d->%d %s", refcount->object_type, + refcount, refcount->destroy.cb_arg, val, val - 1, reason); +#else +void grpc_stream_unref(grpc_exec_ctx *exec_ctx, + grpc_stream_refcount *refcount) { +#endif + if (gpr_unref(&refcount->refs)) { + grpc_exec_ctx_enqueue(exec_ctx, &refcount->destroy, true, NULL); + } +} + +#ifdef GRPC_STREAM_REFCOUNT_DEBUG +void grpc_stream_ref_init(grpc_stream_refcount *refcount, int initial_refs, + grpc_iomgr_cb_func cb, void *cb_arg, + const char *object_type) { + refcount->object_type = object_type; +#else +void grpc_stream_ref_init(grpc_stream_refcount *refcount, int initial_refs, + grpc_iomgr_cb_func cb, void *cb_arg) { +#endif + gpr_ref_init(&refcount->refs, initial_refs); + grpc_closure_init(&refcount->destroy, cb, cb_arg); +} + +size_t grpc_transport_stream_size(grpc_transport *transport) { + return transport->vtable->sizeof_stream; +} + +void grpc_transport_destroy(grpc_exec_ctx *exec_ctx, + grpc_transport *transport) { + transport->vtable->destroy(exec_ctx, transport); +} + +int grpc_transport_init_stream(grpc_exec_ctx *exec_ctx, + grpc_transport *transport, grpc_stream *stream, + grpc_stream_refcount *refcount, + const void *server_data) { + return transport->vtable->init_stream(exec_ctx, transport, stream, refcount, + server_data); +} + +void grpc_transport_perform_stream_op(grpc_exec_ctx *exec_ctx, + grpc_transport *transport, + grpc_stream *stream, + grpc_transport_stream_op *op) { + transport->vtable->perform_stream_op(exec_ctx, transport, stream, op); +} + +void grpc_transport_perform_op(grpc_exec_ctx *exec_ctx, + grpc_transport *transport, + grpc_transport_op *op) { + transport->vtable->perform_op(exec_ctx, transport, op); +} + +void grpc_transport_set_pollset(grpc_exec_ctx *exec_ctx, + grpc_transport *transport, grpc_stream *stream, + grpc_pollset *pollset) { + transport->vtable->set_pollset(exec_ctx, transport, stream, pollset); +} + +void grpc_transport_destroy_stream(grpc_exec_ctx *exec_ctx, + grpc_transport *transport, + grpc_stream *stream) { + transport->vtable->destroy_stream(exec_ctx, transport, stream); +} + +char *grpc_transport_get_peer(grpc_exec_ctx *exec_ctx, + grpc_transport *transport) { + return transport->vtable->get_peer(exec_ctx, transport); +} + +void grpc_transport_stream_op_finish_with_failure( + grpc_exec_ctx *exec_ctx, grpc_transport_stream_op *op) { + grpc_exec_ctx_enqueue(exec_ctx, op->recv_message_ready, false, NULL); + grpc_exec_ctx_enqueue(exec_ctx, op->recv_initial_metadata_ready, false, NULL); + grpc_exec_ctx_enqueue(exec_ctx, op->on_complete, false, NULL); +} + +void grpc_transport_stream_op_add_cancellation(grpc_transport_stream_op *op, + grpc_status_code status) { + GPR_ASSERT(status != GRPC_STATUS_OK); + if (op->cancel_with_status == GRPC_STATUS_OK) { + op->cancel_with_status = status; + } + if (op->close_with_status != GRPC_STATUS_OK) { + op->close_with_status = GRPC_STATUS_OK; + if (op->optional_close_message != NULL) { + gpr_slice_unref(*op->optional_close_message); + op->optional_close_message = NULL; + } + } +} + +typedef struct { + gpr_slice message; + grpc_closure *then_call; + grpc_closure closure; +} close_message_data; + +static void free_message(grpc_exec_ctx *exec_ctx, void *p, bool iomgr_success) { + close_message_data *cmd = p; + gpr_slice_unref(cmd->message); + if (cmd->then_call != NULL) { + cmd->then_call->cb(exec_ctx, cmd->then_call->cb_arg, iomgr_success); + } + gpr_free(cmd); +} + +void grpc_transport_stream_op_add_close(grpc_transport_stream_op *op, + grpc_status_code status, + gpr_slice *optional_message) { + close_message_data *cmd; + GPR_ASSERT(status != GRPC_STATUS_OK); + if (op->cancel_with_status != GRPC_STATUS_OK || + op->close_with_status != GRPC_STATUS_OK) { + if (optional_message) { + gpr_slice_unref(*optional_message); + } + return; + } + if (optional_message) { + cmd = gpr_malloc(sizeof(*cmd)); + cmd->message = *optional_message; + cmd->then_call = op->on_complete; + grpc_closure_init(&cmd->closure, free_message, cmd); + op->on_complete = &cmd->closure; + op->optional_close_message = &cmd->message; + } + op->close_with_status = status; +} diff --git a/src/core/lib/transport/transport.h b/src/core/lib/transport/transport.h new file mode 100644 index 0000000000..e98cfe9515 --- /dev/null +++ b/src/core/lib/transport/transport.h @@ -0,0 +1,242 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_TRANSPORT_H +#define GRPC_CORE_LIB_TRANSPORT_TRANSPORT_H + +#include <stddef.h> + +#include "src/core/lib/channel/context.h" +#include "src/core/lib/iomgr/pollset.h" +#include "src/core/lib/iomgr/pollset_set.h" +#include "src/core/lib/transport/byte_stream.h" +#include "src/core/lib/transport/metadata_batch.h" + +/* forward declarations */ +typedef struct grpc_transport grpc_transport; + +/* grpc_stream doesn't actually exist. It's used as a typesafe + opaque pointer for whatever data the transport wants to track + for a stream. */ +typedef struct grpc_stream grpc_stream; + +/*#define GRPC_STREAM_REFCOUNT_DEBUG*/ + +typedef struct grpc_stream_refcount { + gpr_refcount refs; + grpc_closure destroy; +#ifdef GRPC_STREAM_REFCOUNT_DEBUG + const char *object_type; +#endif +} grpc_stream_refcount; + +#ifdef GRPC_STREAM_REFCOUNT_DEBUG +void grpc_stream_ref_init(grpc_stream_refcount *refcount, int initial_refs, + grpc_iomgr_cb_func cb, void *cb_arg, + const char *object_type); +void grpc_stream_ref(grpc_stream_refcount *refcount, const char *reason); +void grpc_stream_unref(grpc_exec_ctx *exec_ctx, grpc_stream_refcount *refcount, + const char *reason); +#define GRPC_STREAM_REF_INIT(rc, ir, cb, cb_arg, objtype) \ + grpc_stream_ref_init(rc, ir, cb, cb_arg, objtype) +#else +void grpc_stream_ref_init(grpc_stream_refcount *refcount, int initial_refs, + grpc_iomgr_cb_func cb, void *cb_arg); +void grpc_stream_ref(grpc_stream_refcount *refcount); +void grpc_stream_unref(grpc_exec_ctx *exec_ctx, grpc_stream_refcount *refcount); +#define GRPC_STREAM_REF_INIT(rc, ir, cb, cb_arg, objtype) \ + grpc_stream_ref_init(rc, ir, cb, cb_arg) +#endif + +/* Transport stream op: a set of operations to perform on a transport + against a single stream */ +typedef struct grpc_transport_stream_op { + /** Send initial metadata to the peer, from the provided metadata batch. */ + grpc_metadata_batch *send_initial_metadata; + + /** Send trailing metadata to the peer, from the provided metadata batch. */ + grpc_metadata_batch *send_trailing_metadata; + + /** Send message data to the peer, from the provided byte stream. */ + grpc_byte_stream *send_message; + + /** Receive initial metadata from the stream, into provided metadata batch. */ + grpc_metadata_batch *recv_initial_metadata; + /** Should be enqueued when initial metadata is ready to be processed. */ + grpc_closure *recv_initial_metadata_ready; + + /** Receive message data from the stream, into provided byte stream. */ + grpc_byte_stream **recv_message; + /** Should be enqueued when one message is ready to be processed. */ + grpc_closure *recv_message_ready; + + /** Receive trailing metadata from the stream, into provided metadata batch. + */ + grpc_metadata_batch *recv_trailing_metadata; + + /** Should be enqueued when all requested operations (excluding recv_message + and recv_initial_metadata which have their own closures) in a given batch + have been completed. */ + grpc_closure *on_complete; + + /** If != GRPC_STATUS_OK, cancel this stream */ + grpc_status_code cancel_with_status; + + /** If != GRPC_STATUS_OK, send grpc-status, grpc-message, and close this + stream for both reading and writing */ + grpc_status_code close_with_status; + gpr_slice *optional_close_message; + + /* Indexes correspond to grpc_context_index enum values */ + grpc_call_context_element *context; +} grpc_transport_stream_op; + +/** Transport op: a set of operations to perform on a transport as a whole */ +typedef struct grpc_transport_op { + /** Called when processing of this op is done. */ + grpc_closure *on_consumed; + /** connectivity monitoring - set connectivity_state to NULL to unsubscribe */ + grpc_closure *on_connectivity_state_change; + grpc_connectivity_state *connectivity_state; + /** should the transport be disconnected */ + int disconnect; + /** should we send a goaway? + after a goaway is sent, once there are no more active calls on + the transport, the transport should disconnect */ + int send_goaway; + /** what should the goaway contain? */ + grpc_status_code goaway_status; + gpr_slice *goaway_message; + /** set the callback for accepting new streams; + this is a permanent callback, unlike the other one-shot closures. + If true, the callback is set to set_accept_stream_fn, with its + user_data argument set to set_accept_stream_user_data */ + bool set_accept_stream; + void (*set_accept_stream_fn)(grpc_exec_ctx *exec_ctx, void *user_data, + grpc_transport *transport, + const void *server_data); + void *set_accept_stream_user_data; + /** add this transport to a pollset */ + grpc_pollset *bind_pollset; + /** add this transport to a pollset_set */ + grpc_pollset_set *bind_pollset_set; + /** send a ping, call this back if not NULL */ + grpc_closure *send_ping; +} grpc_transport_op; + +/* Returns the amount of memory required to store a grpc_stream for this + transport */ +size_t grpc_transport_stream_size(grpc_transport *transport); + +/* Initialize transport data for a stream. + + Returns 0 on success, any other (transport-defined) value for failure. + + Arguments: + transport - the transport on which to create this stream + stream - a pointer to uninitialized memory to initialize + server_data - either NULL for a client initiated stream, or a pointer + supplied from the accept_stream callback function */ +int grpc_transport_init_stream(grpc_exec_ctx *exec_ctx, + grpc_transport *transport, grpc_stream *stream, + grpc_stream_refcount *refcount, + const void *server_data); + +void grpc_transport_set_pollset(grpc_exec_ctx *exec_ctx, + grpc_transport *transport, grpc_stream *stream, + grpc_pollset *pollset); + +/* Destroy transport data for a stream. + + Requires: a recv_batch with final_state == GRPC_STREAM_CLOSED has been + received by the up-layer. Must not be called in the same call stack as + recv_frame. + + Arguments: + transport - the transport on which to create this stream + stream - the grpc_stream to destroy (memory is still owned by the + caller, but any child memory must be cleaned up) */ +void grpc_transport_destroy_stream(grpc_exec_ctx *exec_ctx, + grpc_transport *transport, + grpc_stream *stream); + +void grpc_transport_stream_op_finish_with_failure(grpc_exec_ctx *exec_ctx, + grpc_transport_stream_op *op); + +void grpc_transport_stream_op_add_cancellation(grpc_transport_stream_op *op, + grpc_status_code status); + +void grpc_transport_stream_op_add_close(grpc_transport_stream_op *op, + grpc_status_code status, + gpr_slice *optional_message); + +char *grpc_transport_stream_op_string(grpc_transport_stream_op *op); + +/* Send a batch of operations on a transport + + Takes ownership of any objects contained in ops. + + Arguments: + transport - the transport on which to initiate the stream + stream - the stream on which to send the operations. This must be + non-NULL and previously initialized by the same transport. + op - a grpc_transport_stream_op specifying the op to perform */ +void grpc_transport_perform_stream_op(grpc_exec_ctx *exec_ctx, + grpc_transport *transport, + grpc_stream *stream, + grpc_transport_stream_op *op); + +void grpc_transport_perform_op(grpc_exec_ctx *exec_ctx, + grpc_transport *transport, + grpc_transport_op *op); + +/* Send a ping on a transport + + Calls cb with user data when a response is received. */ +void grpc_transport_ping(grpc_transport *transport, grpc_closure *cb); + +/* Advise peer of pending connection termination. */ +void grpc_transport_goaway(grpc_transport *transport, grpc_status_code status, + gpr_slice debug_data); + +/* Close a transport. Aborts all open streams. */ +void grpc_transport_close(grpc_transport *transport); + +/* Destroy the transport */ +void grpc_transport_destroy(grpc_exec_ctx *exec_ctx, grpc_transport *transport); + +/* Get the transports peer */ +char *grpc_transport_get_peer(grpc_exec_ctx *exec_ctx, + grpc_transport *transport); + +#endif /* GRPC_CORE_LIB_TRANSPORT_TRANSPORT_H */ diff --git a/src/core/lib/transport/transport_impl.h b/src/core/lib/transport/transport_impl.h new file mode 100644 index 0000000000..92fa5d519d --- /dev/null +++ b/src/core/lib/transport/transport_impl.h @@ -0,0 +1,81 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TRANSPORT_TRANSPORT_IMPL_H +#define GRPC_CORE_LIB_TRANSPORT_TRANSPORT_IMPL_H + +#include "src/core/lib/transport/transport.h" + +typedef struct grpc_transport_vtable { + /* Memory required for a single stream element - this is allocated by upper + layers and initialized by the transport */ + size_t sizeof_stream; /* = sizeof(transport stream) */ + + /* name of this transport implementation */ + const char *name; + + /* implementation of grpc_transport_init_stream */ + int (*init_stream)(grpc_exec_ctx *exec_ctx, grpc_transport *self, + grpc_stream *stream, grpc_stream_refcount *refcount, + const void *server_data); + + /* implementation of grpc_transport_set_pollset */ + void (*set_pollset)(grpc_exec_ctx *exec_ctx, grpc_transport *self, + grpc_stream *stream, grpc_pollset *pollset); + + /* implementation of grpc_transport_perform_stream_op */ + void (*perform_stream_op)(grpc_exec_ctx *exec_ctx, grpc_transport *self, + grpc_stream *stream, grpc_transport_stream_op *op); + + /* implementation of grpc_transport_perform_op */ + void (*perform_op)(grpc_exec_ctx *exec_ctx, grpc_transport *self, + grpc_transport_op *op); + + /* implementation of grpc_transport_destroy_stream */ + void (*destroy_stream)(grpc_exec_ctx *exec_ctx, grpc_transport *self, + grpc_stream *stream); + + /* implementation of grpc_transport_destroy */ + void (*destroy)(grpc_exec_ctx *exec_ctx, grpc_transport *self); + + /* implementation of grpc_transport_get_peer */ + char *(*get_peer)(grpc_exec_ctx *exec_ctx, grpc_transport *self); +} grpc_transport_vtable; + +/* an instance of a grpc transport */ +struct grpc_transport { + /* pointer to a vtable defining operations on this transport */ + const grpc_transport_vtable *vtable; +}; + +#endif /* GRPC_CORE_LIB_TRANSPORT_TRANSPORT_IMPL_H */ diff --git a/src/core/lib/transport/transport_op_string.c b/src/core/lib/transport/transport_op_string.c new file mode 100644 index 0000000000..1fa8fa5d4f --- /dev/null +++ b/src/core/lib/transport/transport_op_string.c @@ -0,0 +1,140 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/channel/channel_stack.h" + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/string_util.h> +#include <grpc/support/useful.h> +#include "src/core/lib/support/string.h" + +/* These routines are here to facilitate debugging - they produce string + representations of various transport data structures */ + +static void put_metadata(gpr_strvec *b, grpc_mdelem *md) { + gpr_strvec_add(b, gpr_strdup("key=")); + gpr_strvec_add(b, + gpr_dump_slice(md->key->slice, GPR_DUMP_HEX | GPR_DUMP_ASCII)); + + gpr_strvec_add(b, gpr_strdup(" value=")); + gpr_strvec_add( + b, gpr_dump_slice(md->value->slice, GPR_DUMP_HEX | GPR_DUMP_ASCII)); +} + +static void put_metadata_list(gpr_strvec *b, grpc_metadata_batch md) { + grpc_linked_mdelem *m; + for (m = md.list.head; m != NULL; m = m->next) { + if (m != md.list.head) gpr_strvec_add(b, gpr_strdup(", ")); + put_metadata(b, m->md); + } + if (gpr_time_cmp(md.deadline, gpr_inf_future(md.deadline.clock_type)) != 0) { + char *tmp; + gpr_asprintf(&tmp, " deadline=%lld.%09d", (long long)md.deadline.tv_sec, + (int)md.deadline.tv_nsec); + gpr_strvec_add(b, tmp); + } +} + +char *grpc_transport_stream_op_string(grpc_transport_stream_op *op) { + char *tmp; + char *out; + int first = 1; + + gpr_strvec b; + gpr_strvec_init(&b); + + if (op->send_initial_metadata != NULL) { + if (!first) gpr_strvec_add(&b, gpr_strdup(" ")); + first = 0; + gpr_strvec_add(&b, gpr_strdup("SEND_INITIAL_METADATA{")); + put_metadata_list(&b, *op->send_initial_metadata); + gpr_strvec_add(&b, gpr_strdup("}")); + } + + if (op->send_message != NULL) { + if (!first) gpr_strvec_add(&b, gpr_strdup(" ")); + first = 0; + gpr_asprintf(&tmp, "SEND_MESSAGE:flags=0x%08x:len=%d", + op->send_message->flags, op->send_message->length); + gpr_strvec_add(&b, tmp); + } + + if (op->send_trailing_metadata != NULL) { + if (!first) gpr_strvec_add(&b, gpr_strdup(" ")); + first = 0; + gpr_strvec_add(&b, gpr_strdup("SEND_TRAILING_METADATA{")); + put_metadata_list(&b, *op->send_trailing_metadata); + gpr_strvec_add(&b, gpr_strdup("}")); + } + + if (op->recv_initial_metadata != NULL) { + if (!first) gpr_strvec_add(&b, gpr_strdup(" ")); + first = 0; + gpr_strvec_add(&b, gpr_strdup("RECV_INITIAL_METADATA")); + } + + if (op->recv_message != NULL) { + if (!first) gpr_strvec_add(&b, gpr_strdup(" ")); + first = 0; + gpr_strvec_add(&b, gpr_strdup("RECV_MESSAGE")); + } + + if (op->recv_trailing_metadata != NULL) { + if (!first) gpr_strvec_add(&b, gpr_strdup(" ")); + first = 0; + gpr_strvec_add(&b, gpr_strdup("RECV_TRAILING_METADATA")); + } + + if (op->cancel_with_status != GRPC_STATUS_OK) { + if (!first) gpr_strvec_add(&b, gpr_strdup(" ")); + first = 0; + gpr_asprintf(&tmp, "CANCEL:%d", op->cancel_with_status); + gpr_strvec_add(&b, tmp); + } + + out = gpr_strvec_flatten(&b, NULL); + gpr_strvec_destroy(&b); + + return out; +} + +void grpc_call_log_op(char *file, int line, gpr_log_severity severity, + grpc_call_element *elem, grpc_transport_stream_op *op) { + char *str = grpc_transport_stream_op_string(op); + gpr_log(file, line, severity, "OP[%s:%p]: %s", elem->filter->name, elem, str); + gpr_free(str); +} diff --git a/src/core/lib/tsi/fake_transport_security.c b/src/core/lib/tsi/fake_transport_security.c new file mode 100644 index 0000000000..4b812f4803 --- /dev/null +++ b/src/core/lib/tsi/fake_transport_security.c @@ -0,0 +1,527 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/tsi/fake_transport_security.h" + +#include <stdlib.h> +#include <string.h> + +#include <grpc/support/log.h> +#include <grpc/support/port_platform.h> +#include <grpc/support/useful.h> +#include "src/core/lib/tsi/transport_security.h" + +/* --- Constants. ---*/ +#define TSI_FAKE_FRAME_HEADER_SIZE 4 +#define TSI_FAKE_FRAME_INITIAL_ALLOCATED_SIZE 64 +#define TSI_FAKE_DEFAULT_FRAME_SIZE 16384 + +/* --- Structure definitions. ---*/ + +/* a frame is encoded like this: + | size | data | + where the size field value is the size of the size field plus the size of + the data encoded in little endian on 4 bytes. */ +typedef struct { + unsigned char *data; + size_t size; + size_t allocated_size; + size_t offset; + int needs_draining; +} tsi_fake_frame; + +typedef enum { + TSI_FAKE_CLIENT_INIT = 0, + TSI_FAKE_SERVER_INIT = 1, + TSI_FAKE_CLIENT_FINISHED = 2, + TSI_FAKE_SERVER_FINISHED = 3, + TSI_FAKE_HANDSHAKE_MESSAGE_MAX = 4 +} tsi_fake_handshake_message; + +typedef struct { + tsi_handshaker base; + int is_client; + tsi_fake_handshake_message next_message_to_send; + int needs_incoming_message; + tsi_fake_frame incoming; + tsi_fake_frame outgoing; + tsi_result result; +} tsi_fake_handshaker; + +typedef struct { + tsi_frame_protector base; + tsi_fake_frame protect_frame; + tsi_fake_frame unprotect_frame; + size_t max_frame_size; +} tsi_fake_frame_protector; + +/* --- Utils. ---*/ + +static const char *tsi_fake_handshake_message_strings[] = { + "CLIENT_INIT", "SERVER_INIT", "CLIENT_FINISHED", "SERVER_FINISHED"}; + +static const char *tsi_fake_handshake_message_to_string(int msg) { + if (msg < 0 || msg >= TSI_FAKE_HANDSHAKE_MESSAGE_MAX) { + gpr_log(GPR_ERROR, "Invalid message %d", msg); + return "UNKNOWN"; + } + return tsi_fake_handshake_message_strings[msg]; +} + +static tsi_result tsi_fake_handshake_message_from_string( + const char *msg_string, tsi_fake_handshake_message *msg) { + tsi_fake_handshake_message i; + for (i = 0; i < TSI_FAKE_HANDSHAKE_MESSAGE_MAX; i++) { + if (strncmp(msg_string, tsi_fake_handshake_message_strings[i], + strlen(tsi_fake_handshake_message_strings[i])) == 0) { + *msg = i; + return TSI_OK; + } + } + gpr_log(GPR_ERROR, "Invalid handshake message."); + return TSI_DATA_CORRUPTED; +} + +static uint32_t load32_little_endian(const unsigned char *buf) { + return ((uint32_t)(buf[0]) | (uint32_t)(buf[1] << 8) | + (uint32_t)(buf[2] << 16) | (uint32_t)(buf[3] << 24)); +} + +static void store32_little_endian(uint32_t value, unsigned char *buf) { + buf[3] = (unsigned char)((value >> 24) & 0xFF); + buf[2] = (unsigned char)((value >> 16) & 0xFF); + buf[1] = (unsigned char)((value >> 8) & 0xFF); + buf[0] = (unsigned char)((value)&0xFF); +} + +static void tsi_fake_frame_reset(tsi_fake_frame *frame, int needs_draining) { + frame->offset = 0; + frame->needs_draining = needs_draining; + if (!needs_draining) frame->size = 0; +} + +/* Returns 1 if successful, 0 otherwise. */ +static int tsi_fake_frame_ensure_size(tsi_fake_frame *frame) { + if (frame->data == NULL) { + frame->allocated_size = frame->size; + frame->data = malloc(frame->allocated_size); + if (frame->data == NULL) return 0; + } else if (frame->size > frame->allocated_size) { + unsigned char *new_data = realloc(frame->data, frame->size); + if (new_data == NULL) { + free(frame->data); + frame->data = NULL; + return 0; + } + frame->data = new_data; + frame->allocated_size = frame->size; + } + return 1; +} + +/* This method should not be called if frame->needs_framing is not 0. */ +static tsi_result fill_frame_from_bytes(const unsigned char *incoming_bytes, + size_t *incoming_bytes_size, + tsi_fake_frame *frame) { + size_t available_size = *incoming_bytes_size; + size_t to_read_size = 0; + const unsigned char *bytes_cursor = incoming_bytes; + + if (frame->needs_draining) return TSI_INTERNAL_ERROR; + if (frame->data == NULL) { + frame->allocated_size = TSI_FAKE_FRAME_INITIAL_ALLOCATED_SIZE; + frame->data = malloc(frame->allocated_size); + if (frame->data == NULL) return TSI_OUT_OF_RESOURCES; + } + + if (frame->offset < TSI_FAKE_FRAME_HEADER_SIZE) { + to_read_size = TSI_FAKE_FRAME_HEADER_SIZE - frame->offset; + if (to_read_size > available_size) { + /* Just fill what we can and exit. */ + memcpy(frame->data + frame->offset, bytes_cursor, available_size); + bytes_cursor += available_size; + frame->offset += available_size; + *incoming_bytes_size = (size_t)(bytes_cursor - incoming_bytes); + return TSI_INCOMPLETE_DATA; + } + memcpy(frame->data + frame->offset, bytes_cursor, to_read_size); + bytes_cursor += to_read_size; + frame->offset += to_read_size; + available_size -= to_read_size; + frame->size = load32_little_endian(frame->data); + if (!tsi_fake_frame_ensure_size(frame)) return TSI_OUT_OF_RESOURCES; + } + + to_read_size = frame->size - frame->offset; + if (to_read_size > available_size) { + memcpy(frame->data + frame->offset, bytes_cursor, available_size); + frame->offset += available_size; + bytes_cursor += available_size; + *incoming_bytes_size = (size_t)(bytes_cursor - incoming_bytes); + return TSI_INCOMPLETE_DATA; + } + memcpy(frame->data + frame->offset, bytes_cursor, to_read_size); + bytes_cursor += to_read_size; + *incoming_bytes_size = (size_t)(bytes_cursor - incoming_bytes); + tsi_fake_frame_reset(frame, 1 /* needs_draining */); + return TSI_OK; +} + +/* This method should not be called if frame->needs_framing is 0. */ +static tsi_result drain_frame_to_bytes(unsigned char *outgoing_bytes, + size_t *outgoing_bytes_size, + tsi_fake_frame *frame) { + size_t to_write_size = frame->size - frame->offset; + if (!frame->needs_draining) return TSI_INTERNAL_ERROR; + if (*outgoing_bytes_size < to_write_size) { + memcpy(outgoing_bytes, frame->data + frame->offset, *outgoing_bytes_size); + frame->offset += *outgoing_bytes_size; + return TSI_INCOMPLETE_DATA; + } + memcpy(outgoing_bytes, frame->data + frame->offset, to_write_size); + *outgoing_bytes_size = to_write_size; + tsi_fake_frame_reset(frame, 0 /* needs_draining */); + return TSI_OK; +} + +static tsi_result bytes_to_frame(unsigned char *bytes, size_t bytes_size, + tsi_fake_frame *frame) { + frame->offset = 0; + frame->size = bytes_size + TSI_FAKE_FRAME_HEADER_SIZE; + if (!tsi_fake_frame_ensure_size(frame)) return TSI_OUT_OF_RESOURCES; + store32_little_endian((uint32_t)frame->size, frame->data); + memcpy(frame->data + TSI_FAKE_FRAME_HEADER_SIZE, bytes, bytes_size); + tsi_fake_frame_reset(frame, 1 /* needs draining */); + return TSI_OK; +} + +static void tsi_fake_frame_destruct(tsi_fake_frame *frame) { + if (frame->data != NULL) free(frame->data); +} + +/* --- tsi_frame_protector methods implementation. ---*/ + +static tsi_result fake_protector_protect(tsi_frame_protector *self, + const unsigned char *unprotected_bytes, + size_t *unprotected_bytes_size, + unsigned char *protected_output_frames, + size_t *protected_output_frames_size) { + tsi_result result = TSI_OK; + tsi_fake_frame_protector *impl = (tsi_fake_frame_protector *)self; + unsigned char frame_header[TSI_FAKE_FRAME_HEADER_SIZE]; + tsi_fake_frame *frame = &impl->protect_frame; + size_t saved_output_size = *protected_output_frames_size; + size_t drained_size = 0; + size_t *num_bytes_written = protected_output_frames_size; + *num_bytes_written = 0; + + /* Try to drain first. */ + if (frame->needs_draining) { + drained_size = saved_output_size - *num_bytes_written; + result = + drain_frame_to_bytes(protected_output_frames, &drained_size, frame); + *num_bytes_written += drained_size; + protected_output_frames += drained_size; + if (result != TSI_OK) { + if (result == TSI_INCOMPLETE_DATA) { + *unprotected_bytes_size = 0; + result = TSI_OK; + } + return result; + } + } + + /* Now process the unprotected_bytes. */ + if (frame->needs_draining) return TSI_INTERNAL_ERROR; + if (frame->size == 0) { + /* New frame, create a header. */ + size_t written_in_frame_size = 0; + store32_little_endian((uint32_t)impl->max_frame_size, frame_header); + written_in_frame_size = TSI_FAKE_FRAME_HEADER_SIZE; + result = fill_frame_from_bytes(frame_header, &written_in_frame_size, frame); + if (result != TSI_INCOMPLETE_DATA) { + gpr_log(GPR_ERROR, "fill_frame_from_bytes returned %s", + tsi_result_to_string(result)); + return result; + } + } + result = + fill_frame_from_bytes(unprotected_bytes, unprotected_bytes_size, frame); + if (result != TSI_OK) { + if (result == TSI_INCOMPLETE_DATA) result = TSI_OK; + return result; + } + + /* Try to drain again. */ + if (!frame->needs_draining) return TSI_INTERNAL_ERROR; + if (frame->offset != 0) return TSI_INTERNAL_ERROR; + drained_size = saved_output_size - *num_bytes_written; + result = drain_frame_to_bytes(protected_output_frames, &drained_size, frame); + *num_bytes_written += drained_size; + if (result == TSI_INCOMPLETE_DATA) result = TSI_OK; + return result; +} + +static tsi_result fake_protector_protect_flush( + tsi_frame_protector *self, unsigned char *protected_output_frames, + size_t *protected_output_frames_size, size_t *still_pending_size) { + tsi_result result = TSI_OK; + tsi_fake_frame_protector *impl = (tsi_fake_frame_protector *)self; + tsi_fake_frame *frame = &impl->protect_frame; + if (!frame->needs_draining) { + /* Create a short frame. */ + frame->size = frame->offset; + frame->offset = 0; + frame->needs_draining = 1; + store32_little_endian((uint32_t)frame->size, + frame->data); /* Overwrite header. */ + } + result = drain_frame_to_bytes(protected_output_frames, + protected_output_frames_size, frame); + if (result == TSI_INCOMPLETE_DATA) result = TSI_OK; + *still_pending_size = frame->size - frame->offset; + return result; +} + +static tsi_result fake_protector_unprotect( + tsi_frame_protector *self, const unsigned char *protected_frames_bytes, + size_t *protected_frames_bytes_size, unsigned char *unprotected_bytes, + size_t *unprotected_bytes_size) { + tsi_result result = TSI_OK; + tsi_fake_frame_protector *impl = (tsi_fake_frame_protector *)self; + tsi_fake_frame *frame = &impl->unprotect_frame; + size_t saved_output_size = *unprotected_bytes_size; + size_t drained_size = 0; + size_t *num_bytes_written = unprotected_bytes_size; + *num_bytes_written = 0; + + /* Try to drain first. */ + if (frame->needs_draining) { + /* Go past the header if needed. */ + if (frame->offset == 0) frame->offset = TSI_FAKE_FRAME_HEADER_SIZE; + drained_size = saved_output_size - *num_bytes_written; + result = drain_frame_to_bytes(unprotected_bytes, &drained_size, frame); + unprotected_bytes += drained_size; + *num_bytes_written += drained_size; + if (result != TSI_OK) { + if (result == TSI_INCOMPLETE_DATA) { + *protected_frames_bytes_size = 0; + result = TSI_OK; + } + return result; + } + } + + /* Now process the protected_bytes. */ + if (frame->needs_draining) return TSI_INTERNAL_ERROR; + result = fill_frame_from_bytes(protected_frames_bytes, + protected_frames_bytes_size, frame); + if (result != TSI_OK) { + if (result == TSI_INCOMPLETE_DATA) result = TSI_OK; + return result; + } + + /* Try to drain again. */ + if (!frame->needs_draining) return TSI_INTERNAL_ERROR; + if (frame->offset != 0) return TSI_INTERNAL_ERROR; + frame->offset = TSI_FAKE_FRAME_HEADER_SIZE; /* Go past the header. */ + drained_size = saved_output_size - *num_bytes_written; + result = drain_frame_to_bytes(unprotected_bytes, &drained_size, frame); + *num_bytes_written += drained_size; + if (result == TSI_INCOMPLETE_DATA) result = TSI_OK; + return result; +} + +static void fake_protector_destroy(tsi_frame_protector *self) { + tsi_fake_frame_protector *impl = (tsi_fake_frame_protector *)self; + tsi_fake_frame_destruct(&impl->protect_frame); + tsi_fake_frame_destruct(&impl->unprotect_frame); + free(self); +} + +static const tsi_frame_protector_vtable frame_protector_vtable = { + fake_protector_protect, fake_protector_protect_flush, + fake_protector_unprotect, fake_protector_destroy, +}; + +/* --- tsi_handshaker methods implementation. ---*/ + +static tsi_result fake_handshaker_get_bytes_to_send_to_peer( + tsi_handshaker *self, unsigned char *bytes, size_t *bytes_size) { + tsi_fake_handshaker *impl = (tsi_fake_handshaker *)self; + tsi_result result = TSI_OK; + if (impl->needs_incoming_message || impl->result == TSI_OK) { + *bytes_size = 0; + return TSI_OK; + } + if (!impl->outgoing.needs_draining) { + tsi_fake_handshake_message next_message_to_send = + impl->next_message_to_send + 2; + const char *msg_string = + tsi_fake_handshake_message_to_string(impl->next_message_to_send); + result = bytes_to_frame((unsigned char *)msg_string, strlen(msg_string), + &impl->outgoing); + if (result != TSI_OK) return result; + if (next_message_to_send > TSI_FAKE_HANDSHAKE_MESSAGE_MAX) { + next_message_to_send = TSI_FAKE_HANDSHAKE_MESSAGE_MAX; + } + if (tsi_tracing_enabled) { + gpr_log(GPR_INFO, "%s prepared %s.", + impl->is_client ? "Client" : "Server", + tsi_fake_handshake_message_to_string(impl->next_message_to_send)); + } + impl->next_message_to_send = next_message_to_send; + } + result = drain_frame_to_bytes(bytes, bytes_size, &impl->outgoing); + if (result != TSI_OK) return result; + if (!impl->is_client && + impl->next_message_to_send == TSI_FAKE_HANDSHAKE_MESSAGE_MAX) { + /* We're done. */ + if (tsi_tracing_enabled) { + gpr_log(GPR_INFO, "Server is done."); + } + impl->result = TSI_OK; + } else { + impl->needs_incoming_message = 1; + } + return TSI_OK; +} + +static tsi_result fake_handshaker_process_bytes_from_peer( + tsi_handshaker *self, const unsigned char *bytes, size_t *bytes_size) { + tsi_result result = TSI_OK; + tsi_fake_handshaker *impl = (tsi_fake_handshaker *)self; + tsi_fake_handshake_message expected_msg = impl->next_message_to_send - 1; + tsi_fake_handshake_message received_msg; + + if (!impl->needs_incoming_message || impl->result == TSI_OK) { + *bytes_size = 0; + return TSI_OK; + } + result = fill_frame_from_bytes(bytes, bytes_size, &impl->incoming); + if (result != TSI_OK) return result; + + /* We now have a complete frame. */ + result = tsi_fake_handshake_message_from_string( + (const char *)impl->incoming.data + TSI_FAKE_FRAME_HEADER_SIZE, + &received_msg); + if (result != TSI_OK) { + impl->result = result; + return result; + } + if (received_msg != expected_msg) { + gpr_log(GPR_ERROR, "Invalid received message (%s instead of %s)", + tsi_fake_handshake_message_to_string(received_msg), + tsi_fake_handshake_message_to_string(expected_msg)); + } + if (tsi_tracing_enabled) { + gpr_log(GPR_INFO, "%s received %s.", impl->is_client ? "Client" : "Server", + tsi_fake_handshake_message_to_string(received_msg)); + } + tsi_fake_frame_reset(&impl->incoming, 0 /* needs_draining */); + impl->needs_incoming_message = 0; + if (impl->next_message_to_send == TSI_FAKE_HANDSHAKE_MESSAGE_MAX) { + /* We're done. */ + if (tsi_tracing_enabled) { + gpr_log(GPR_INFO, "%s is done.", impl->is_client ? "Client" : "Server"); + } + impl->result = TSI_OK; + } + return TSI_OK; +} + +static tsi_result fake_handshaker_get_result(tsi_handshaker *self) { + tsi_fake_handshaker *impl = (tsi_fake_handshaker *)self; + return impl->result; +} + +static tsi_result fake_handshaker_extract_peer(tsi_handshaker *self, + tsi_peer *peer) { + tsi_result result = tsi_construct_peer(1, peer); + if (result != TSI_OK) return result; + result = tsi_construct_string_peer_property_from_cstring( + TSI_CERTIFICATE_TYPE_PEER_PROPERTY, TSI_FAKE_CERTIFICATE_TYPE, + &peer->properties[0]); + if (result != TSI_OK) tsi_peer_destruct(peer); + return result; +} + +static tsi_result fake_handshaker_create_frame_protector( + tsi_handshaker *self, size_t *max_protected_frame_size, + tsi_frame_protector **protector) { + *protector = tsi_create_fake_protector(max_protected_frame_size); + if (*protector == NULL) return TSI_OUT_OF_RESOURCES; + return TSI_OK; +} + +static void fake_handshaker_destroy(tsi_handshaker *self) { + tsi_fake_handshaker *impl = (tsi_fake_handshaker *)self; + tsi_fake_frame_destruct(&impl->incoming); + tsi_fake_frame_destruct(&impl->outgoing); + free(self); +} + +static const tsi_handshaker_vtable handshaker_vtable = { + fake_handshaker_get_bytes_to_send_to_peer, + fake_handshaker_process_bytes_from_peer, + fake_handshaker_get_result, + fake_handshaker_extract_peer, + fake_handshaker_create_frame_protector, + fake_handshaker_destroy, +}; + +tsi_handshaker *tsi_create_fake_handshaker(int is_client) { + tsi_fake_handshaker *impl = calloc(1, sizeof(tsi_fake_handshaker)); + impl->base.vtable = &handshaker_vtable; + impl->is_client = is_client; + impl->result = TSI_HANDSHAKE_IN_PROGRESS; + if (is_client) { + impl->needs_incoming_message = 0; + impl->next_message_to_send = TSI_FAKE_CLIENT_INIT; + } else { + impl->needs_incoming_message = 1; + impl->next_message_to_send = TSI_FAKE_SERVER_INIT; + } + return &impl->base; +} + +tsi_frame_protector *tsi_create_fake_protector( + size_t *max_protected_frame_size) { + tsi_fake_frame_protector *impl = calloc(1, sizeof(tsi_fake_frame_protector)); + if (impl == NULL) return NULL; + impl->max_frame_size = (max_protected_frame_size == NULL) + ? TSI_FAKE_DEFAULT_FRAME_SIZE + : *max_protected_frame_size; + impl->base.vtable = &frame_protector_vtable; + return &impl->base; +} diff --git a/src/core/lib/tsi/fake_transport_security.h b/src/core/lib/tsi/fake_transport_security.h new file mode 100644 index 0000000000..b887dfcb09 --- /dev/null +++ b/src/core/lib/tsi/fake_transport_security.h @@ -0,0 +1,61 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TSI_FAKE_TRANSPORT_SECURITY_H +#define GRPC_CORE_LIB_TSI_FAKE_TRANSPORT_SECURITY_H + +#include "src/core/lib/tsi/transport_security_interface.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Value for the TSI_CERTIFICATE_TYPE_PEER_PROPERTY property for FAKE certs. */ +#define TSI_FAKE_CERTIFICATE_TYPE "FAKE" + +/* Creates a fake handshaker that will create a fake frame protector. + + No cryptography is performed in these objects. They just simulate handshake + messages going back and forth for the handshaker and do some framing on + cleartext data for the protector. */ +tsi_handshaker *tsi_create_fake_handshaker(int is_client); + +/* Creates a protector directly without going through the handshake phase. */ +tsi_frame_protector *tsi_create_fake_protector( + size_t *max_protected_frame_size); + +#ifdef __cplusplus +} +#endif + +#endif /* GRPC_CORE_LIB_TSI_FAKE_TRANSPORT_SECURITY_H */ diff --git a/src/core/lib/tsi/ssl_transport_security.c b/src/core/lib/tsi/ssl_transport_security.c new file mode 100644 index 0000000000..d03201eec6 --- /dev/null +++ b/src/core/lib/tsi/ssl_transport_security.c @@ -0,0 +1,1536 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/tsi/ssl_transport_security.h" + +#include <grpc/support/port_platform.h> + +#include <limits.h> +#include <string.h> + +/* TODO(jboeuf): refactor inet_ntop into a portability header. */ +#ifdef GPR_WINSOCK_SOCKET +#include <ws2tcpip.h> +#else +#include <arpa/inet.h> +#endif + +#include <grpc/support/log.h> +#include <grpc/support/sync.h> +#include <grpc/support/thd.h> +#include <grpc/support/useful.h> + +#include <openssl/bio.h> +#include <openssl/crypto.h> /* For OPENSSL_free */ +#include <openssl/err.h> +#include <openssl/ssl.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> + +#include "src/core/lib/tsi/ssl_types.h" +#include "src/core/lib/tsi/transport_security.h" + +/* --- Constants. ---*/ + +#define TSI_SSL_MAX_PROTECTED_FRAME_SIZE_UPPER_BOUND 16384 +#define TSI_SSL_MAX_PROTECTED_FRAME_SIZE_LOWER_BOUND 1024 + +/* Putting a macro like this and littering the source file with #if is really + bad practice. + TODO(jboeuf): refactor all the #if / #endif in a separate module. */ +#ifndef TSI_OPENSSL_ALPN_SUPPORT +#define TSI_OPENSSL_ALPN_SUPPORT 1 +#endif + +/* TODO(jboeuf): I have not found a way to get this number dynamically from the + SSL structure. This is what we would ultimately want though... */ +#define TSI_SSL_MAX_PROTECTION_OVERHEAD 100 + +/* --- Structure definitions. ---*/ + +struct tsi_ssl_handshaker_factory { + tsi_result (*create_handshaker)(tsi_ssl_handshaker_factory *self, + const char *server_name_indication, + tsi_handshaker **handshaker); + void (*destroy)(tsi_ssl_handshaker_factory *self); +}; + +typedef struct { + tsi_ssl_handshaker_factory base; + SSL_CTX *ssl_context; + unsigned char *alpn_protocol_list; + size_t alpn_protocol_list_length; +} tsi_ssl_client_handshaker_factory; + +typedef struct { + tsi_ssl_handshaker_factory base; + + /* Several contexts to support SNI. + The tsi_peer array contains the subject names of the server certificates + associated with the contexts at the same index. */ + SSL_CTX **ssl_contexts; + tsi_peer *ssl_context_x509_subject_names; + size_t ssl_context_count; + unsigned char *alpn_protocol_list; + size_t alpn_protocol_list_length; +} tsi_ssl_server_handshaker_factory; + +typedef struct { + tsi_handshaker base; + SSL *ssl; + BIO *into_ssl; + BIO *from_ssl; + tsi_result result; +} tsi_ssl_handshaker; + +typedef struct { + tsi_frame_protector base; + SSL *ssl; + BIO *into_ssl; + BIO *from_ssl; + unsigned char *buffer; + size_t buffer_size; + size_t buffer_offset; +} tsi_ssl_frame_protector; + +/* --- Library Initialization. ---*/ + +static gpr_once init_openssl_once = GPR_ONCE_INIT; +static gpr_mu *openssl_mutexes = NULL; + +static void openssl_locking_cb(int mode, int type, const char *file, int line) { + if (mode & CRYPTO_LOCK) { + gpr_mu_lock(&openssl_mutexes[type]); + } else { + gpr_mu_unlock(&openssl_mutexes[type]); + } +} + +static unsigned long openssl_thread_id_cb(void) { + return (unsigned long)gpr_thd_currentid(); +} + +static void init_openssl(void) { + int i; + int num_locks; + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + num_locks = CRYPTO_num_locks(); + GPR_ASSERT(num_locks > 0); + openssl_mutexes = malloc((size_t)num_locks * sizeof(gpr_mu)); + GPR_ASSERT(openssl_mutexes != NULL); + for (i = 0; i < CRYPTO_num_locks(); i++) { + gpr_mu_init(&openssl_mutexes[i]); + } + CRYPTO_set_locking_callback(openssl_locking_cb); + CRYPTO_set_id_callback(openssl_thread_id_cb); +} + +/* --- Ssl utils. ---*/ + +static const char *ssl_error_string(int error) { + switch (error) { + case SSL_ERROR_NONE: + return "SSL_ERROR_NONE"; + case SSL_ERROR_ZERO_RETURN: + return "SSL_ERROR_ZERO_RETURN"; + case SSL_ERROR_WANT_READ: + return "SSL_ERROR_WANT_READ"; + case SSL_ERROR_WANT_WRITE: + return "SSL_ERROR_WANT_WRITE"; + case SSL_ERROR_WANT_CONNECT: + return "SSL_ERROR_WANT_CONNECT"; + case SSL_ERROR_WANT_ACCEPT: + return "SSL_ERROR_WANT_ACCEPT"; + case SSL_ERROR_WANT_X509_LOOKUP: + return "SSL_ERROR_WANT_X509_LOOKUP"; + case SSL_ERROR_SYSCALL: + return "SSL_ERROR_SYSCALL"; + case SSL_ERROR_SSL: + return "SSL_ERROR_SSL"; + default: + return "Unknown error"; + } +} + +/* TODO(jboeuf): Remove when we are past the debugging phase with this code. */ +static void ssl_log_where_info(const SSL *ssl, int where, int flag, + const char *msg) { + if ((where & flag) && tsi_tracing_enabled) { + gpr_log(GPR_INFO, "%20.20s - %30.30s - %5.10s", msg, + SSL_state_string_long(ssl), SSL_state_string(ssl)); + } +} + +/* Used for debugging. TODO(jboeuf): Remove when code is mature enough. */ +static void ssl_info_callback(const SSL *ssl, int where, int ret) { + if (ret == 0) { + gpr_log(GPR_ERROR, "ssl_info_callback: error occured.\n"); + return; + } + + ssl_log_where_info(ssl, where, SSL_CB_LOOP, "LOOP"); + ssl_log_where_info(ssl, where, SSL_CB_HANDSHAKE_START, "HANDSHAKE START"); + ssl_log_where_info(ssl, where, SSL_CB_HANDSHAKE_DONE, "HANDSHAKE DONE"); +} + +/* Returns 1 if name looks like an IP address, 0 otherwise. + This is a very rough heuristic, and only handles IPv6 in hexadecimal form. */ +static int looks_like_ip_address(const char *name) { + size_t i; + size_t dot_count = 0; + size_t num_size = 0; + for (i = 0; i < strlen(name); i++) { + if (name[i] == ':') { + /* IPv6 Address in hexadecimal form, : is not allowed in DNS names. */ + return 1; + } + if (name[i] >= '0' && name[i] <= '9') { + if (num_size > 3) return 0; + num_size++; + } else if (name[i] == '.') { + if (dot_count > 3 || num_size == 0) return 0; + dot_count++; + num_size = 0; + } else { + return 0; + } + } + if (dot_count < 3 || num_size == 0) return 0; + return 1; +} + +/* Gets the subject CN from an X509 cert. */ +static tsi_result ssl_get_x509_common_name(X509 *cert, unsigned char **utf8, + size_t *utf8_size) { + int common_name_index = -1; + X509_NAME_ENTRY *common_name_entry = NULL; + ASN1_STRING *common_name_asn1 = NULL; + X509_NAME *subject_name = X509_get_subject_name(cert); + int utf8_returned_size = 0; + if (subject_name == NULL) { + gpr_log(GPR_ERROR, "Could not get subject name from certificate."); + return TSI_NOT_FOUND; + } + common_name_index = + X509_NAME_get_index_by_NID(subject_name, NID_commonName, -1); + if (common_name_index == -1) { + gpr_log(GPR_ERROR, + "Could not get common name of subject from certificate."); + return TSI_NOT_FOUND; + } + common_name_entry = X509_NAME_get_entry(subject_name, common_name_index); + if (common_name_entry == NULL) { + gpr_log(GPR_ERROR, "Could not get common name entry from certificate."); + return TSI_INTERNAL_ERROR; + } + common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry); + if (common_name_asn1 == NULL) { + gpr_log(GPR_ERROR, + "Could not get common name entry asn1 from certificate."); + return TSI_INTERNAL_ERROR; + } + utf8_returned_size = ASN1_STRING_to_UTF8(utf8, common_name_asn1); + if (utf8_returned_size < 0) { + gpr_log(GPR_ERROR, "Could not extract utf8 from asn1 string."); + return TSI_OUT_OF_RESOURCES; + } + *utf8_size = (size_t)utf8_returned_size; + return TSI_OK; +} + +/* Gets the subject CN of an X509 cert as a tsi_peer_property. */ +static tsi_result peer_property_from_x509_common_name( + X509 *cert, tsi_peer_property *property) { + unsigned char *common_name; + size_t common_name_size; + tsi_result result = + ssl_get_x509_common_name(cert, &common_name, &common_name_size); + if (result != TSI_OK) { + if (result == TSI_NOT_FOUND) { + common_name = NULL; + common_name_size = 0; + } else { + return result; + } + } + result = tsi_construct_string_peer_property( + TSI_X509_SUBJECT_COMMON_NAME_PEER_PROPERTY, + common_name == NULL ? "" : (const char *)common_name, common_name_size, + property); + OPENSSL_free(common_name); + return result; +} + +/* Gets the X509 cert in PEM format as a tsi_peer_property. */ +static tsi_result add_pem_certificate(X509 *cert, tsi_peer_property *property) { + BIO *bio = BIO_new(BIO_s_mem()); + if (!PEM_write_bio_X509(bio, cert)) { + BIO_free(bio); + return TSI_INTERNAL_ERROR; + } + char *contents; + long len = BIO_get_mem_data(bio, &contents); + if (len <= 0) { + BIO_free(bio); + return TSI_INTERNAL_ERROR; + } + tsi_result result = tsi_construct_string_peer_property( + TSI_X509_PEM_CERT_PROPERTY, (const char *)contents, (size_t)len, + property); + BIO_free(bio); + return result; +} + +/* Gets the subject SANs from an X509 cert as a tsi_peer_property. */ +static tsi_result add_subject_alt_names_properties_to_peer( + tsi_peer *peer, GENERAL_NAMES *subject_alt_names, + size_t subject_alt_name_count) { + size_t i; + tsi_result result = TSI_OK; + + /* Reset for DNS entries filtering. */ + peer->property_count -= subject_alt_name_count; + + for (i = 0; i < subject_alt_name_count; i++) { + GENERAL_NAME *subject_alt_name = + sk_GENERAL_NAME_value(subject_alt_names, TSI_SIZE_AS_SIZE(i)); + /* Filter out the non-dns entries names. */ + if (subject_alt_name->type == GEN_DNS) { + unsigned char *name = NULL; + int name_size; + name_size = ASN1_STRING_to_UTF8(&name, subject_alt_name->d.dNSName); + if (name_size < 0) { + gpr_log(GPR_ERROR, "Could not get utf8 from asn1 string."); + result = TSI_INTERNAL_ERROR; + break; + } + result = tsi_construct_string_peer_property( + TSI_X509_SUBJECT_ALTERNATIVE_NAME_PEER_PROPERTY, (const char *)name, + (size_t)name_size, &peer->properties[peer->property_count++]); + OPENSSL_free(name); + } else if (subject_alt_name->type == GEN_IPADD) { + char ntop_buf[INET6_ADDRSTRLEN]; + int af; + + if (subject_alt_name->d.iPAddress->length == 4) { + af = AF_INET; + } else if (subject_alt_name->d.iPAddress->length == 16) { + af = AF_INET6; + } else { + gpr_log(GPR_ERROR, "SAN IP Address contained invalid IP"); + result = TSI_INTERNAL_ERROR; + break; + } + const char *name = inet_ntop(af, subject_alt_name->d.iPAddress->data, + ntop_buf, INET6_ADDRSTRLEN); + if (name == NULL) { + gpr_log(GPR_ERROR, "Could not get IP string from asn1 octet."); + result = TSI_INTERNAL_ERROR; + break; + } + + result = tsi_construct_string_peer_property_from_cstring( + TSI_X509_SUBJECT_ALTERNATIVE_NAME_PEER_PROPERTY, name, + &peer->properties[peer->property_count++]); + } + if (result != TSI_OK) break; + } + return result; +} + +/* Gets information about the peer's X509 cert as a tsi_peer object. */ +static tsi_result peer_from_x509(X509 *cert, int include_certificate_type, + tsi_peer *peer) { + /* TODO(jboeuf): Maybe add more properties. */ + GENERAL_NAMES *subject_alt_names = + X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0); + int subject_alt_name_count = (subject_alt_names != NULL) + ? (int)sk_GENERAL_NAME_num(subject_alt_names) + : 0; + size_t property_count; + tsi_result result; + GPR_ASSERT(subject_alt_name_count >= 0); + property_count = (include_certificate_type ? (size_t)1 : 0) + + 2 /* common name, certificate */ + + (size_t)subject_alt_name_count; + result = tsi_construct_peer(property_count, peer); + if (result != TSI_OK) return result; + do { + if (include_certificate_type) { + result = tsi_construct_string_peer_property_from_cstring( + TSI_CERTIFICATE_TYPE_PEER_PROPERTY, TSI_X509_CERTIFICATE_TYPE, + &peer->properties[0]); + if (result != TSI_OK) break; + } + result = peer_property_from_x509_common_name( + cert, &peer->properties[include_certificate_type ? 1 : 0]); + if (result != TSI_OK) break; + + result = add_pem_certificate( + cert, &peer->properties[include_certificate_type ? 2 : 1]); + if (result != TSI_OK) break; + + if (subject_alt_name_count != 0) { + result = add_subject_alt_names_properties_to_peer( + peer, subject_alt_names, (size_t)subject_alt_name_count); + if (result != TSI_OK) break; + } + } while (0); + + if (subject_alt_names != NULL) { + sk_GENERAL_NAME_pop_free(subject_alt_names, GENERAL_NAME_free); + } + if (result != TSI_OK) tsi_peer_destruct(peer); + return result; +} + +/* Logs the SSL error stack. */ +static void log_ssl_error_stack(void) { + unsigned long err; + while ((err = ERR_get_error()) != 0) { + char details[256]; + ERR_error_string_n((uint32_t)err, details, sizeof(details)); + gpr_log(GPR_ERROR, "%s", details); + } +} + +/* Performs an SSL_read and handle errors. */ +static tsi_result do_ssl_read(SSL *ssl, unsigned char *unprotected_bytes, + size_t *unprotected_bytes_size) { + int read_from_ssl; + GPR_ASSERT(*unprotected_bytes_size <= INT_MAX); + read_from_ssl = + SSL_read(ssl, unprotected_bytes, (int)*unprotected_bytes_size); + if (read_from_ssl == 0) { + gpr_log(GPR_ERROR, "SSL_read returned 0 unexpectedly."); + return TSI_INTERNAL_ERROR; + } + if (read_from_ssl < 0) { + read_from_ssl = SSL_get_error(ssl, read_from_ssl); + switch (read_from_ssl) { + case SSL_ERROR_WANT_READ: + /* We need more data to finish the frame. */ + *unprotected_bytes_size = 0; + return TSI_OK; + case SSL_ERROR_WANT_WRITE: + gpr_log( + GPR_ERROR, + "Peer tried to renegotiate SSL connection. This is unsupported."); + return TSI_UNIMPLEMENTED; + case SSL_ERROR_SSL: + gpr_log(GPR_ERROR, "Corruption detected."); + log_ssl_error_stack(); + return TSI_DATA_CORRUPTED; + default: + gpr_log(GPR_ERROR, "SSL_read failed with error %s.", + ssl_error_string(read_from_ssl)); + return TSI_PROTOCOL_FAILURE; + } + } + *unprotected_bytes_size = (size_t)read_from_ssl; + return TSI_OK; +} + +/* Performs an SSL_write and handle errors. */ +static tsi_result do_ssl_write(SSL *ssl, unsigned char *unprotected_bytes, + size_t unprotected_bytes_size) { + int ssl_write_result; + GPR_ASSERT(unprotected_bytes_size <= INT_MAX); + ssl_write_result = + SSL_write(ssl, unprotected_bytes, (int)unprotected_bytes_size); + if (ssl_write_result < 0) { + ssl_write_result = SSL_get_error(ssl, ssl_write_result); + if (ssl_write_result == SSL_ERROR_WANT_READ) { + gpr_log(GPR_ERROR, + "Peer tried to renegotiate SSL connection. This is unsupported."); + return TSI_UNIMPLEMENTED; + } else { + gpr_log(GPR_ERROR, "SSL_write failed with error %s.", + ssl_error_string(ssl_write_result)); + return TSI_INTERNAL_ERROR; + } + } + return TSI_OK; +} + +/* Loads an in-memory PEM certificate chain into the SSL context. */ +static tsi_result ssl_ctx_use_certificate_chain( + SSL_CTX *context, const unsigned char *pem_cert_chain, + size_t pem_cert_chain_size) { + tsi_result result = TSI_OK; + X509 *certificate = NULL; + BIO *pem; + GPR_ASSERT(pem_cert_chain_size <= INT_MAX); + pem = BIO_new_mem_buf((void *)pem_cert_chain, (int)pem_cert_chain_size); + if (pem == NULL) return TSI_OUT_OF_RESOURCES; + + do { + certificate = PEM_read_bio_X509_AUX(pem, NULL, NULL, ""); + if (certificate == NULL) { + result = TSI_INVALID_ARGUMENT; + break; + } + if (!SSL_CTX_use_certificate(context, certificate)) { + result = TSI_INVALID_ARGUMENT; + break; + } + while (1) { + X509 *certificate_authority = PEM_read_bio_X509(pem, NULL, NULL, ""); + if (certificate_authority == NULL) { + ERR_clear_error(); + break; /* Done reading. */ + } + if (!SSL_CTX_add_extra_chain_cert(context, certificate_authority)) { + X509_free(certificate_authority); + result = TSI_INVALID_ARGUMENT; + break; + } + /* We don't need to free certificate_authority as its ownership has been + transfered to the context. That is not the case for certificate though. + */ + } + } while (0); + + if (certificate != NULL) X509_free(certificate); + BIO_free(pem); + return result; +} + +/* Loads an in-memory PEM private key into the SSL context. */ +static tsi_result ssl_ctx_use_private_key(SSL_CTX *context, + const unsigned char *pem_key, + size_t pem_key_size) { + tsi_result result = TSI_OK; + EVP_PKEY *private_key = NULL; + BIO *pem; + GPR_ASSERT(pem_key_size <= INT_MAX); + pem = BIO_new_mem_buf((void *)pem_key, (int)pem_key_size); + if (pem == NULL) return TSI_OUT_OF_RESOURCES; + do { + private_key = PEM_read_bio_PrivateKey(pem, NULL, NULL, ""); + if (private_key == NULL) { + result = TSI_INVALID_ARGUMENT; + break; + } + if (!SSL_CTX_use_PrivateKey(context, private_key)) { + result = TSI_INVALID_ARGUMENT; + break; + } + } while (0); + if (private_key != NULL) EVP_PKEY_free(private_key); + BIO_free(pem); + return result; +} + +/* Loads in-memory PEM verification certs into the SSL context and optionally + returns the verification cert names (root_names can be NULL). */ +static tsi_result ssl_ctx_load_verification_certs( + SSL_CTX *context, const unsigned char *pem_roots, size_t pem_roots_size, + STACK_OF(X509_NAME) * *root_names) { + tsi_result result = TSI_OK; + size_t num_roots = 0; + X509 *root = NULL; + X509_NAME *root_name = NULL; + BIO *pem; + X509_STORE *root_store; + GPR_ASSERT(pem_roots_size <= INT_MAX); + pem = BIO_new_mem_buf((void *)pem_roots, (int)pem_roots_size); + root_store = SSL_CTX_get_cert_store(context); + if (root_store == NULL) return TSI_INVALID_ARGUMENT; + if (pem == NULL) return TSI_OUT_OF_RESOURCES; + if (root_names != NULL) { + *root_names = sk_X509_NAME_new_null(); + if (*root_names == NULL) return TSI_OUT_OF_RESOURCES; + } + + while (1) { + root = PEM_read_bio_X509_AUX(pem, NULL, NULL, ""); + if (root == NULL) { + ERR_clear_error(); + break; /* We're at the end of stream. */ + } + if (root_names != NULL) { + root_name = X509_get_subject_name(root); + if (root_name == NULL) { + gpr_log(GPR_ERROR, "Could not get name from root certificate."); + result = TSI_INVALID_ARGUMENT; + break; + } + root_name = X509_NAME_dup(root_name); + if (root_name == NULL) { + result = TSI_OUT_OF_RESOURCES; + break; + } + sk_X509_NAME_push(*root_names, root_name); + root_name = NULL; + } + if (!X509_STORE_add_cert(root_store, root)) { + gpr_log(GPR_ERROR, "Could not add root certificate to ssl context."); + result = TSI_INTERNAL_ERROR; + break; + } + X509_free(root); + num_roots++; + } + + if (num_roots == 0) { + gpr_log(GPR_ERROR, "Could not load any root certificate."); + result = TSI_INVALID_ARGUMENT; + } + + if (result != TSI_OK) { + if (root != NULL) X509_free(root); + if (root_names != NULL) { + sk_X509_NAME_pop_free(*root_names, X509_NAME_free); + *root_names = NULL; + if (root_name != NULL) X509_NAME_free(root_name); + } + } + BIO_free(pem); + return result; +} + +/* Populates the SSL context with a private key and a cert chain, and sets the + cipher list and the ephemeral ECDH key. */ +static tsi_result populate_ssl_context( + SSL_CTX *context, const unsigned char *pem_private_key, + size_t pem_private_key_size, const unsigned char *pem_certificate_chain, + size_t pem_certificate_chain_size, const char *cipher_list) { + tsi_result result = TSI_OK; + if (pem_certificate_chain != NULL) { + result = ssl_ctx_use_certificate_chain(context, pem_certificate_chain, + pem_certificate_chain_size); + if (result != TSI_OK) { + gpr_log(GPR_ERROR, "Invalid cert chain file."); + return result; + } + } + if (pem_private_key != NULL) { + result = + ssl_ctx_use_private_key(context, pem_private_key, pem_private_key_size); + if (result != TSI_OK || !SSL_CTX_check_private_key(context)) { + gpr_log(GPR_ERROR, "Invalid private key."); + return result != TSI_OK ? result : TSI_INVALID_ARGUMENT; + } + } + if ((cipher_list != NULL) && !SSL_CTX_set_cipher_list(context, cipher_list)) { + gpr_log(GPR_ERROR, "Invalid cipher list: %s.", cipher_list); + return TSI_INVALID_ARGUMENT; + } + { + EC_KEY *ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (!SSL_CTX_set_tmp_ecdh(context, ecdh)) { + gpr_log(GPR_ERROR, "Could not set ephemeral ECDH key."); + EC_KEY_free(ecdh); + return TSI_INTERNAL_ERROR; + } + SSL_CTX_set_options(context, SSL_OP_SINGLE_ECDH_USE); + EC_KEY_free(ecdh); + } + return TSI_OK; +} + +/* Extracts the CN and the SANs from an X509 cert as a peer object. */ +static tsi_result extract_x509_subject_names_from_pem_cert( + const unsigned char *pem_cert, size_t pem_cert_size, tsi_peer *peer) { + tsi_result result = TSI_OK; + X509 *cert = NULL; + BIO *pem; + GPR_ASSERT(pem_cert_size <= INT_MAX); + pem = BIO_new_mem_buf((void *)pem_cert, (int)pem_cert_size); + if (pem == NULL) return TSI_OUT_OF_RESOURCES; + + cert = PEM_read_bio_X509(pem, NULL, NULL, ""); + if (cert == NULL) { + gpr_log(GPR_ERROR, "Invalid certificate"); + result = TSI_INVALID_ARGUMENT; + } else { + result = peer_from_x509(cert, 0, peer); + } + if (cert != NULL) X509_free(cert); + BIO_free(pem); + return result; +} + +/* Builds the alpn protocol name list according to rfc 7301. */ +static tsi_result build_alpn_protocol_name_list( + const unsigned char **alpn_protocols, + const unsigned char *alpn_protocols_lengths, uint16_t num_alpn_protocols, + unsigned char **protocol_name_list, size_t *protocol_name_list_length) { + uint16_t i; + unsigned char *current; + *protocol_name_list = NULL; + *protocol_name_list_length = 0; + if (num_alpn_protocols == 0) return TSI_INVALID_ARGUMENT; + for (i = 0; i < num_alpn_protocols; i++) { + if (alpn_protocols_lengths[i] == 0) { + gpr_log(GPR_ERROR, "Invalid 0-length protocol name."); + return TSI_INVALID_ARGUMENT; + } + *protocol_name_list_length += (size_t)alpn_protocols_lengths[i] + 1; + } + *protocol_name_list = malloc(*protocol_name_list_length); + if (*protocol_name_list == NULL) return TSI_OUT_OF_RESOURCES; + current = *protocol_name_list; + for (i = 0; i < num_alpn_protocols; i++) { + *(current++) = alpn_protocols_lengths[i]; + memcpy(current, alpn_protocols[i], alpn_protocols_lengths[i]); + current += alpn_protocols_lengths[i]; + } + /* Safety check. */ + if ((current < *protocol_name_list) || + ((uintptr_t)(current - *protocol_name_list) != + *protocol_name_list_length)) { + return TSI_INTERNAL_ERROR; + } + return TSI_OK; +} + +/* --- tsi_frame_protector methods implementation. ---*/ + +static tsi_result ssl_protector_protect(tsi_frame_protector *self, + const unsigned char *unprotected_bytes, + size_t *unprotected_bytes_size, + unsigned char *protected_output_frames, + size_t *protected_output_frames_size) { + tsi_ssl_frame_protector *impl = (tsi_ssl_frame_protector *)self; + int read_from_ssl; + size_t available; + tsi_result result = TSI_OK; + + /* First see if we have some pending data in the SSL BIO. */ + int pending_in_ssl = (int)BIO_pending(impl->from_ssl); + if (pending_in_ssl > 0) { + *unprotected_bytes_size = 0; + GPR_ASSERT(*protected_output_frames_size <= INT_MAX); + read_from_ssl = BIO_read(impl->from_ssl, protected_output_frames, + (int)*protected_output_frames_size); + if (read_from_ssl < 0) { + gpr_log(GPR_ERROR, + "Could not read from BIO even though some data is pending"); + return TSI_INTERNAL_ERROR; + } + *protected_output_frames_size = (size_t)read_from_ssl; + return TSI_OK; + } + + /* Now see if we can send a complete frame. */ + available = impl->buffer_size - impl->buffer_offset; + if (available > *unprotected_bytes_size) { + /* If we cannot, just copy the data in our internal buffer. */ + memcpy(impl->buffer + impl->buffer_offset, unprotected_bytes, + *unprotected_bytes_size); + impl->buffer_offset += *unprotected_bytes_size; + *protected_output_frames_size = 0; + return TSI_OK; + } + + /* If we can, prepare the buffer, send it to SSL_write and read. */ + memcpy(impl->buffer + impl->buffer_offset, unprotected_bytes, available); + result = do_ssl_write(impl->ssl, impl->buffer, impl->buffer_size); + if (result != TSI_OK) return result; + + GPR_ASSERT(*protected_output_frames_size <= INT_MAX); + read_from_ssl = BIO_read(impl->from_ssl, protected_output_frames, + (int)*protected_output_frames_size); + if (read_from_ssl < 0) { + gpr_log(GPR_ERROR, "Could not read from BIO after SSL_write."); + return TSI_INTERNAL_ERROR; + } + *protected_output_frames_size = (size_t)read_from_ssl; + *unprotected_bytes_size = available; + impl->buffer_offset = 0; + return TSI_OK; +} + +static tsi_result ssl_protector_protect_flush( + tsi_frame_protector *self, unsigned char *protected_output_frames, + size_t *protected_output_frames_size, size_t *still_pending_size) { + tsi_result result = TSI_OK; + tsi_ssl_frame_protector *impl = (tsi_ssl_frame_protector *)self; + int read_from_ssl = 0; + int pending; + + if (impl->buffer_offset != 0) { + result = do_ssl_write(impl->ssl, impl->buffer, impl->buffer_offset); + if (result != TSI_OK) return result; + impl->buffer_offset = 0; + } + + pending = (int)BIO_pending(impl->from_ssl); + GPR_ASSERT(pending >= 0); + *still_pending_size = (size_t)pending; + if (*still_pending_size == 0) return TSI_OK; + + GPR_ASSERT(*protected_output_frames_size <= INT_MAX); + read_from_ssl = BIO_read(impl->from_ssl, protected_output_frames, + (int)*protected_output_frames_size); + if (read_from_ssl <= 0) { + gpr_log(GPR_ERROR, "Could not read from BIO after SSL_write."); + return TSI_INTERNAL_ERROR; + } + *protected_output_frames_size = (size_t)read_from_ssl; + pending = (int)BIO_pending(impl->from_ssl); + GPR_ASSERT(pending >= 0); + *still_pending_size = (size_t)pending; + return TSI_OK; +} + +static tsi_result ssl_protector_unprotect( + tsi_frame_protector *self, const unsigned char *protected_frames_bytes, + size_t *protected_frames_bytes_size, unsigned char *unprotected_bytes, + size_t *unprotected_bytes_size) { + tsi_result result = TSI_OK; + int written_into_ssl = 0; + size_t output_bytes_size = *unprotected_bytes_size; + size_t output_bytes_offset = 0; + tsi_ssl_frame_protector *impl = (tsi_ssl_frame_protector *)self; + + /* First, try to read remaining data from ssl. */ + result = do_ssl_read(impl->ssl, unprotected_bytes, unprotected_bytes_size); + if (result != TSI_OK) return result; + if (*unprotected_bytes_size == output_bytes_size) { + /* We have read everything we could and cannot process any more input. */ + *protected_frames_bytes_size = 0; + return TSI_OK; + } + output_bytes_offset = *unprotected_bytes_size; + unprotected_bytes += output_bytes_offset; + *unprotected_bytes_size = output_bytes_size - output_bytes_offset; + + /* Then, try to write some data to ssl. */ + GPR_ASSERT(*protected_frames_bytes_size <= INT_MAX); + written_into_ssl = BIO_write(impl->into_ssl, protected_frames_bytes, + (int)*protected_frames_bytes_size); + if (written_into_ssl < 0) { + gpr_log(GPR_ERROR, "Sending protected frame to ssl failed with %d", + written_into_ssl); + return TSI_INTERNAL_ERROR; + } + *protected_frames_bytes_size = (size_t)written_into_ssl; + + /* Now try to read some data again. */ + result = do_ssl_read(impl->ssl, unprotected_bytes, unprotected_bytes_size); + if (result == TSI_OK) { + /* Don't forget to output the total number of bytes read. */ + *unprotected_bytes_size += output_bytes_offset; + } + return result; +} + +static void ssl_protector_destroy(tsi_frame_protector *self) { + tsi_ssl_frame_protector *impl = (tsi_ssl_frame_protector *)self; + if (impl->buffer != NULL) free(impl->buffer); + if (impl->ssl != NULL) SSL_free(impl->ssl); + free(self); +} + +static const tsi_frame_protector_vtable frame_protector_vtable = { + ssl_protector_protect, ssl_protector_protect_flush, ssl_protector_unprotect, + ssl_protector_destroy, +}; + +/* --- tsi_handshaker methods implementation. ---*/ + +static tsi_result ssl_handshaker_get_bytes_to_send_to_peer(tsi_handshaker *self, + unsigned char *bytes, + size_t *bytes_size) { + tsi_ssl_handshaker *impl = (tsi_ssl_handshaker *)self; + int bytes_read_from_ssl = 0; + if (bytes == NULL || bytes_size == NULL || *bytes_size == 0 || + *bytes_size > INT_MAX) { + return TSI_INVALID_ARGUMENT; + } + GPR_ASSERT(*bytes_size <= INT_MAX); + bytes_read_from_ssl = BIO_read(impl->from_ssl, bytes, (int)*bytes_size); + if (bytes_read_from_ssl < 0) { + *bytes_size = 0; + if (!BIO_should_retry(impl->from_ssl)) { + impl->result = TSI_INTERNAL_ERROR; + return impl->result; + } else { + return TSI_OK; + } + } + *bytes_size = (size_t)bytes_read_from_ssl; + return BIO_pending(impl->from_ssl) == 0 ? TSI_OK : TSI_INCOMPLETE_DATA; +} + +static tsi_result ssl_handshaker_get_result(tsi_handshaker *self) { + tsi_ssl_handshaker *impl = (tsi_ssl_handshaker *)self; + if ((impl->result == TSI_HANDSHAKE_IN_PROGRESS) && + SSL_is_init_finished(impl->ssl)) { + impl->result = TSI_OK; + } + return impl->result; +} + +static tsi_result ssl_handshaker_process_bytes_from_peer( + tsi_handshaker *self, const unsigned char *bytes, size_t *bytes_size) { + tsi_ssl_handshaker *impl = (tsi_ssl_handshaker *)self; + int bytes_written_into_ssl_size = 0; + if (bytes == NULL || bytes_size == 0 || *bytes_size > INT_MAX) { + return TSI_INVALID_ARGUMENT; + } + GPR_ASSERT(*bytes_size <= INT_MAX); + bytes_written_into_ssl_size = + BIO_write(impl->into_ssl, bytes, (int)*bytes_size); + if (bytes_written_into_ssl_size < 0) { + gpr_log(GPR_ERROR, "Could not write to memory BIO."); + impl->result = TSI_INTERNAL_ERROR; + return impl->result; + } + *bytes_size = (size_t)bytes_written_into_ssl_size; + + if (!tsi_handshaker_is_in_progress(self)) { + impl->result = TSI_OK; + return impl->result; + } else { + /* Get ready to get some bytes from SSL. */ + int ssl_result = SSL_do_handshake(impl->ssl); + ssl_result = SSL_get_error(impl->ssl, ssl_result); + switch (ssl_result) { + case SSL_ERROR_WANT_READ: + if (BIO_pending(impl->from_ssl) == 0) { + /* We need more data. */ + return TSI_INCOMPLETE_DATA; + } else { + return TSI_OK; + } + case SSL_ERROR_NONE: + return TSI_OK; + default: { + char err_str[256]; + ERR_error_string_n(ERR_get_error(), err_str, sizeof(err_str)); + gpr_log(GPR_ERROR, "Handshake failed with fatal error %s: %s.", + ssl_error_string(ssl_result), err_str); + impl->result = TSI_PROTOCOL_FAILURE; + return impl->result; + } + } + } +} + +static tsi_result ssl_handshaker_extract_peer(tsi_handshaker *self, + tsi_peer *peer) { + tsi_result result = TSI_OK; + const unsigned char *alpn_selected = NULL; + unsigned int alpn_selected_len; + tsi_ssl_handshaker *impl = (tsi_ssl_handshaker *)self; + X509 *peer_cert = SSL_get_peer_certificate(impl->ssl); + if (peer_cert != NULL) { + result = peer_from_x509(peer_cert, 1, peer); + X509_free(peer_cert); + if (result != TSI_OK) return result; + } +#if TSI_OPENSSL_ALPN_SUPPORT + SSL_get0_alpn_selected(impl->ssl, &alpn_selected, &alpn_selected_len); +#endif /* TSI_OPENSSL_ALPN_SUPPORT */ + if (alpn_selected == NULL) { + /* Try npn. */ + SSL_get0_next_proto_negotiated(impl->ssl, &alpn_selected, + &alpn_selected_len); + } + if (alpn_selected != NULL) { + size_t i; + tsi_peer_property *new_properties = + calloc(1, sizeof(tsi_peer_property) * (peer->property_count + 1)); + if (new_properties == NULL) return TSI_OUT_OF_RESOURCES; + for (i = 0; i < peer->property_count; i++) { + new_properties[i] = peer->properties[i]; + } + result = tsi_construct_string_peer_property( + TSI_SSL_ALPN_SELECTED_PROTOCOL, (const char *)alpn_selected, + alpn_selected_len, &new_properties[peer->property_count]); + if (result != TSI_OK) { + free(new_properties); + return result; + } + if (peer->properties != NULL) free(peer->properties); + peer->property_count++; + peer->properties = new_properties; + } + return result; +} + +static tsi_result ssl_handshaker_create_frame_protector( + tsi_handshaker *self, size_t *max_output_protected_frame_size, + tsi_frame_protector **protector) { + size_t actual_max_output_protected_frame_size = + TSI_SSL_MAX_PROTECTED_FRAME_SIZE_UPPER_BOUND; + tsi_ssl_handshaker *impl = (tsi_ssl_handshaker *)self; + tsi_ssl_frame_protector *protector_impl = + calloc(1, sizeof(tsi_ssl_frame_protector)); + if (protector_impl == NULL) { + return TSI_OUT_OF_RESOURCES; + } + + if (max_output_protected_frame_size != NULL) { + if (*max_output_protected_frame_size > + TSI_SSL_MAX_PROTECTED_FRAME_SIZE_UPPER_BOUND) { + *max_output_protected_frame_size = + TSI_SSL_MAX_PROTECTED_FRAME_SIZE_UPPER_BOUND; + } else if (*max_output_protected_frame_size < + TSI_SSL_MAX_PROTECTED_FRAME_SIZE_LOWER_BOUND) { + *max_output_protected_frame_size = + TSI_SSL_MAX_PROTECTED_FRAME_SIZE_LOWER_BOUND; + } + actual_max_output_protected_frame_size = *max_output_protected_frame_size; + } + protector_impl->buffer_size = + actual_max_output_protected_frame_size - TSI_SSL_MAX_PROTECTION_OVERHEAD; + protector_impl->buffer = malloc(protector_impl->buffer_size); + if (protector_impl->buffer == NULL) { + gpr_log(GPR_ERROR, + "Could not allocated buffer for tsi_ssl_frame_protector."); + free(protector_impl); + return TSI_INTERNAL_ERROR; + } + + /* Transfer ownership of ssl to the frame protector. It is OK as the caller + * cannot call anything else but destroy on the handshaker after this call. */ + protector_impl->ssl = impl->ssl; + impl->ssl = NULL; + protector_impl->into_ssl = impl->into_ssl; + protector_impl->from_ssl = impl->from_ssl; + + protector_impl->base.vtable = &frame_protector_vtable; + *protector = &protector_impl->base; + return TSI_OK; +} + +static void ssl_handshaker_destroy(tsi_handshaker *self) { + tsi_ssl_handshaker *impl = (tsi_ssl_handshaker *)self; + SSL_free(impl->ssl); /* The BIO objects are owned by ssl */ + free(impl); +} + +static const tsi_handshaker_vtable handshaker_vtable = { + ssl_handshaker_get_bytes_to_send_to_peer, + ssl_handshaker_process_bytes_from_peer, + ssl_handshaker_get_result, + ssl_handshaker_extract_peer, + ssl_handshaker_create_frame_protector, + ssl_handshaker_destroy, +}; + +/* --- tsi_ssl_handshaker_factory common methods. --- */ + +tsi_result tsi_ssl_handshaker_factory_create_handshaker( + tsi_ssl_handshaker_factory *self, const char *server_name_indication, + tsi_handshaker **handshaker) { + if (self == NULL || handshaker == NULL) return TSI_INVALID_ARGUMENT; + return self->create_handshaker(self, server_name_indication, handshaker); +} + +void tsi_ssl_handshaker_factory_destroy(tsi_ssl_handshaker_factory *self) { + if (self == NULL) return; + self->destroy(self); +} + +static tsi_result create_tsi_ssl_handshaker(SSL_CTX *ctx, int is_client, + const char *server_name_indication, + tsi_handshaker **handshaker) { + SSL *ssl = SSL_new(ctx); + BIO *into_ssl = NULL; + BIO *from_ssl = NULL; + tsi_ssl_handshaker *impl = NULL; + *handshaker = NULL; + if (ctx == NULL) { + gpr_log(GPR_ERROR, "SSL Context is null. Should never happen."); + return TSI_INTERNAL_ERROR; + } + if (ssl == NULL) { + return TSI_OUT_OF_RESOURCES; + } + SSL_set_info_callback(ssl, ssl_info_callback); + + into_ssl = BIO_new(BIO_s_mem()); + from_ssl = BIO_new(BIO_s_mem()); + if (into_ssl == NULL || from_ssl == NULL) { + gpr_log(GPR_ERROR, "BIO_new failed."); + SSL_free(ssl); + if (into_ssl != NULL) BIO_free(into_ssl); + if (from_ssl != NULL) BIO_free(into_ssl); + return TSI_OUT_OF_RESOURCES; + } + SSL_set_bio(ssl, into_ssl, from_ssl); + if (is_client) { + int ssl_result; + SSL_set_connect_state(ssl); + if (server_name_indication != NULL) { + if (!SSL_set_tlsext_host_name(ssl, server_name_indication)) { + gpr_log(GPR_ERROR, "Invalid server name indication %s.", + server_name_indication); + SSL_free(ssl); + return TSI_INTERNAL_ERROR; + } + } + ssl_result = SSL_do_handshake(ssl); + ssl_result = SSL_get_error(ssl, ssl_result); + if (ssl_result != SSL_ERROR_WANT_READ) { + gpr_log(GPR_ERROR, + "Unexpected error received from first SSL_do_handshake call: %s", + ssl_error_string(ssl_result)); + SSL_free(ssl); + return TSI_INTERNAL_ERROR; + } + } else { + SSL_set_accept_state(ssl); + } + + impl = calloc(1, sizeof(tsi_ssl_handshaker)); + if (impl == NULL) { + SSL_free(ssl); + return TSI_OUT_OF_RESOURCES; + } + impl->ssl = ssl; + impl->into_ssl = into_ssl; + impl->from_ssl = from_ssl; + impl->result = TSI_HANDSHAKE_IN_PROGRESS; + impl->base.vtable = &handshaker_vtable; + *handshaker = &impl->base; + return TSI_OK; +} + +static int select_protocol_list(const unsigned char **out, + unsigned char *outlen, + const unsigned char *client_list, + size_t client_list_len, + const unsigned char *server_list, + size_t server_list_len) { + const unsigned char *client_current = client_list; + while ((unsigned int)(client_current - client_list) < client_list_len) { + unsigned char client_current_len = *(client_current++); + const unsigned char *server_current = server_list; + while ((server_current >= server_list) && + (uintptr_t)(server_current - server_list) < server_list_len) { + unsigned char server_current_len = *(server_current++); + if ((client_current_len == server_current_len) && + !memcmp(client_current, server_current, server_current_len)) { + *out = server_current; + *outlen = server_current_len; + return SSL_TLSEXT_ERR_OK; + } + server_current += server_current_len; + } + client_current += client_current_len; + } + return SSL_TLSEXT_ERR_NOACK; +} + +/* --- tsi_ssl__client_handshaker_factory methods implementation. --- */ + +static tsi_result ssl_client_handshaker_factory_create_handshaker( + tsi_ssl_handshaker_factory *self, const char *server_name_indication, + tsi_handshaker **handshaker) { + tsi_ssl_client_handshaker_factory *impl = + (tsi_ssl_client_handshaker_factory *)self; + return create_tsi_ssl_handshaker(impl->ssl_context, 1, server_name_indication, + handshaker); +} + +static void ssl_client_handshaker_factory_destroy( + tsi_ssl_handshaker_factory *self) { + tsi_ssl_client_handshaker_factory *impl = + (tsi_ssl_client_handshaker_factory *)self; + if (impl->ssl_context != NULL) SSL_CTX_free(impl->ssl_context); + if (impl->alpn_protocol_list != NULL) free(impl->alpn_protocol_list); + free(impl); +} + +static int client_handshaker_factory_npn_callback(SSL *ssl, unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, + void *arg) { + tsi_ssl_client_handshaker_factory *factory = + (tsi_ssl_client_handshaker_factory *)arg; + return select_protocol_list((const unsigned char **)out, outlen, + factory->alpn_protocol_list, + factory->alpn_protocol_list_length, in, inlen); +} + +/* --- tsi_ssl_server_handshaker_factory methods implementation. --- */ + +static tsi_result ssl_server_handshaker_factory_create_handshaker( + tsi_ssl_handshaker_factory *self, const char *server_name_indication, + tsi_handshaker **handshaker) { + tsi_ssl_server_handshaker_factory *impl = + (tsi_ssl_server_handshaker_factory *)self; + if (impl->ssl_context_count == 0 || server_name_indication != NULL) { + return TSI_INVALID_ARGUMENT; + } + /* Create the handshaker with the first context. We will switch if needed + because of SNI in ssl_server_handshaker_factory_servername_callback. */ + return create_tsi_ssl_handshaker(impl->ssl_contexts[0], 0, NULL, handshaker); +} + +static void ssl_server_handshaker_factory_destroy( + tsi_ssl_handshaker_factory *self) { + tsi_ssl_server_handshaker_factory *impl = + (tsi_ssl_server_handshaker_factory *)self; + size_t i; + for (i = 0; i < impl->ssl_context_count; i++) { + if (impl->ssl_contexts[i] != NULL) { + SSL_CTX_free(impl->ssl_contexts[i]); + tsi_peer_destruct(&impl->ssl_context_x509_subject_names[i]); + } + } + if (impl->ssl_contexts != NULL) free(impl->ssl_contexts); + if (impl->ssl_context_x509_subject_names != NULL) { + free(impl->ssl_context_x509_subject_names); + } + if (impl->alpn_protocol_list != NULL) free(impl->alpn_protocol_list); + free(impl); +} + +static int does_entry_match_name(const char *entry, size_t entry_length, + const char *name) { + const char *dot; + const char *name_subdomain = NULL; + size_t name_length = strlen(name); + size_t name_subdomain_length; + if (entry_length == 0) return 0; + + /* Take care of '.' terminations. */ + if (name[name_length - 1] == '.') { + name_length--; + } + if (entry[entry_length - 1] == '.') { + entry_length--; + if (entry_length == 0) return 0; + } + + if ((name_length == entry_length) && + strncmp(name, entry, entry_length) == 0) { + return 1; /* Perfect match. */ + } + if (entry[0] != '*') return 0; + + /* Wildchar subdomain matching. */ + if (entry_length < 3 || entry[1] != '.') { /* At least *.x */ + gpr_log(GPR_ERROR, "Invalid wildchar entry."); + return 0; + } + name_subdomain = strchr(name, '.'); + if (name_subdomain == NULL) return 0; + name_subdomain_length = strlen(name_subdomain); + if (name_subdomain_length < 2) return 0; + name_subdomain++; /* Starts after the dot. */ + name_subdomain_length--; + entry += 2; /* Remove *. */ + entry_length -= 2; + dot = strchr(name_subdomain, '.'); + if ((dot == NULL) || (dot == &name_subdomain[name_subdomain_length - 1])) { + gpr_log(GPR_ERROR, "Invalid toplevel subdomain: %s", name_subdomain); + return 0; + } + if (name_subdomain[name_subdomain_length - 1] == '.') { + name_subdomain_length--; + } + return ((entry_length > 0) && (name_subdomain_length == entry_length) && + strncmp(entry, name_subdomain, entry_length) == 0); +} + +static int ssl_server_handshaker_factory_servername_callback(SSL *ssl, int *ap, + void *arg) { + tsi_ssl_server_handshaker_factory *impl = + (tsi_ssl_server_handshaker_factory *)arg; + size_t i = 0; + const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (servername == NULL || strlen(servername) == 0) { + return SSL_TLSEXT_ERR_NOACK; + } + + for (i = 0; i < impl->ssl_context_count; i++) { + if (tsi_ssl_peer_matches_name(&impl->ssl_context_x509_subject_names[i], + servername)) { + SSL_set_SSL_CTX(ssl, impl->ssl_contexts[i]); + return SSL_TLSEXT_ERR_OK; + } + } + gpr_log(GPR_ERROR, "No match found for server name: %s.", servername); + return SSL_TLSEXT_ERR_ALERT_WARNING; +} + +#if TSI_OPENSSL_ALPN_SUPPORT +static int server_handshaker_factory_alpn_callback( + SSL *ssl, const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, void *arg) { + tsi_ssl_server_handshaker_factory *factory = + (tsi_ssl_server_handshaker_factory *)arg; + return select_protocol_list(out, outlen, in, inlen, + factory->alpn_protocol_list, + factory->alpn_protocol_list_length); +} +#endif /* TSI_OPENSSL_ALPN_SUPPORT */ + +static int server_handshaker_factory_npn_advertised_callback( + SSL *ssl, const unsigned char **out, unsigned int *outlen, void *arg) { + tsi_ssl_server_handshaker_factory *factory = + (tsi_ssl_server_handshaker_factory *)arg; + *out = factory->alpn_protocol_list; + GPR_ASSERT(factory->alpn_protocol_list_length <= UINT_MAX); + *outlen = (unsigned int)factory->alpn_protocol_list_length; + return SSL_TLSEXT_ERR_OK; +} + +/* --- tsi_ssl_handshaker_factory constructors. --- */ + +tsi_result tsi_create_ssl_client_handshaker_factory( + const unsigned char *pem_private_key, size_t pem_private_key_size, + const unsigned char *pem_cert_chain, size_t pem_cert_chain_size, + const unsigned char *pem_root_certs, size_t pem_root_certs_size, + const char *cipher_list, const unsigned char **alpn_protocols, + const unsigned char *alpn_protocols_lengths, uint16_t num_alpn_protocols, + tsi_ssl_handshaker_factory **factory) { + SSL_CTX *ssl_context = NULL; + tsi_ssl_client_handshaker_factory *impl = NULL; + tsi_result result = TSI_OK; + + gpr_once_init(&init_openssl_once, init_openssl); + + if (factory == NULL) return TSI_INVALID_ARGUMENT; + *factory = NULL; + if (pem_root_certs == NULL) return TSI_INVALID_ARGUMENT; + + ssl_context = SSL_CTX_new(TLSv1_2_method()); + if (ssl_context == NULL) { + gpr_log(GPR_ERROR, "Could not create ssl context."); + return TSI_INVALID_ARGUMENT; + } + + impl = calloc(1, sizeof(tsi_ssl_client_handshaker_factory)); + if (impl == NULL) { + SSL_CTX_free(ssl_context); + return TSI_OUT_OF_RESOURCES; + } + impl->ssl_context = ssl_context; + + do { + result = + populate_ssl_context(ssl_context, pem_private_key, pem_private_key_size, + pem_cert_chain, pem_cert_chain_size, cipher_list); + if (result != TSI_OK) break; + result = ssl_ctx_load_verification_certs(ssl_context, pem_root_certs, + pem_root_certs_size, NULL); + if (result != TSI_OK) { + gpr_log(GPR_ERROR, "Cannot load server root certificates."); + break; + } + + if (num_alpn_protocols != 0) { + result = build_alpn_protocol_name_list( + alpn_protocols, alpn_protocols_lengths, num_alpn_protocols, + &impl->alpn_protocol_list, &impl->alpn_protocol_list_length); + if (result != TSI_OK) { + gpr_log(GPR_ERROR, "Building alpn list failed with error %s.", + tsi_result_to_string(result)); + break; + } +#if TSI_OPENSSL_ALPN_SUPPORT + GPR_ASSERT(impl->alpn_protocol_list_length < UINT_MAX); + if (SSL_CTX_set_alpn_protos( + ssl_context, impl->alpn_protocol_list, + (unsigned int)impl->alpn_protocol_list_length)) { + gpr_log(GPR_ERROR, "Could not set alpn protocol list to context."); + result = TSI_INVALID_ARGUMENT; + break; + } +#endif /* TSI_OPENSSL_ALPN_SUPPORT */ + SSL_CTX_set_next_proto_select_cb( + ssl_context, client_handshaker_factory_npn_callback, impl); + } + } while (0); + if (result != TSI_OK) { + ssl_client_handshaker_factory_destroy(&impl->base); + return result; + } + SSL_CTX_set_verify(ssl_context, SSL_VERIFY_PEER, NULL); + /* TODO(jboeuf): Add revocation verification. */ + + impl->base.create_handshaker = + ssl_client_handshaker_factory_create_handshaker; + impl->base.destroy = ssl_client_handshaker_factory_destroy; + *factory = &impl->base; + return TSI_OK; +} + +tsi_result tsi_create_ssl_server_handshaker_factory( + const unsigned char **pem_private_keys, + const size_t *pem_private_keys_sizes, const unsigned char **pem_cert_chains, + const size_t *pem_cert_chains_sizes, size_t key_cert_pair_count, + const unsigned char *pem_client_root_certs, + size_t pem_client_root_certs_size, int force_client_auth, + const char *cipher_list, const unsigned char **alpn_protocols, + const unsigned char *alpn_protocols_lengths, uint16_t num_alpn_protocols, + tsi_ssl_handshaker_factory **factory) { + tsi_ssl_server_handshaker_factory *impl = NULL; + tsi_result result = TSI_OK; + size_t i = 0; + + gpr_once_init(&init_openssl_once, init_openssl); + + if (factory == NULL) return TSI_INVALID_ARGUMENT; + *factory = NULL; + if (key_cert_pair_count == 0 || pem_private_keys == NULL || + pem_cert_chains == NULL) { + return TSI_INVALID_ARGUMENT; + } + + impl = calloc(1, sizeof(tsi_ssl_server_handshaker_factory)); + if (impl == NULL) return TSI_OUT_OF_RESOURCES; + impl->base.create_handshaker = + ssl_server_handshaker_factory_create_handshaker; + impl->base.destroy = ssl_server_handshaker_factory_destroy; + impl->ssl_contexts = calloc(key_cert_pair_count, sizeof(SSL_CTX *)); + impl->ssl_context_x509_subject_names = + calloc(key_cert_pair_count, sizeof(tsi_peer)); + if (impl->ssl_contexts == NULL || + impl->ssl_context_x509_subject_names == NULL) { + tsi_ssl_handshaker_factory_destroy(&impl->base); + return TSI_OUT_OF_RESOURCES; + } + impl->ssl_context_count = key_cert_pair_count; + + if (num_alpn_protocols > 0) { + result = build_alpn_protocol_name_list( + alpn_protocols, alpn_protocols_lengths, num_alpn_protocols, + &impl->alpn_protocol_list, &impl->alpn_protocol_list_length); + if (result != TSI_OK) { + tsi_ssl_handshaker_factory_destroy(&impl->base); + return result; + } + } + + for (i = 0; i < key_cert_pair_count; i++) { + do { + impl->ssl_contexts[i] = SSL_CTX_new(TLSv1_2_method()); + if (impl->ssl_contexts[i] == NULL) { + gpr_log(GPR_ERROR, "Could not create ssl context."); + result = TSI_OUT_OF_RESOURCES; + break; + } + result = populate_ssl_context( + impl->ssl_contexts[i], pem_private_keys[i], pem_private_keys_sizes[i], + pem_cert_chains[i], pem_cert_chains_sizes[i], cipher_list); + if (result != TSI_OK) break; + + if (pem_client_root_certs != NULL) { + int flags = SSL_VERIFY_PEER; + STACK_OF(X509_NAME) *root_names = NULL; + result = ssl_ctx_load_verification_certs( + impl->ssl_contexts[i], pem_client_root_certs, + pem_client_root_certs_size, &root_names); + if (result != TSI_OK) { + gpr_log(GPR_ERROR, "Invalid verification certs."); + break; + } + SSL_CTX_set_client_CA_list(impl->ssl_contexts[i], root_names); + if (force_client_auth) flags |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + SSL_CTX_set_verify(impl->ssl_contexts[i], flags, NULL); + /* TODO(jboeuf): Add revocation verification. */ + } + + result = extract_x509_subject_names_from_pem_cert( + pem_cert_chains[i], pem_cert_chains_sizes[i], + &impl->ssl_context_x509_subject_names[i]); + if (result != TSI_OK) break; + + SSL_CTX_set_tlsext_servername_callback( + impl->ssl_contexts[i], + ssl_server_handshaker_factory_servername_callback); + SSL_CTX_set_tlsext_servername_arg(impl->ssl_contexts[i], impl); +#if TSI_OPENSSL_ALPN_SUPPORT + SSL_CTX_set_alpn_select_cb(impl->ssl_contexts[i], + server_handshaker_factory_alpn_callback, impl); +#endif /* TSI_OPENSSL_ALPN_SUPPORT */ + SSL_CTX_set_next_protos_advertised_cb( + impl->ssl_contexts[i], + server_handshaker_factory_npn_advertised_callback, impl); + } while (0); + + if (result != TSI_OK) { + tsi_ssl_handshaker_factory_destroy(&impl->base); + return result; + } + } + *factory = &impl->base; + return TSI_OK; +} + +/* --- tsi_ssl utils. --- */ + +int tsi_ssl_peer_matches_name(const tsi_peer *peer, const char *name) { + size_t i = 0; + size_t san_count = 0; + const tsi_peer_property *cn_property = NULL; + int like_ip = looks_like_ip_address(name); + + /* Check the SAN first. */ + for (i = 0; i < peer->property_count; i++) { + const tsi_peer_property *property = &peer->properties[i]; + if (property->name == NULL) continue; + if (strcmp(property->name, + TSI_X509_SUBJECT_ALTERNATIVE_NAME_PEER_PROPERTY) == 0) { + san_count++; + + if (!like_ip && does_entry_match_name(property->value.data, + property->value.length, name)) { + return 1; + } else if (like_ip && + strncmp(name, property->value.data, property->value.length) == + 0 && + strlen(name) == property->value.length) { + /* IP Addresses are exact matches only. */ + return 1; + } + } else if (strcmp(property->name, + TSI_X509_SUBJECT_COMMON_NAME_PEER_PROPERTY) == 0) { + cn_property = property; + } + } + + /* If there's no SAN, try the CN, but only if its not like an IP Address */ + if (san_count == 0 && cn_property != NULL && !like_ip) { + if (does_entry_match_name(cn_property->value.data, + cn_property->value.length, name)) { + return 1; + } + } + + return 0; /* Not found. */ +} diff --git a/src/core/lib/tsi/ssl_transport_security.h b/src/core/lib/tsi/ssl_transport_security.h new file mode 100644 index 0000000000..c9b9e8f54b --- /dev/null +++ b/src/core/lib/tsi/ssl_transport_security.h @@ -0,0 +1,174 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TSI_SSL_TRANSPORT_SECURITY_H +#define GRPC_CORE_LIB_TSI_SSL_TRANSPORT_SECURITY_H + +#include "src/core/lib/tsi/transport_security_interface.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Value for the TSI_CERTIFICATE_TYPE_PEER_PROPERTY property for X509 certs. */ +#define TSI_X509_CERTIFICATE_TYPE "X509" + +/* This property is of type TSI_PEER_PROPERTY_STRING. */ +#define TSI_X509_SUBJECT_COMMON_NAME_PEER_PROPERTY "x509_subject_common_name" +#define TSI_X509_SUBJECT_ALTERNATIVE_NAME_PEER_PROPERTY \ + "x509_subject_alternative_name" + +#define TSI_X509_PEM_CERT_PROPERTY "x509_pem_cert" + +#define TSI_SSL_ALPN_SELECTED_PROTOCOL "ssl_alpn_selected_protocol" + +/* --- tsi_ssl_handshaker_factory object --- + + This object creates tsi_handshaker objects implemented in terms of the + TLS 1.2 specificiation. */ + +typedef struct tsi_ssl_handshaker_factory tsi_ssl_handshaker_factory; + +/* Creates a client handshaker factory. + - pem_private_key is the buffer containing the PEM encoding of the client's + private key. This parameter can be NULL if the client does not have a + private key. + - pem_private_key_size is the size of the associated buffer. + - pem_cert_chain is the buffer containing the PEM encoding of the client's + certificate chain. This parameter can be NULL if the client does not have + a certificate chain. + - pem_cert_chain_size is the size of the associated buffer. + - pem_roots_cert is the buffer containing the PEM encoding of the server + root certificates. This parameter cannot be NULL. + - pem_roots_cert_size is the size of the associated buffer. + - cipher_suites contains an optional list of the ciphers that the client + supports. The format of this string is described in: + https://www.openssl.org/docs/apps/ciphers.html. + This parameter can be set to NULL to use the default set of ciphers. + TODO(jboeuf): Revisit the format of this parameter. + - alpn_protocols is an array containing the protocol names that the + handshakers created with this factory support. This parameter can be NULL. + - alpn_protocols_lengths is an array containing the lengths of the alpn + protocols specified in alpn_protocols. This parameter can be NULL. + - num_alpn_protocols is the number of alpn protocols and associated lengths + specified. If this parameter is 0, the other alpn parameters must be NULL. + - factory is the address of the factory pointer to be created. + + - This method returns TSI_OK on success or TSI_INVALID_PARAMETER in the case + where a parameter is invalid. */ +tsi_result tsi_create_ssl_client_handshaker_factory( + const unsigned char *pem_private_key, size_t pem_private_key_size, + const unsigned char *pem_cert_chain, size_t pem_cert_chain_size, + const unsigned char *pem_root_certs, size_t pem_root_certs_size, + const char *cipher_suites, const unsigned char **alpn_protocols, + const unsigned char *alpn_protocols_lengths, uint16_t num_alpn_protocols, + tsi_ssl_handshaker_factory **factory); + +/* Creates a server handshaker factory. + - version indicates which version of the specification to use. + - pem_private_keys is an array containing the PEM encoding of the server's + private keys. This parameter cannot be NULL. The size of the array is + given by the key_cert_pair_count parameter. + - pem_private_keys_sizes is the array containing the sizes of the associated + buffers. + - pem_cert_chains is an array containing the PEM encoding of the server's + cert chains. This parameter cannot be NULL. The size of the array is + given by the key_cert_pair_count parameter. + - pem_cert_chains_sizes is the array containing the sizes of the associated + buffers. + - key_cert_pair_count indicates the number of items in the private_key_files + and cert_chain_files parameters. + - pem_client_roots is the buffer containing the PEM encoding of the client + root certificates. This parameter may be NULL in which case the server will + not authenticate the client. If not NULL, the force_client_auth parameter + specifies if the server will accept only authenticated clients or both + authenticated and non-authenticated clients. + - pem_client_root_certs_size is the size of the associated buffer. + - force_client_auth, if set to non-zero will force the client to authenticate + with an SSL cert. Note that this option is ignored if pem_client_root_certs + is NULL or pem_client_roots_certs_size is 0 + - cipher_suites contains an optional list of the ciphers that the server + supports. The format of this string is described in: + https://www.openssl.org/docs/apps/ciphers.html. + This parameter can be set to NULL to use the default set of ciphers. + TODO(jboeuf): Revisit the format of this parameter. + - alpn_protocols is an array containing the protocol names that the + handshakers created with this factory support. This parameter can be NULL. + - alpn_protocols_lengths is an array containing the lengths of the alpn + protocols specified in alpn_protocols. This parameter can be NULL. + - num_alpn_protocols is the number of alpn protocols and associated lengths + specified. If this parameter is 0, the other alpn parameters must be NULL. + - factory is the address of the factory pointer to be created. + + - This method returns TSI_OK on success or TSI_INVALID_PARAMETER in the case + where a parameter is invalid. */ +tsi_result tsi_create_ssl_server_handshaker_factory( + const unsigned char **pem_private_keys, + const size_t *pem_private_keys_sizes, const unsigned char **pem_cert_chains, + const size_t *pem_cert_chains_sizes, size_t key_cert_pair_count, + const unsigned char *pem_client_root_certs, + size_t pem_client_root_certs_size, int force_client_auth, + const char *cipher_suites, const unsigned char **alpn_protocols, + const unsigned char *alpn_protocols_lengths, uint16_t num_alpn_protocols, + tsi_ssl_handshaker_factory **factory); + +/* Creates a handshaker. + - self is the factory from which the handshaker will be created. + - server_name_indication indicates the name of the server the client is + trying to connect to which will be relayed to the server using the SNI + extension. + This parameter must be NULL for a server handshaker factory. + - handhshaker is the address of the handshaker pointer to be created. + + - This method returns TSI_OK on success or TSI_INVALID_PARAMETER in the case + where a parameter is invalid. */ +tsi_result tsi_ssl_handshaker_factory_create_handshaker( + tsi_ssl_handshaker_factory *self, const char *server_name_indication, + tsi_handshaker **handshaker); + +/* Destroys the handshaker factory. WARNING: it is unsafe to destroy a factory + while handshakers created with this factory are still in use. */ +void tsi_ssl_handshaker_factory_destroy(tsi_ssl_handshaker_factory *self); + +/* Util that checks that an ssl peer matches a specific name. + Still TODO(jboeuf): + - handle mixed case. + - handle %encoded chars. + - handle public suffix wildchar more strictly (e.g. *.co.uk) */ +int tsi_ssl_peer_matches_name(const tsi_peer *peer, const char *name); + +#ifdef __cplusplus +} +#endif + +#endif /* GRPC_CORE_LIB_TSI_SSL_TRANSPORT_SECURITY_H */ diff --git a/src/core/lib/tsi/ssl_types.h b/src/core/lib/tsi/ssl_types.h new file mode 100644 index 0000000000..c6e68c01ad --- /dev/null +++ b/src/core/lib/tsi/ssl_types.h @@ -0,0 +1,55 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TSI_SSL_TYPES_H +#define GRPC_CORE_LIB_TSI_SSL_TYPES_H + +/* A collection of macros to cast between various integer types that are + * used differently between BoringSSL and OpenSSL: + * TSI_INT_AS_SIZE(x): convert 'int x' to a length parameter for an OpenSSL + * function + * TSI_SIZE_AS_SIZE(x): convert 'size_t x' to a length parameter for an OpenSSL + * function + */ + +#include <openssl/ssl.h> + +#ifdef OPENSSL_IS_BORINGSSL +#define TSI_INT_AS_SIZE(x) ((size_t)(x)) +#define TSI_SIZE_AS_SIZE(x) (x) +#else +#define TSI_INT_AS_SIZE(x) (x) +#define TSI_SIZE_AS_SIZE(x) ((int)(x)) +#endif + +#endif /* GRPC_CORE_LIB_TSI_SSL_TYPES_H */ diff --git a/src/core/lib/tsi/test_creds/README b/src/core/lib/tsi/test_creds/README new file mode 100644 index 0000000000..eb8482d648 --- /dev/null +++ b/src/core/lib/tsi/test_creds/README @@ -0,0 +1,62 @@ +The test credentials (CONFIRMEDTESTKEY) have been generated with the following +commands: + +Bad credentials (badclient.* / badserver.*): +============================================ + +These are self-signed certificates: + +$ openssl req -x509 -newkey rsa:1024 -keyout badserver.key -out badserver.pem \ + -days 3650 -nodes + +When prompted for certificate information, everything is default except the +common name which is set to badserver.test.google.com. + + +Valid test credentials: +======================= + +The ca is self-signed: +---------------------- + +$ openssl req -x509 -new -newkey rsa:1024 -nodes -out ca.pem -config ca-openssl.cnf -days 3650 -extensions v3_req +When prompted for certificate information, everything is default. + +client is issued by CA: +----------------------- + +$ openssl genrsa -out client.key.rsa 1024 +$ openssl pkcs8 -topk8 -in client.key.rsa -out client.key -nocrypt +$ rm client.key.rsa +$ openssl req -new -key client.key -out client.csr + +When prompted for certificate information, everything is default except the +common name which is set to testclient. + +$ openssl ca -in client.csr -out client.pem + +server0 is issued by CA: +------------------------ + +$ openssl genrsa -out server0.key.rsa 1024 +$ openssl pkcs8 -topk8 -in server0.key.rsa -out server0.key -nocrypt +$ rm server0.key.rsa +$ openssl req -new -key server0.key -out server0.csr + +When prompted for certificate information, everything is default except the +common name which is set to *.test.google.com.au. + +$ openssl ca -in server0.csr -out server0.pem + +server1 is issued by CA with a special config for subject alternative names: +---------------------------------------------------------------------------- + +$ openssl genrsa -out server1.key.rsa 1024 +$ openssl pkcs8 -topk8 -in server1.key.rsa -out server1.key -nocrypt +$ rm server1.key.rsa +$ openssl req -new -key server1.key -out server1.csr -config server1-openssl.cnf + +When prompted for certificate information, everything is default except the +common name which is set to *.test.google.com. + +$ openssl ca -in server1.csr -out server1.pem diff --git a/src/core/lib/tsi/test_creds/badclient.key b/src/core/lib/tsi/test_creds/badclient.key new file mode 100644 index 0000000000..5832685122 --- /dev/null +++ b/src/core/lib/tsi/test_creds/badclient.key @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALJfYnFn4nkj52WF +E5W2qUxCfjsEFyuXYYKS/07UPWsv3gpZhtjXgdeGL+dpwEBC0IRDBfGnkMp6YY5S +O7rnEz0X3r/fvgYy+dEl2jnaA6zgc7RzMGl9U11d56gP9FiDC2190mvP/hpq2xLZ +CTbIximpmaoQyxuuH1bbYunesIG/AgMBAAECgYAdqJCEzMIyZE7oaW0tOpcB0BiP +FYoIvH4BKRH8eHvR476mt+YdDhBP1scGUmYeCT4Ej+RgHv2LPTgVYwT9eciP2+E/ +CBCNRel0Sw9JepwW0r+jWJtDY1pp6YXAgNRGX2UflvUsT+o9lZvagf9moLTMyGvU +uLFnsyfLim1B4vXvWQJBANouZllXGZoSrZLtR3VgV4tzRQvJxu84kLeIk64Ov47X +pHVBMTRBfzPEhbBodjr1m5OLaVLqkFcXftzRCrbWoKsCQQDRSoLLXOiLrtJ3DLJC +rX7Y8wrHZrqk5bMdZLGa/UX8RanhVw3+Xp+urd1711umeNJfzu/MCk4a1KkG/CU0 +rqs9AkA4cSx1DD1JSG+yxMNpsAS1xJomFIrsM9vsPt7FdndDwrF+y+CovhDkGYDk +RAHh+svGfZg/pQK2JRPimAmHhzqFAkEAu6Ya70s2FUeB3Mu9aJs2CD6hg3dQEVkB +53DI7TX48d9kGW58VX1xnqS02LyWqAPcW5qm1kLHFLdndaPNmBaj4QJBAJugl367 +9d9t/QLTSuULLaoYv2vJT3s1y9HN89EoaDDEkPVfQu6GVEXgIBtim1sI/VPSzI8H +aXvaTUwblFWSM70= +-----END PRIVATE KEY----- diff --git a/src/core/lib/tsi/test_creds/badclient.pem b/src/core/lib/tsi/test_creds/badclient.pem new file mode 100644 index 0000000000..1785970221 --- /dev/null +++ b/src/core/lib/tsi/test_creds/badclient.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICoDCCAgmgAwIBAgIJANIz2/zoRiapMA0GCSqGSIb3DQEBBQUAMGkxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxIjAgBgNVBAMMGWJhZGNsaWVudC50ZXN0Lmdvb2dsZS5j +b20wHhcNMTQwNzI4MjAwODI1WhcNMjQwNzI1MjAwODI1WjBpMQswCQYDVQQGEwJB +VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMSIwIAYDVQQDDBliYWRjbGllbnQudGVzdC5nb29nbGUuY29tMIGf +MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCyX2JxZ+J5I+dlhROVtqlMQn47BBcr +l2GCkv9O1D1rL94KWYbY14HXhi/nacBAQtCEQwXxp5DKemGOUju65xM9F96/374G +MvnRJdo52gOs4HO0czBpfVNdXeeoD/RYgwttfdJrz/4aatsS2Qk2yMYpqZmqEMsb +rh9W22Lp3rCBvwIDAQABo1AwTjAdBgNVHQ4EFgQU523AJMR8Ds9V8fhf7gu1i0MM +UqAwHwYDVR0jBBgwFoAU523AJMR8Ds9V8fhf7gu1i0MMUqAwDAYDVR0TBAUwAwEB +/zANBgkqhkiG9w0BAQUFAAOBgQCI/tvSBYH1iyfLaCTBKwpdj36+MkR9EeJJmImx +X+bjhKWXwsBX4PDMWvdusr++QGUYtyoya+hfYMXRhXua39mD54xgloQNuu9REDwX +Ffto+aOw3BcYducz6ofxicFK/Y2VeXDurSMpRv5TfGf2Qr6eOOdaRhj6ed7BibHk +X1VGZA== +-----END CERTIFICATE----- diff --git a/src/core/lib/tsi/test_creds/badserver.key b/src/core/lib/tsi/test_creds/badserver.key new file mode 100644 index 0000000000..abfbde10ff --- /dev/null +++ b/src/core/lib/tsi/test_creds/badserver.key @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKeZ1e1y29cmBKaW +oIUwJ5neOJUjx+eD/3nRPe+dvLXEd9+db0fG5RYRR0S3mF1Ywuj4PIxlTW2YprUS +oGSw+tcqWNIzxv94HjwYFkkvER3AblXcDBh0P2zAkzg+nf9AcAsMh0QpDTyrXtMl +gqryjq1/vkhFofKMMbY+aXJdG6OBAgMBAAECgYAAgaB51S0A22aMMkxN2rVj6530 +JWWHN4jgD1fGj41wZyWNkWYyq1Ep3ed/N6bIMWp1VbqpGe0/9YQba/D8HOTFHGRt +72YXnP1e/ds8cxU4x4j1vvqSPtXpMmkiXfXijOvCl9mrMH2xjghFAt6/1Nb9xo1m +VdcOB8OdSuOIw6CI+QJBAN5FZUbS+bRXDWII/FaAih1DBpwCxhYEN+TXPJBxSen6 +kOzGt5g+mB6YqRMZ/qshshwPq7bsgFGfJ2lIdS2t3GsCQQDBCKifV5AAkOdOUrkK +HvoX3qnVmyIA8CyvWLcIWpfZ76QAYh0q0StedKdOMXaB1jTeSJ2KU1nlss7UD1Yw +VbrDAkAwjMHpbW3jiVw//Kx5jIwehiRscWKpLnSzBJyTBFvbwsJjJai2lX2OuVO8 ++2GYKb0Iyhd81j3VFkl6grwtpRtPAkB7+n+yt555fpfRKjhGU9b09cHGu7h/OcK5 +bBVCfE0DYHLI/DsXgPiF1g6Onh4rDdUu3xyv9xDKAqnscV099hHZAkEAvcFBfXZs +tk18N+bUcvXTdZjzZbfLCHlJmwPIspZ8G/6Pn63deg4GVYoCvTwGruah+8y734Ph +7PskfPgUQlB7Ag== +-----END PRIVATE KEY----- diff --git a/src/core/lib/tsi/test_creds/badserver.pem b/src/core/lib/tsi/test_creds/badserver.pem new file mode 100644 index 0000000000..983c979f31 --- /dev/null +++ b/src/core/lib/tsi/test_creds/badserver.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICoDCCAgmgAwIBAgIJAPdqwqsKNy81MA0GCSqGSIb3DQEBBQUAMGkxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxIjAgBgNVBAMMGWJhZHNlcnZlci50ZXN0Lmdvb2dsZS5j +b20wHhcNMTQwNzI4MjAwODU0WhcNMjQwNzI1MjAwODU0WjBpMQswCQYDVQQGEwJB +VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMSIwIAYDVQQDDBliYWRzZXJ2ZXIudGVzdC5nb29nbGUuY29tMIGf +MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnmdXtctvXJgSmlqCFMCeZ3jiVI8fn +g/950T3vnby1xHffnW9HxuUWEUdEt5hdWMLo+DyMZU1tmKa1EqBksPrXKljSM8b/ +eB48GBZJLxEdwG5V3AwYdD9swJM4Pp3/QHALDIdEKQ08q17TJYKq8o6tf75IRaHy +jDG2PmlyXRujgQIDAQABo1AwTjAdBgNVHQ4EFgQU3u/qvHr9knMBeZyAD7mAA/ec +8cUwHwYDVR0jBBgwFoAU3u/qvHr9knMBeZyAD7mAA/ec8cUwDAYDVR0TBAUwAwEB +/zANBgkqhkiG9w0BAQUFAAOBgQA/FmR1SGLguxCCfhp4CYCbrAePSyPWDi48gTwj +vVZf/OMxdVu/H8sBYFf27BjbrEugAw16DElFtgTZ83pLb2BvkUgb6vBUK5sEkgmh +z88zBsgDp8aCf4STDOLFZMBh/E9ZKkm1zogbEmlTjFp/ceSpa2gNv7OuN4WiorOh +Wvw40g== +-----END CERTIFICATE----- diff --git a/src/core/lib/tsi/test_creds/ca-openssl.cnf b/src/core/lib/tsi/test_creds/ca-openssl.cnf new file mode 100644 index 0000000000..e97b945e4b --- /dev/null +++ b/src/core/lib/tsi/test_creds/ca-openssl.cnf @@ -0,0 +1,17 @@ +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_req + +[req_distinguished_name] +countryName = Country Name (2 letter code) +countryName_default = AU +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = Some-State +organizationName = Organization Name (eg, company) +organizationName_default = Internet Widgits Pty Ltd +commonName = Common Name (eg, YOUR name) +commonName_default = testca + +[v3_req] +basicConstraints = CA:true +keyUsage = critical, keyCertSign diff --git a/src/core/lib/tsi/test_creds/ca.key b/src/core/lib/tsi/test_creds/ca.key new file mode 100644 index 0000000000..03c4f950e3 --- /dev/null +++ b/src/core/lib/tsi/test_creds/ca.key @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMBA3wVeTGHZR1Ry +e/i+J8a2cu5gXwFV6TnObzGM7bLFCO5i9v4mLo4iFzPsHmWDUxKS3Y8iXbu0eYBl +LoNY0lSvxDx33O+DuwMmVN+DzSD+Eod9zfvwOWHsazYCZT2PhNxnVWIuJXViY4JA +HUGodjx+QAi6yCAurUZGvYXGgZSBAgMBAAECgYAxRi8i9BlFlufGSBVoGmydbJOm +bwLKl9dP3o33ODSP9hok5y6A0w5plWk3AJSF1hPLleK9VcSKYGYnt0clmPVHF35g +bx2rVK8dOT0mn7rz9Zr70jcSz1ETA2QonHZ+Y+niLmcic9At6hRtWiewblUmyFQm +GwggIzi7LOyEUHrEcQJBAOXxyQvnLvtKzXiqcsW/K6rExqVJVk+KF0fzzVyMzTJx +HRBxUVgvGdEJT7j+7P2kcTyafve0BBzDSPIaDyiJ+Y0CQQDWCb7jASFSbu5M3Zcd +Gkr4ZKN1XO3VLQX10b22bQYdF45hrTN2tnzRvVUR4q86VVnXmiGiTqmLkXcA2WWf +pHfFAkAhv9olUBo6MeF0i3frBEMRfm41hk0PwZHnMqZ6pgPcGnQMnMU2rzsXzkkQ +OwJnvAIOxhJKovZTjmofdqmw5odlAkBYVUdRWjsNUTjJwj3GRf6gyq/nFMYWz3EB +RWFdM1ttkDYzu45ctO2IhfHg4sPceDMO1s6AtKQmNI9/azkUjITdAkApNa9yFRzc +TBaDNPd5KVd58LVIzoPQ6i7uMHteLXJUWqSroji6S3s4gKMFJ/dO+ZXIlgQgfJJJ +ZDL4cdrdkeoM +-----END PRIVATE KEY----- diff --git a/src/core/lib/tsi/test_creds/ca.pem b/src/core/lib/tsi/test_creds/ca.pem new file mode 100644 index 0000000000..6c8511a73c --- /dev/null +++ b/src/core/lib/tsi/test_creds/ca.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla +Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0 +YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT +BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7 ++L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu +g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd +Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau +sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m +oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG +Dfcog5wrJytaQ6UA0wE= +-----END CERTIFICATE----- diff --git a/src/core/lib/tsi/test_creds/client.key b/src/core/lib/tsi/test_creds/client.key new file mode 100644 index 0000000000..f48d0735d9 --- /dev/null +++ b/src/core/lib/tsi/test_creds/client.key @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICeQIBADANBgkqhkiG9w0BAQEFAASCAmMwggJfAgEAAoGBAOxUR9uhvhbeVUIM +s5WbH0px0mehl2+6sZpNjzvE2KimZpHzMJHukVH0Ffkvhs0b8+S5Ut9VNUAqd3IM +JCCAEGtRNoQhM1t9Yr2zAckSvbRacp+FL/Cj9eDmyo00KsVGaeefA4Dh4OW+ZhkT +NKcldXqkSuj1sEf244JZYuqZp6/tAgMBAAECgYEAi2NSVqpZMafE5YYUTcMGe6QS +k2jtpsqYgggI2RnLJ/2tNZwYI5pwP8QVSbnMaiF4gokD5hGdrNDfTnb2v+yIwYEH +0w8+oG7Z81KodsiZSIDJfTGsAZhVNwOz9y0VD8BBZZ1/274Zh52AUKLjZS/ZwIbS +W2ywya855dPnH/wj+0ECQQD9X8D920kByTNHhBG18biAEZ4pxs9f0OAG8333eVcI +w2lJDLsYDZrCB2ocgA3lUdozlzPC7YDYw8reg0tkiRY5AkEA7sdNzOeQsQRn7++5 +0bP9DtT/iON1gbfxRzCfCfXdoOtfQWIzTePWtURt9X/5D9NofI0Rg5W2oGy/MLe5 +/sXHVQJBAIup5XrJDkQywNZyAUU2ecn2bCWBFjwtqd+LBmuMciI9fOKsZtEKZrz/ +U0lkeMRoSwvXE8wmGLjjrAbdfohrXFkCQQDZEx/LtIl6JINJQiswVe0tWr6k+ASP +1WXoTm+HYpoF/XUvv9LccNF1IazFj34hwRQwhx7w/V52Ieb+p0jUMYGxAkEAjDhd +9pBO1fKXWiXzi9ZKfoyTNcUq3eBSVKwPG2nItg5ycXengjT5sgcWDnciIzW7BIVI +JiqOszq9GWESErAatg== +-----END PRIVATE KEY----- diff --git a/src/core/lib/tsi/test_creds/client.pem b/src/core/lib/tsi/test_creds/client.pem new file mode 100644 index 0000000000..e332091019 --- /dev/null +++ b/src/core/lib/tsi/test_creds/client.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICHzCCAYgCAQEwDQYJKoZIhvcNAQEFBQAwVjELMAkGA1UEBhMCQVUxEzARBgNV +BAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0 +ZDEPMA0GA1UEAwwGdGVzdGNhMB4XDTE0MDcxNzIzNTYwMloXDTI0MDcxNDIzNTYw +MlowWjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UEAwwKdGVzdGNsaWVudDCB +nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA7FRH26G+Ft5VQgyzlZsfSnHSZ6GX +b7qxmk2PO8TYqKZmkfMwke6RUfQV+S+GzRvz5LlS31U1QCp3cgwkIIAQa1E2hCEz +W31ivbMByRK9tFpyn4Uv8KP14ObKjTQqxUZp558DgOHg5b5mGRM0pyV1eqRK6PWw +R/bjglli6pmnr+0CAwEAATANBgkqhkiG9w0BAQUFAAOBgQAStSm5PM7ubROiKK6/ +T2FkKlhiTOx+Ryenm3Eio59emq+jXl+1nhPySX5G2PQzSR5vd1dIhwgZSR4Gyttk +tRZ57k/NI1brUW8joiEOMJA/Mr7H7asx7wIRYDE91Fs8GkKWd5LhoPAQj+qdG35C +OO+svdkmqH0KZo320ZUqdl2ooQ== +-----END CERTIFICATE----- diff --git a/src/core/lib/tsi/test_creds/server0.key b/src/core/lib/tsi/test_creds/server0.key new file mode 100644 index 0000000000..add153c9ae --- /dev/null +++ b/src/core/lib/tsi/test_creds/server0.key @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANOmffupIGC8YDau +rOF4eKnHwPszgpkkhWzKsVxhNDBxCVYx4TEjG0XWIO0iyRXupZbUC+7N/8HnEVNa +8F1jYhng14Iiq99cNQbbnuHHhIztmpocrJTxmnhGzoAnRa1Tb+GnAuRoIHRA/V2c +VUE9tbikQugFx/SPgXAw6tfWB+YvAgMBAAECgYEAoEq9qzUBgoHoVEGiSPiWWe8g +5p6yUA1qx2QTQyWTAwT4z0DjjfVKmG99bFsl8+hTnJFnoCp/gnjflEOROwkjp5kG +m0drqOPx1jeipJjpXYTBu49h+WpZ1PF+KhVtxsIm3OOCvh67iWaKyyOVb5Og8aiR +jl6dn/TdG/dlGD8AfUECQQDuNMle6p0oU8amC6O9wIMBroxx2nFstzE6O35PLEzG +/tj0kxxn9Jp2TS9mGaLCzSuXmpjlF4+NOWiBPkrLC2TfAkEA43Xg7uEUkaJAz2/W +m1lIBTLt+4rIQY/2emh33bDcA+rv8rwwrMMIv17/xPx7bs49YqGG5xufD+Rwl6TL +qFXYsQJAPrOwagax1aKvwJeBw3oAQhoTKAkLIEXcdGqipe6QSzVcIIz0xjxxyEAr +AOIwoLxnBCISqwMXq2H4K0UdZPMb2wJAdhdYLY1L6YRMk6XjzImg25oidisKZweA +FvMv8DgHMj2CUAqmVrt3SivfLH1M9C09L3zfFhOAFHcsgX58gav4MQJBANSBnrHj +tIq4l8z79CPUIuu3QyeEh+XwY8s5qE5CNTck0U59lzp9NvENHbkx3KO896TTerko ++8bXHMLkJkHPXms= +-----END PRIVATE KEY----- diff --git a/src/core/lib/tsi/test_creds/server0.pem b/src/core/lib/tsi/test_creds/server0.pem new file mode 100644 index 0000000000..ade75d8563 --- /dev/null +++ b/src/core/lib/tsi/test_creds/server0.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICHDCCAYUCAQQwDQYJKoZIhvcNAQEFBQAwVjELMAkGA1UEBhMCQVUxEzARBgNV +BAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0 +ZDEPMA0GA1UEAwwGdGVzdGNhMB4XDTE0MDcyMjE3NTk0OVoXDTI0MDcxOTE3NTk0 +OVowVzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxFDASBgNVBAoM +C0dvb2dsZSBJbmMuMR0wGwYDVQQDDBQqLnRlc3QuZ29vZ2xlLmNvbS5hdTCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA06Z9+6kgYLxgNq6s4Xh4qcfA+zOCmSSF +bMqxXGE0MHEJVjHhMSMbRdYg7SLJFe6lltQL7s3/wecRU1rwXWNiGeDXgiKr31w1 +Btue4ceEjO2amhyslPGaeEbOgCdFrVNv4acC5GggdED9XZxVQT21uKRC6AXH9I+B +cDDq19YH5i8CAwEAATANBgkqhkiG9w0BAQUFAAOBgQBtfR5qXG9TTI8YcYh7sA4V +GeNoplp0x6p7OG0NLvbJqAkUnkvjIkk1m1R2AUHhbkxzx6G75JIOoNJcWrCzywBA +BIsaTdmnNysf/s1hQJuD3IHiVb+7Ji0jhttnJlYcMid4o0tJO/a2E9YUxR+9cg0i +obb+Ql3qsvKdWBC1dDLDLw== +-----END CERTIFICATE----- diff --git a/src/core/lib/tsi/test_creds/server1-openssl.cnf b/src/core/lib/tsi/test_creds/server1-openssl.cnf new file mode 100644 index 0000000000..8a02108289 --- /dev/null +++ b/src/core/lib/tsi/test_creds/server1-openssl.cnf @@ -0,0 +1,26 @@ +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_req + +[req_distinguished_name] +countryName = Country Name (2 letter code) +countryName_default = US +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = Illinois +localityName = Locality Name (eg, city) +localityName_default = Chicago +organizationName = Organization Name (eg, company) +organizationName_default = Example, Co. +commonName = Common Name (eg, YOUR name) +commonName_max = 64 + +[v3_req] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName = @alt_names + +[alt_names] +DNS.1 = *.test.google.fr +DNS.2 = waterzooi.test.google.be +DNS.3 = *.test.youtube.com +IP.1 = "192.168.1.3" diff --git a/src/core/lib/tsi/test_creds/server1.key b/src/core/lib/tsi/test_creds/server1.key new file mode 100644 index 0000000000..143a5b8765 --- /dev/null +++ b/src/core/lib/tsi/test_creds/server1.key @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOHDFScoLCVJpYDD +M4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1BgzkWF+slf +3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd9N8YwbBY +AckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAECgYAn7qGnM2vbjJNBm0VZCkOkTIWm +V10okw7EPJrdL2mkre9NasghNXbE1y5zDshx5Nt3KsazKOxTT8d0Jwh/3KbaN+YY +tTCbKGW0pXDRBhwUHRcuRzScjli8Rih5UOCiZkhefUTcRb6xIhZJuQy71tjaSy0p +dHZRmYyBYO2YEQ8xoQJBAPrJPhMBkzmEYFtyIEqAxQ/o/A6E+E4w8i+KM7nQCK7q +K4JXzyXVAjLfyBZWHGM2uro/fjqPggGD6QH1qXCkI4MCQQDmdKeb2TrKRh5BY1LR +81aJGKcJ2XbcDu6wMZK4oqWbTX2KiYn9GB0woM6nSr/Y6iy1u145YzYxEV/iMwff +DJULAkB8B2MnyzOg0pNFJqBJuH29bKCcHa8gHJzqXhNO5lAlEbMK95p/P2Wi+4Hd +aiEIAF1BF326QJcvYKmwSmrORp85AkAlSNxRJ50OWrfMZnBgzVjDx3xG6KsFQVk2 +ol6VhqL6dFgKUORFUWBvnKSyhjJxurlPEahV6oo6+A+mPhFY8eUvAkAZQyTdupP3 +XEFQKctGz+9+gKkemDp7LBBMEMBXrGTLPhpEfcjv/7KPdnFHYmhYeBTBnuVmTVWe +F98XJ7tIFfJq +-----END PRIVATE KEY----- diff --git a/src/core/lib/tsi/test_creds/server1.pem b/src/core/lib/tsi/test_creds/server1.pem new file mode 100644 index 0000000000..f3d43fcc5b --- /dev/null +++ b/src/core/lib/tsi/test_creds/server1.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICnDCCAgWgAwIBAgIBBzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJBVTET +MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ +dHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2EwHhcNMTUxMTA0MDIyMDI0WhcNMjUxMTAx +MDIyMDI0WjBlMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV +BAcTB0NoaWNhZ28xFTATBgNVBAoTDEV4YW1wbGUsIENvLjEaMBgGA1UEAxQRKi50 +ZXN0Lmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOHDFSco +LCVJpYDDM4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1Bg +zkWF+slf3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd +9N8YwbBYAckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAGjazBpMAkGA1UdEwQCMAAw +CwYDVR0PBAQDAgXgME8GA1UdEQRIMEaCECoudGVzdC5nb29nbGUuZnKCGHdhdGVy +em9vaS50ZXN0Lmdvb2dsZS5iZYISKi50ZXN0LnlvdXR1YmUuY29thwTAqAEDMA0G +CSqGSIb3DQEBCwUAA4GBAJFXVifQNub1LUP4JlnX5lXNlo8FxZ2a12AFQs+bzoJ6 +hM044EDjqyxUqSbVePK0ni3w1fHQB5rY9yYC5f8G7aqqTY1QOhoUk8ZTSTRpnkTh +y4jjdvTZeLDVBlueZUTDRmy2feY5aZIU18vFDK08dTG0A87pppuv1LNIR3loveU8 +-----END CERTIFICATE----- diff --git a/src/core/lib/tsi/transport_security.c b/src/core/lib/tsi/transport_security.c new file mode 100644 index 0000000000..a2c0d46196 --- /dev/null +++ b/src/core/lib/tsi/transport_security.c @@ -0,0 +1,284 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "src/core/lib/tsi/transport_security.h" + +#include <stdlib.h> +#include <string.h> + +/* --- Tracing. --- */ + +int tsi_tracing_enabled = 0; + +/* --- Utils. --- */ + +char *tsi_strdup(const char *src) { + char *dst; + size_t len; + if (!src) return NULL; + len = strlen(src) + 1; + dst = malloc(len); + if (!dst) return NULL; + memcpy(dst, src, len); + return dst; +} + +/* --- tsi_result common implementation. --- */ + +const char *tsi_result_to_string(tsi_result result) { + switch (result) { + case TSI_OK: + return "TSI_OK"; + case TSI_UNKNOWN_ERROR: + return "TSI_UNKNOWN_ERROR"; + case TSI_INVALID_ARGUMENT: + return "TSI_INVALID_ARGUMENT"; + case TSI_PERMISSION_DENIED: + return "TSI_PERMISSION_DENIED"; + case TSI_INCOMPLETE_DATA: + return "TSI_INCOMPLETE_DATA"; + case TSI_FAILED_PRECONDITION: + return "TSI_FAILED_PRECONDITION"; + case TSI_UNIMPLEMENTED: + return "TSI_UNIMPLEMENTED"; + case TSI_INTERNAL_ERROR: + return "TSI_INTERNAL_ERROR"; + case TSI_DATA_CORRUPTED: + return "TSI_DATA_CORRUPTED"; + case TSI_NOT_FOUND: + return "TSI_NOT_FOUND"; + case TSI_PROTOCOL_FAILURE: + return "TSI_PROTOCOL_FAILURE"; + case TSI_HANDSHAKE_IN_PROGRESS: + return "TSI_HANDSHAKE_IN_PROGRESS"; + case TSI_OUT_OF_RESOURCES: + return "TSI_OUT_OF_RESOURCES"; + default: + return "UNKNOWN"; + } +} + +/* --- tsi_frame_protector common implementation. --- + + Calls specific implementation after state/input validation. */ + +tsi_result tsi_frame_protector_protect(tsi_frame_protector *self, + const unsigned char *unprotected_bytes, + size_t *unprotected_bytes_size, + unsigned char *protected_output_frames, + size_t *protected_output_frames_size) { + if (self == NULL || unprotected_bytes == NULL || + unprotected_bytes_size == NULL || protected_output_frames == NULL || + protected_output_frames_size == NULL) { + return TSI_INVALID_ARGUMENT; + } + return self->vtable->protect(self, unprotected_bytes, unprotected_bytes_size, + protected_output_frames, + protected_output_frames_size); +} + +tsi_result tsi_frame_protector_protect_flush( + tsi_frame_protector *self, unsigned char *protected_output_frames, + size_t *protected_output_frames_size, size_t *still_pending_size) { + if (self == NULL || protected_output_frames == NULL || + protected_output_frames == NULL || still_pending_size == NULL) { + return TSI_INVALID_ARGUMENT; + } + return self->vtable->protect_flush(self, protected_output_frames, + protected_output_frames_size, + still_pending_size); +} + +tsi_result tsi_frame_protector_unprotect( + tsi_frame_protector *self, const unsigned char *protected_frames_bytes, + size_t *protected_frames_bytes_size, unsigned char *unprotected_bytes, + size_t *unprotected_bytes_size) { + if (self == NULL || protected_frames_bytes == NULL || + protected_frames_bytes_size == NULL || unprotected_bytes == NULL || + unprotected_bytes_size == NULL) { + return TSI_INVALID_ARGUMENT; + } + return self->vtable->unprotect(self, protected_frames_bytes, + protected_frames_bytes_size, unprotected_bytes, + unprotected_bytes_size); +} + +void tsi_frame_protector_destroy(tsi_frame_protector *self) { + if (self == NULL) return; + self->vtable->destroy(self); +} + +/* --- tsi_handshaker common implementation. --- + + Calls specific implementation after state/input validation. */ + +tsi_result tsi_handshaker_get_bytes_to_send_to_peer(tsi_handshaker *self, + unsigned char *bytes, + size_t *bytes_size) { + if (self == NULL || bytes == NULL || bytes_size == NULL) { + return TSI_INVALID_ARGUMENT; + } + if (self->frame_protector_created) return TSI_FAILED_PRECONDITION; + return self->vtable->get_bytes_to_send_to_peer(self, bytes, bytes_size); +} + +tsi_result tsi_handshaker_process_bytes_from_peer(tsi_handshaker *self, + const unsigned char *bytes, + size_t *bytes_size) { + if (self == NULL || bytes == NULL || bytes_size == NULL) { + return TSI_INVALID_ARGUMENT; + } + if (self->frame_protector_created) return TSI_FAILED_PRECONDITION; + return self->vtable->process_bytes_from_peer(self, bytes, bytes_size); +} + +tsi_result tsi_handshaker_get_result(tsi_handshaker *self) { + if (self == NULL) return TSI_INVALID_ARGUMENT; + if (self->frame_protector_created) return TSI_FAILED_PRECONDITION; + return self->vtable->get_result(self); +} + +tsi_result tsi_handshaker_extract_peer(tsi_handshaker *self, tsi_peer *peer) { + if (self == NULL || peer == NULL) return TSI_INVALID_ARGUMENT; + memset(peer, 0, sizeof(tsi_peer)); + if (self->frame_protector_created) return TSI_FAILED_PRECONDITION; + if (tsi_handshaker_get_result(self) != TSI_OK) { + return TSI_FAILED_PRECONDITION; + } + return self->vtable->extract_peer(self, peer); +} + +tsi_result tsi_handshaker_create_frame_protector( + tsi_handshaker *self, size_t *max_protected_frame_size, + tsi_frame_protector **protector) { + tsi_result result; + if (self == NULL || protector == NULL) return TSI_INVALID_ARGUMENT; + if (self->frame_protector_created) return TSI_FAILED_PRECONDITION; + if (tsi_handshaker_get_result(self) != TSI_OK) { + return TSI_FAILED_PRECONDITION; + } + result = self->vtable->create_frame_protector(self, max_protected_frame_size, + protector); + if (result == TSI_OK) { + self->frame_protector_created = 1; + } + return result; +} + +void tsi_handshaker_destroy(tsi_handshaker *self) { + if (self == NULL) return; + self->vtable->destroy(self); +} + +/* --- tsi_peer implementation. --- */ + +tsi_peer_property tsi_init_peer_property(void) { + tsi_peer_property property; + memset(&property, 0, sizeof(tsi_peer_property)); + return property; +} + +static void tsi_peer_destroy_list_property(tsi_peer_property *children, + size_t child_count) { + size_t i; + for (i = 0; i < child_count; i++) { + tsi_peer_property_destruct(&children[i]); + } + free(children); +} + +void tsi_peer_property_destruct(tsi_peer_property *property) { + if (property->name != NULL) { + free(property->name); + } + if (property->value.data != NULL) { + free(property->value.data); + } + *property = tsi_init_peer_property(); /* Reset everything to 0. */ +} + +void tsi_peer_destruct(tsi_peer *self) { + if (self == NULL) return; + if (self->properties != NULL) { + tsi_peer_destroy_list_property(self->properties, self->property_count); + self->properties = NULL; + } + self->property_count = 0; +} + +tsi_result tsi_construct_allocated_string_peer_property( + const char *name, size_t value_length, tsi_peer_property *property) { + *property = tsi_init_peer_property(); + if (name != NULL) { + property->name = tsi_strdup(name); + if (property->name == NULL) return TSI_OUT_OF_RESOURCES; + } + if (value_length > 0) { + property->value.data = calloc(1, value_length); + if (property->value.data == NULL) { + tsi_peer_property_destruct(property); + return TSI_OUT_OF_RESOURCES; + } + property->value.length = value_length; + } + return TSI_OK; +} + +tsi_result tsi_construct_string_peer_property_from_cstring( + const char *name, const char *value, tsi_peer_property *property) { + return tsi_construct_string_peer_property(name, value, strlen(value), + property); +} + +tsi_result tsi_construct_string_peer_property(const char *name, + const char *value, + size_t value_length, + tsi_peer_property *property) { + tsi_result result = tsi_construct_allocated_string_peer_property( + name, value_length, property); + if (result != TSI_OK) return result; + if (value_length > 0) { + memcpy(property->value.data, value, value_length); + } + return TSI_OK; +} + +tsi_result tsi_construct_peer(size_t property_count, tsi_peer *peer) { + memset(peer, 0, sizeof(tsi_peer)); + if (property_count > 0) { + peer->properties = calloc(property_count, sizeof(tsi_peer_property)); + if (peer->properties == NULL) return TSI_OUT_OF_RESOURCES; + peer->property_count = property_count; + } + return TSI_OK; +} diff --git a/src/core/lib/tsi/transport_security.h b/src/core/lib/tsi/transport_security.h new file mode 100644 index 0000000000..349dd0ae9c --- /dev/null +++ b/src/core/lib/tsi/transport_security.h @@ -0,0 +1,111 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TSI_TRANSPORT_SECURITY_H +#define GRPC_CORE_LIB_TSI_TRANSPORT_SECURITY_H + +#include "src/core/lib/tsi/transport_security_interface.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int tsi_tracing_enabled; + +/* Base for tsi_frame_protector implementations. + See transport_security_interface.h for documentation. */ +typedef struct { + tsi_result (*protect)(tsi_frame_protector *self, + const unsigned char *unprotected_bytes, + size_t *unprotected_bytes_size, + unsigned char *protected_output_frames, + size_t *protected_output_frames_size); + tsi_result (*protect_flush)(tsi_frame_protector *self, + unsigned char *protected_output_frames, + size_t *protected_output_frames_size, + size_t *still_pending_size); + tsi_result (*unprotect)(tsi_frame_protector *self, + const unsigned char *protected_frames_bytes, + size_t *protected_frames_bytes_size, + unsigned char *unprotected_bytes, + size_t *unprotected_bytes_size); + void (*destroy)(tsi_frame_protector *self); +} tsi_frame_protector_vtable; + +struct tsi_frame_protector { + const tsi_frame_protector_vtable *vtable; +}; + +/* Base for tsi_handshaker implementations. + See transport_security_interface.h for documentation. */ +typedef struct { + tsi_result (*get_bytes_to_send_to_peer)(tsi_handshaker *self, + unsigned char *bytes, + size_t *bytes_size); + tsi_result (*process_bytes_from_peer)(tsi_handshaker *self, + const unsigned char *bytes, + size_t *bytes_size); + tsi_result (*get_result)(tsi_handshaker *self); + tsi_result (*extract_peer)(tsi_handshaker *self, tsi_peer *peer); + tsi_result (*create_frame_protector)(tsi_handshaker *self, + size_t *max_protected_frame_size, + tsi_frame_protector **protector); + void (*destroy)(tsi_handshaker *self); +} tsi_handshaker_vtable; + +struct tsi_handshaker { + const tsi_handshaker_vtable *vtable; + int frame_protector_created; +}; + +/* Peer and property construction/destruction functions. */ +tsi_result tsi_construct_peer(size_t property_count, tsi_peer *peer); +tsi_peer_property tsi_init_peer_property(void); +void tsi_peer_property_destruct(tsi_peer_property *property); +tsi_result tsi_construct_string_peer_property(const char *name, + const char *value, + size_t value_length, + tsi_peer_property *property); +tsi_result tsi_construct_allocated_string_peer_property( + const char *name, size_t value_length, tsi_peer_property *property); +tsi_result tsi_construct_string_peer_property_from_cstring( + const char *name, const char *value, tsi_peer_property *property); + +/* Utils. */ +char *tsi_strdup(const char *src); /* Sadly, no strdup in C89. */ + +#ifdef __cplusplus +} +#endif + +#endif /* GRPC_CORE_LIB_TSI_TRANSPORT_SECURITY_H */ diff --git a/src/core/lib/tsi/transport_security_interface.h b/src/core/lib/tsi/transport_security_interface.h new file mode 100644 index 0000000000..f88f1516a9 --- /dev/null +++ b/src/core/lib/tsi/transport_security_interface.h @@ -0,0 +1,344 @@ +/* + * + * Copyright 2015-2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_CORE_LIB_TSI_TRANSPORT_SECURITY_INTERFACE_H +#define GRPC_CORE_LIB_TSI_TRANSPORT_SECURITY_INTERFACE_H + +#include <stdint.h> +#include <stdlib.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* --- tsi result --- */ + +typedef enum { + TSI_OK = 0, + TSI_UNKNOWN_ERROR = 1, + TSI_INVALID_ARGUMENT = 2, + TSI_PERMISSION_DENIED = 3, + TSI_INCOMPLETE_DATA = 4, + TSI_FAILED_PRECONDITION = 5, + TSI_UNIMPLEMENTED = 6, + TSI_INTERNAL_ERROR = 7, + TSI_DATA_CORRUPTED = 8, + TSI_NOT_FOUND = 9, + TSI_PROTOCOL_FAILURE = 10, + TSI_HANDSHAKE_IN_PROGRESS = 11, + TSI_OUT_OF_RESOURCES = 12 +} tsi_result; + +const char *tsi_result_to_string(tsi_result result); + +/* --- tsi tracing --- */ + +/* Set this early to avoid races */ +extern int tsi_tracing_enabled; + +/* --- tsi_frame_protector object --- + + This object protects and unprotects buffers once the handshake is done. + Implementations of this object must be thread compatible. */ + +typedef struct tsi_frame_protector tsi_frame_protector; + +/* Outputs protected frames. + - unprotected_bytes is an input only parameter and points to the data + to be protected. + - unprotected_bytes_size is an input/output parameter used by the caller to + specify how many bytes are available in unprotected_bytes. The output + value is the number of bytes consumed during the call. + - protected_output_frames points to a buffer allocated by the caller that + will be written. + - protected_output_frames_size is an input/output parameter used by the + caller to specify how many bytes are available in protected_output_frames. + As an output, this value indicates the number of bytes written. + - This method returns TSI_OK in case of success or a specific error code in + case of failure. Note that even if all the input unprotected bytes are + consumed, they may not have been processed into the returned protected + output frames. The caller should call the protect_flush method + to make sure that there are no more protected bytes buffered in the + protector. + + A typical way to call this method would be: + + ------------------------------------------------------------------------ + unsigned char protected_buffer[4096]; + size_t protected_buffer_size = sizeof(protected_buffer); + tsi_result result = TSI_OK; + while (message_size > 0) { + size_t protected_buffer_size_to_send = protected_buffer_size; + size_t processed_message_size = message_size; + result = tsi_frame_protector_protect(protector, + message_bytes, + &processed_message_size, + protected_buffer, + &protected_buffer_size_to_send); + if (result != TSI_OK) break; + send_bytes_to_peer(protected_buffer, protected_buffer_size_to_send); + message_bytes += processed_message_size; + message_size -= processed_message_size; + + // Don't forget to flush. + if (message_size == 0) { + size_t still_pending_size; + do { + protected_buffer_size_to_send = protected_buffer_size; + result = tsi_frame_protector_protect_flush( + protector, protected_buffer, + &protected_buffer_size_to_send, &still_pending_size); + if (result != TSI_OK) break; + send_bytes_to_peer(protected_buffer, protected_buffer_size_to_send); + } while (still_pending_size > 0); + } + } + + if (result != TSI_OK) HandleError(result); + ------------------------------------------------------------------------ */ +tsi_result tsi_frame_protector_protect(tsi_frame_protector *self, + const unsigned char *unprotected_bytes, + size_t *unprotected_bytes_size, + unsigned char *protected_output_frames, + size_t *protected_output_frames_size); + +/* Indicates that we need to flush the bytes buffered in the protector and get + the resulting frame. + - protected_output_frames points to a buffer allocated by the caller that + will be written. + - protected_output_frames_size is an input/output parameter used by the + caller to specify how many bytes are available in protected_output_frames. + - still_pending_bytes is an output parameter indicating the number of bytes + that still need to be flushed from the protector.*/ +tsi_result tsi_frame_protector_protect_flush( + tsi_frame_protector *self, unsigned char *protected_output_frames, + size_t *protected_output_frames_size, size_t *still_pending_size); + +/* Outputs unprotected bytes. + - protected_frames_bytes is an input only parameter and points to the + protected frames to be unprotected. + - protected_frames_bytes_size is an input/output only parameter used by the + caller to specify how many bytes are available in protected_bytes. The + output value is the number of bytes consumed during the call. + Implementations will buffer up to a frame of protected data. + - unprotected_bytes points to a buffer allocated by the caller that will be + written. + - unprotected_bytes_size is an input/output parameter used by the caller to + specify how many bytes are available in unprotected_bytes. This + value is expected to be at most max_protected_frame_size minus overhead + which means that max_protected_frame_size is a safe bet. The output value + is the number of bytes actually written. + If *unprotected_bytes_size is unchanged, there may be more data remaining + to unprotect, and the caller should call this function again. + + - This method returns TSI_OK in case of success. Success includes cases where + there is not enough data to output a frame in which case + unprotected_bytes_size will be set to 0 and cases where the internal buffer + needs to be read before new protected data can be processed in which case + protected_frames_size will be set to 0. */ +tsi_result tsi_frame_protector_unprotect( + tsi_frame_protector *self, const unsigned char *protected_frames_bytes, + size_t *protected_frames_bytes_size, unsigned char *unprotected_bytes, + size_t *unprotected_bytes_size); + +/* Destroys the tsi_frame_protector object. */ +void tsi_frame_protector_destroy(tsi_frame_protector *self); + +/* --- tsi_peer objects --- + + tsi_peer objects are a set of properties. The peer owns the properties. */ + +/* This property is of type TSI_PEER_PROPERTY_STRING. */ +#define TSI_CERTIFICATE_TYPE_PEER_PROPERTY "certificate_type" + +/* Property values may contain NULL characters just like C++ strings. + The length field gives the length of the string. */ +typedef struct tsi_peer_property { + char *name; + struct { + char *data; + size_t length; + } value; +} tsi_peer_property; + +typedef struct { + tsi_peer_property *properties; + size_t property_count; +} tsi_peer; + +/* Destructs the tsi_peer object. */ +void tsi_peer_destruct(tsi_peer *self); + +/* --- tsi_handshaker objects ---- + + Implementations of this object must be thread compatible. + + A typical usage of this object would be: + + ------------------------------------------------------------------------ + tsi_result result = TSI_OK; + unsigned char buf[4096]; + size_t buf_offset; + size_t buf_size; + while (1) { + // See if we need to send some bytes to the peer. + do { + size_t buf_size_to_send = sizeof(buf); + result = tsi_handshaker_get_bytes_to_send_to_peer(handshaker, buf, + &buf_size_to_send); + if (buf_size_to_send > 0) send_bytes_to_peer(buf, buf_size_to_send); + } while (result == TSI_INCOMPLETE_DATA); + if (result != TSI_OK) return result; + if (!tsi_handshaker_is_in_progress(handshaker)) break; + + do { + // Read bytes from the peer. + buf_size = sizeof(buf); + buf_offset = 0; + read_bytes_from_peer(buf, &buf_size); + if (buf_size == 0) break; + + // Process the bytes from the peer. We have to be careful as these bytes + // may contain non-handshake data (protected data). If this is the case, + // we will exit from the loop with buf_size > 0. + size_t consumed_by_handshaker = buf_size; + result = tsi_handshaker_process_bytes_from_peer( + handshaker, buf, &consumed_by_handshaker); + buf_size -= consumed_by_handshaker; + buf_offset += consumed_by_handshaker; + } while (result == TSI_INCOMPLETE_DATA); + + if (result != TSI_OK) return result; + if (!tsi_handshaker_is_in_progress(handshaker)) break; + } + + // Check the Peer. + tsi_peer peer; + do { + result = tsi_handshaker_extract_peer(handshaker, &peer); + if (result != TSI_OK) break; + result = check_peer(&peer); + } while (0); + tsi_peer_destruct(&peer); + if (result != TSI_OK) return result; + + // Create the protector. + tsi_frame_protector* protector = NULL; + result = tsi_handshaker_create_frame_protector(handshaker, NULL, + &protector); + if (result != TSI_OK) return result; + + // Do not forget to unprotect outstanding data if any. + if (buf_size > 0) { + result = tsi_frame_protector_unprotect(protector, buf + buf_offset, + buf_size, ..., ...); + .... + } + ... + ------------------------------------------------------------------------ */ +typedef struct tsi_handshaker tsi_handshaker; + +/* Gets bytes that need to be sent to the peer. + - bytes is the buffer that will be written with the data to be sent to the + peer. + - bytes_size is an input/output parameter specifying the capacity of the + bytes parameter as input and the number of bytes written as output. + Returns TSI_OK if all the data to send to the peer has been written or if + nothing has to be sent to the peer (in which base bytes_size outputs to 0), + otherwise returns TSI_INCOMPLETE_DATA which indicates that this method + needs to be called again to get all the bytes to send to the peer (there + was more data to write than the specified bytes_size). In case of a fatal + error in the handshake, another specific error code is returned. */ +tsi_result tsi_handshaker_get_bytes_to_send_to_peer(tsi_handshaker *self, + unsigned char *bytes, + size_t *bytes_size); + +/* Processes bytes received from the peer. + - bytes is the buffer containing the data. + - bytes_size is an input/output parameter specifying the size of the data as + input and the number of bytes consumed as output. + Return TSI_OK if the handshake has all the data it needs to process, + otherwise return TSI_INCOMPLETE_DATA which indicates that this method + needs to be called again to complete the data needed for processing. In + case of a fatal error in the handshake, another specific error code is + returned. */ +tsi_result tsi_handshaker_process_bytes_from_peer(tsi_handshaker *self, + const unsigned char *bytes, + size_t *bytes_size); + +/* Gets the result of the handshaker. + Returns TSI_OK if the hanshake completed successfully and there has been no + errors. Returns TSI_HANDSHAKE_IN_PROGRESS if the handshaker is not done yet + but no error has been encountered so far. Otherwise the handshaker failed + with the returned error. */ +tsi_result tsi_handshaker_get_result(tsi_handshaker *self); + +/* Returns 1 if the handshake is in progress, 0 otherwise. */ +#define tsi_handshaker_is_in_progress(h) \ + (tsi_handshaker_get_result((h)) == TSI_HANDSHAKE_IN_PROGRESS) + +/* This method may return TSI_FAILED_PRECONDITION if + tsi_handshaker_is_in_progress returns 1, it returns TSI_OK otherwise + assuming the handshaker is not in a fatal error state. + The caller is responsible for destructing the peer. */ +tsi_result tsi_handshaker_extract_peer(tsi_handshaker *self, tsi_peer *peer); + +/* This method creates a tsi_frame_protector object after the handshake phase + is done. After this method has been called successfully, the only method + that can be called on this object is Destroy. + - max_output_protected_frame_size is an input/output parameter specifying the + desired max output protected frame size as input and outputing the actual + max output frame size as the output. Passing NULL is OK and will result in + the implementation choosing the default maximum protected frame size. Note + that this size only applies to outgoing frames (generated with + tsi_frame_protector_protect) and not incoming frames (input of + tsi_frame_protector_unprotect). + - protector is an output parameter pointing to the newly created + tsi_frame_protector object. + This method may return TSI_FAILED_PRECONDITION if + tsi_handshaker_is_in_progress returns 1, it returns TSI_OK otherwise assuming + the handshaker is not in a fatal error state. + The caller is responsible for destroying the protector. */ +tsi_result tsi_handshaker_create_frame_protector( + tsi_handshaker *self, size_t *max_output_protected_frame_size, + tsi_frame_protector **protector); + +/* This method releases the tsi_handshaker object. After this method is called, + no other method can be called on the object. */ +void tsi_handshaker_destroy(tsi_handshaker *self); + +#ifdef __cplusplus +} +#endif + +#endif /* GRPC_CORE_LIB_TSI_TRANSPORT_SECURITY_INTERFACE_H */ |