diff options
author | Nicolas Noble <nnoble@google.com> | 2014-11-26 16:33:03 -0800 |
---|---|---|
committer | Nicolas Noble <nnoble@google.com> | 2014-11-26 16:33:03 -0800 |
commit | b7ebd3b8c6fe39f99c40b10c1b563e4adb607b6c (patch) | |
tree | c1decf819492d455ec81cd471942c5516138f825 /src | |
parent | 0e905e63db21bcdd85d3d1af051fcdc5bb5caa38 (diff) |
Initial import.
Diffstat (limited to 'src')
218 files changed, 44879 insertions, 0 deletions
diff --git a/src/core/channel/call_op_string.c b/src/core/channel/call_op_string.c new file mode 100644 index 0000000000..4a98cbfbbb --- /dev/null +++ b/src/core/channel/call_op_string.c @@ -0,0 +1,155 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/channel/channel_stack.h" + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/string.h> +#include <grpc/support/useful.h> + +#define MAX_APPEND 1024 + +typedef struct { + size_t cap; + size_t len; + char *buffer; +} buf; + +static void bprintf(buf *b, const char *fmt, ...) { + va_list arg; + if (b->len + MAX_APPEND > b->cap) { + b->cap = GPR_MAX(b->len + MAX_APPEND, b->cap * 3 / 2); + b->buffer = gpr_realloc(b->buffer, b->cap); + } + va_start(arg, fmt); + b->len += vsprintf(b->buffer + b->len, fmt, arg); + va_end(arg); +} + +static void bputs(buf *b, const char *s) { + size_t slen = strlen(s); + if (b->len + slen + 1 > b->cap) { + b->cap = GPR_MAX(b->len + slen + 1, b->cap * 3 / 2); + b->buffer = gpr_realloc(b->buffer, b->cap); + } + strcat(b->buffer, s); + b->len += slen; +} + +static void put_metadata(buf *b, grpc_mdelem *md) { + char *txt; + + txt = gpr_hexdump((char *)GPR_SLICE_START_PTR(md->key->slice), + GPR_SLICE_LENGTH(md->key->slice), GPR_HEXDUMP_PLAINTEXT); + bputs(b, " key="); + bputs(b, txt); + gpr_free(txt); + + txt = gpr_hexdump((char *)GPR_SLICE_START_PTR(md->value->slice), + GPR_SLICE_LENGTH(md->value->slice), GPR_HEXDUMP_PLAINTEXT); + bputs(b, " value="); + bputs(b, txt); + gpr_free(txt); +} + +char *grpc_call_op_string(grpc_call_op *op) { + buf b = {0, 0, 0}; + + switch (op->dir) { + case GRPC_CALL_DOWN: + bprintf(&b, ">"); + break; + case GRPC_CALL_UP: + bprintf(&b, "<"); + break; + } + switch (op->type) { + case GRPC_SEND_METADATA: + bprintf(&b, "SEND_METADATA"); + put_metadata(&b, op->data.metadata); + break; + case GRPC_SEND_DEADLINE: + bprintf(&b, "SEND_DEADLINE %d.%09d", op->data.deadline.tv_sec, + op->data.deadline.tv_nsec); + break; + case GRPC_SEND_START: + bprintf(&b, "SEND_START"); + break; + case GRPC_SEND_MESSAGE: + bprintf(&b, "SEND_MESSAGE"); + break; + case GRPC_SEND_FINISH: + bprintf(&b, "SEND_FINISH"); + break; + case GRPC_REQUEST_DATA: + bprintf(&b, "REQUEST_DATA"); + break; + case GRPC_RECV_METADATA: + bprintf(&b, "RECV_METADATA"); + put_metadata(&b, op->data.metadata); + break; + case GRPC_RECV_DEADLINE: + bprintf(&b, "RECV_DEADLINE %d.%09d", op->data.deadline.tv_sec, + op->data.deadline.tv_nsec); + break; + case GRPC_RECV_END_OF_INITIAL_METADATA: + bprintf(&b, "RECV_END_OF_INITIAL_METADATA"); + break; + case GRPC_RECV_MESSAGE: + bprintf(&b, "RECV_MESSAGE"); + break; + case GRPC_RECV_HALF_CLOSE: + bprintf(&b, "RECV_HALF_CLOSE"); + break; + case GRPC_RECV_FINISH: + bprintf(&b, "RECV_FINISH"); + break; + case GRPC_CANCEL_OP: + bprintf(&b, "CANCEL_OP"); + break; + } + bprintf(&b, " flags=0x%08x", op->flags); + + return b.buffer; +} + +void grpc_call_log_op(char *file, int line, gpr_log_severity severity, + grpc_call_element *elem, grpc_call_op *op) { + char *str = grpc_call_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/channel/census_filter.c b/src/core/channel/census_filter.c new file mode 100644 index 0000000000..4285b284ce --- /dev/null +++ b/src/core/channel/census_filter.c @@ -0,0 +1,189 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/channel/census_filter.h" + +#include <stdio.h> +#include <string.h> + +#include "src/core/channel/channel_stack.h" +#include "src/core/channel/noop_filter.h" +#include "src/core/statistics/census_interface.h" +#include "src/core/statistics/census_rpc_stats.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/slice.h> +#include <grpc/support/time.h> + +typedef struct call_data { + census_op_id op_id; + census_rpc_stats stats; + gpr_timespec start_ts; +} call_data; + +typedef struct channel_data { + grpc_mdstr* path_str; /* pointer to meta data str with key == ":path" */ +} channel_data; + +static void init_rpc_stats(census_rpc_stats* stats) { + memset(stats, 0, sizeof(census_rpc_stats)); + stats->cnt = 1; +} + +static double gpr_timespec_to_micros(gpr_timespec t) { + return t.tv_sec * GPR_US_PER_SEC + t.tv_nsec * 1e-3; +} + +static void extract_and_annotate_method_tag(grpc_call_op* op, call_data* calld, + channel_data* chand) { + if (op->data.metadata->key == chand->path_str) { + census_add_method_tag(calld->op_id, (const char*)GPR_SLICE_START_PTR( + op->data.metadata->value->slice)); + } +} + +static void client_call_op(grpc_call_element* elem, grpc_call_op* op) { + call_data* calld = elem->call_data; + channel_data* chand = elem->channel_data; + GPR_ASSERT(calld != NULL); + GPR_ASSERT(chand != NULL); + GPR_ASSERT((calld->op_id.upper != 0) && (calld->op_id.lower != 0)); + switch (op->type) { + case GRPC_SEND_METADATA: + extract_and_annotate_method_tag(op, calld, chand); + break; + case GRPC_RECV_FINISH: + /* Should we stop timing the rpc here? */ + break; + default: + break; + } + /* Always pass control up or down the stack depending on op->dir */ + grpc_call_next_op(elem, op); +} + +static void server_call_op(grpc_call_element* elem, grpc_call_op* op) { + call_data* calld = elem->call_data; + channel_data* chand = elem->channel_data; + GPR_ASSERT(calld != NULL); + GPR_ASSERT(chand != NULL); + GPR_ASSERT((calld->op_id.upper != 0) && (calld->op_id.lower != 0)); + switch (op->type) { + case GRPC_RECV_METADATA: + extract_and_annotate_method_tag(op, calld, chand); + break; + case GRPC_SEND_FINISH: + /* Should we stop timing the rpc here? */ + break; + default: + break; + } + /* Always pass control up or down the stack depending on op->dir */ + grpc_call_next_op(elem, op); +} + +static void channel_op(grpc_channel_element* elem, grpc_channel_op* op) { + switch (op->type) { + case GRPC_TRANSPORT_CLOSED: + /* TODO(hongyu): Annotate trace information for all calls of the channel + */ + break; + default: + break; + } + grpc_channel_next_op(elem, op); +} + +static void client_init_call_elem(grpc_call_element* elem, + const void* server_transport_data) { + call_data* d = elem->call_data; + GPR_ASSERT(d != NULL); + init_rpc_stats(&d->stats); + d->start_ts = gpr_now(); + d->op_id = census_tracing_start_op(); +} + +static void client_destroy_call_elem(grpc_call_element* elem) { + call_data* d = elem->call_data; + GPR_ASSERT(d != NULL); + census_record_rpc_client_stats(d->op_id, &d->stats); + census_tracing_end_op(d->op_id); +} + +static void server_init_call_elem(grpc_call_element* elem, + const void* server_transport_data) { + call_data* d = elem->call_data; + GPR_ASSERT(d != NULL); + init_rpc_stats(&d->stats); + d->start_ts = gpr_now(); + d->op_id = census_tracing_start_op(); +} + +static void server_destroy_call_elem(grpc_call_element* elem) { + call_data* d = elem->call_data; + GPR_ASSERT(d != NULL); + d->stats.elapsed_time_ms = + gpr_timespec_to_micros(gpr_time_sub(gpr_now(), d->start_ts)); + census_record_rpc_server_stats(d->op_id, &d->stats); + census_tracing_end_op(d->op_id); +} + +static void init_channel_elem(grpc_channel_element* elem, + const grpc_channel_args* args, grpc_mdctx* mdctx, + int is_first, int is_last) { + channel_data* chand = elem->channel_data; + GPR_ASSERT(chand != NULL); + GPR_ASSERT(!is_first); + GPR_ASSERT(!is_last); + chand->path_str = grpc_mdstr_from_string(mdctx, ":path"); +} + +static void destroy_channel_elem(grpc_channel_element* elem) {} + +const grpc_channel_filter grpc_client_census_filter = { + client_call_op, channel_op, + + sizeof(call_data), client_init_call_elem, client_destroy_call_elem, + + sizeof(channel_data), init_channel_elem, destroy_channel_elem, + + "census-client"}; + +const grpc_channel_filter grpc_server_census_filter = { + server_call_op, channel_op, + + sizeof(call_data), server_init_call_elem, server_destroy_call_elem, + + sizeof(channel_data), init_channel_elem, destroy_channel_elem, + + "census-server"}; diff --git a/src/core/channel/census_filter.h b/src/core/channel/census_filter.h new file mode 100644 index 0000000000..5b2c01ca9b --- /dev/null +++ b/src/core/channel/census_filter.h @@ -0,0 +1,44 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_CHANNEL_CENSUS_FILTER_H__ +#define __GRPC_INTERNAL_CHANNEL_CENSUS_FILTER_H__ + +#include "src/core/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_INTERNAL_CHANNEL_CENSUS_FILTER_H__ */ diff --git a/src/core/channel/channel_args.c b/src/core/channel/channel_args.c new file mode 100644 index 0000000000..36312e54de --- /dev/null +++ b/src/core/channel/channel_args.c @@ -0,0 +1,112 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 "src/core/channel/channel_args.h" + +#include <grpc/support/alloc.h> +#include <grpc/support/string.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.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) { + 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 + ((to_add == NULL) ? 0 : 1); + 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]); + } + if (to_add != NULL) dst->args[src_num_args] = copy_arg(to_add); + return dst; +} + +grpc_channel_args *grpc_channel_args_copy(const grpc_channel_args *src) { + return grpc_channel_args_copy_and_add(src, NULL); +} + +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.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) { + int 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; + } + } + return 0; +} diff --git a/src/core/channel/channel_args.h b/src/core/channel/channel_args.h new file mode 100644 index 0000000000..cf38d5d01f --- /dev/null +++ b/src/core/channel/channel_args.h @@ -0,0 +1,54 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_CHANNEL_CHANNEL_ARGS_H__ +#define __GRPC_INTERNAL_CHANNEL_CHANNEL_ARGS_H__ + +#include <grpc/grpc.h> + +/* Copy some arguments */ +grpc_channel_args *grpc_channel_args_copy(const grpc_channel_args *src); + +/* 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); + +/* 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); + +#endif /* __GRPC_INTERNAL_CHANNEL_CHANNEL_ARGS_H__ */ diff --git a/src/core/channel/channel_stack.c b/src/core/channel/channel_stack.c new file mode 100644 index 0000000000..a403db35c2 --- /dev/null +++ b/src/core/channel/channel_stack.c @@ -0,0 +1,223 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/channel/channel_stack.h" +#include <grpc/support/log.h> + +#include <stdlib.h> + +/* 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 - 1) & ~(GPR_MAX_ALIGNMENT - 1)) + +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(const grpc_channel_filter **filters, + size_t filter_count, const grpc_channel_args *args, + grpc_mdctx *metadata_context, + 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; + char *user_data; + size_t i; + + stack->count = filter_count; + 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++) { + elems[i].filter = filters[i]; + elems[i].channel_data = user_data; + elems[i].filter->init_channel_elem(&elems[i], args, metadata_context, + i == 0, i == (filter_count - 1)); + 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 == + grpc_channel_stack_size(filters, filter_count)); + + stack->call_stack_size = call_size; +} + +void grpc_channel_stack_destroy(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(&channel_elems[i]); + } +} + +void grpc_call_stack_init(grpc_channel_stack *channel_stack, + const void *transport_server_data, + grpc_call_stack *call_stack) { + grpc_channel_element *channel_elems = CHANNEL_ELEMS_FROM_STACK(channel_stack); + size_t count = channel_stack->count; + grpc_call_element *call_elems; + char *user_data; + size_t i; + + call_stack->count = count; + 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 = 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(&call_elems[i], transport_server_data); + user_data += + ROUND_UP_TO_ALIGNMENT_SIZE(call_elems[i].filter->sizeof_call_data); + } +} + +void grpc_call_stack_destroy(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(&elems[i]); + } +} + +void grpc_call_next_op(grpc_call_element *elem, grpc_call_op *op) { + grpc_call_element *next_elem = elem + op->dir; + next_elem->filter->call_op(next_elem, op); +} + +void grpc_channel_next_op(grpc_channel_element *elem, grpc_channel_op *op) { + grpc_channel_element *next_elem = elem + op->dir; + next_elem->filter->channel_op(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))); +} + +static void do_nothing(void *user_data, grpc_op_error error) {} + +void grpc_call_element_send_metadata(grpc_call_element *cur_elem, + grpc_mdelem *mdelem) { + grpc_call_op metadata_op; + metadata_op.type = GRPC_SEND_METADATA; + metadata_op.dir = GRPC_CALL_DOWN; + metadata_op.done_cb = do_nothing; + metadata_op.user_data = NULL; + metadata_op.data.metadata = grpc_mdelem_ref(mdelem); + grpc_call_next_op(cur_elem, &metadata_op); +} + +void grpc_call_element_send_cancel(grpc_call_element *cur_elem) { + grpc_call_op cancel_op; + cancel_op.type = GRPC_CANCEL_OP; + cancel_op.dir = GRPC_CALL_DOWN; + cancel_op.done_cb = do_nothing; + cancel_op.user_data = NULL; + grpc_call_next_op(cur_elem, &cancel_op); +} diff --git a/src/core/channel/channel_stack.h b/src/core/channel/channel_stack.h new file mode 100644 index 0000000000..0ae1005e67 --- /dev/null +++ b/src/core/channel/channel_stack.h @@ -0,0 +1,288 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_CHANNEL_CHANNEL_STACK_H__ +#define __GRPC_INTERNAL_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/transport/transport.h" + +/* #define GRPC_CHANNEL_STACK_TRACE 1 */ + +typedef struct grpc_channel_element grpc_channel_element; +typedef struct grpc_call_element grpc_call_element; + +/* Call operations - things that can be sent and received. + + Threading: + SEND, RECV, and CANCEL ops can be active on a call at the same time, but + only one SEND, one RECV, and one CANCEL can be active at a time. + + If state is shared between send/receive/cancel operations, it is up to + filters to provide their own protection around that. */ +typedef enum { + /* send metadata to the channels peer */ + GRPC_SEND_METADATA, + /* send a deadline */ + GRPC_SEND_DEADLINE, + /* start a connection (corresponds to start_invoke/accept) */ + GRPC_SEND_START, + /* send a message to the channels peer */ + GRPC_SEND_MESSAGE, + /* send half-close to the channels peer */ + GRPC_SEND_FINISH, + /* request that more data be allowed through flow control */ + GRPC_REQUEST_DATA, + /* metadata was received from the channels peer */ + GRPC_RECV_METADATA, + /* receive a deadline */ + GRPC_RECV_DEADLINE, + /* the end of the first batch of metadata was received */ + GRPC_RECV_END_OF_INITIAL_METADATA, + /* a message was received from the channels peer */ + GRPC_RECV_MESSAGE, + /* half-close was received from the channels peer */ + GRPC_RECV_HALF_CLOSE, + /* full close was received from the channels peer */ + GRPC_RECV_FINISH, + /* the call has been abnormally terminated */ + GRPC_CANCEL_OP +} grpc_call_op_type; + +/* The direction of the call. + The values of the enums (1, -1) matter here - they are used to increment + or decrement a pointer to find the next element to call */ +typedef enum { GRPC_CALL_DOWN = 1, GRPC_CALL_UP = -1 } grpc_call_dir; + +/* A single filterable operation to be performed on a call */ +typedef struct { + /* The type of operation we're performing */ + grpc_call_op_type type; + /* The directionality of this call - does the operation begin at the bottom + of the stack and flow up, or does the operation start at the top of the + stack and flow down through the filters. */ + grpc_call_dir dir; + + /* Flags associated with this call: see GRPC_WRITE_* in grpc.h */ + gpr_uint32 flags; + + /* Argument data, matching up with grpc_call_op_type names */ + union { + grpc_byte_buffer *message; + grpc_mdelem *metadata; + gpr_timespec deadline; + } data; + + /* Must be called when processing of this call-op is complete. + Signature chosen to match transport flow control callbacks */ + void (*done_cb)(void *user_data, grpc_op_error error); + /* User data to be passed into done_cb */ + void *user_data; +} grpc_call_op; + +/* returns a string representation of op, that can be destroyed with gpr_free */ +char *grpc_call_op_string(grpc_call_op *op); + +typedef enum { + GRPC_CHANNEL_SHUTDOWN, + GRPC_ACCEPT_CALL, + GRPC_TRANSPORT_CLOSED +} grpc_channel_op_type; + +/* A single filterable operation to be performed on a channel */ +typedef struct { + /* The type of operation we're performing */ + grpc_channel_op_type type; + /* The directionality of this call - is it bubbling up the stack, or down? */ + grpc_call_dir dir; + + /* Argument data, matching up with grpc_channel_op_type names */ + union { + struct { + grpc_transport *transport; + const void *transport_server_data; + } accept_call; + } data; +} grpc_channel_op; + +/* 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 (*call_op)(grpc_call_element *elem, grpc_call_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 (*channel_op)(grpc_channel_element *elem, grpc_channel_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_call_element *elem, + const void *server_transport_data); + /* Destroy per call data. + The filter does not need to do any chaining */ + void (*destroy_call_elem)(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_channel_element *elem, + const grpc_channel_args *args, + grpc_mdctx *metadata_context, int is_first, + int is_last); + /* Destroy per channel data. + The filter does not need to do any chaining */ + void (*destroy_channel_elem)(grpc_channel_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 */ +typedef struct { + size_t count; + /* Memory required for a call stack (computed at channel stack + initialization) */ + size_t call_stack_size; +} grpc_channel_stack; + +/* A call stack tracks a set of related filters for one call, and guarantees + they live within a single malloc() allocation */ +typedef struct { size_t count; } grpc_call_stack; + +/* 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(const grpc_channel_filter **filters, + size_t filter_count, const grpc_channel_args *args, + grpc_mdctx *metadata_context, + grpc_channel_stack *stack); +/* Destroy a channel stack */ +void grpc_channel_stack_destroy(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_channel_stack *channel_stack, + const void *transport_server_data, + grpc_call_stack *call_stack); +/* Destroy a call stack */ +void grpc_call_stack_destroy(grpc_call_stack *stack); + +/* Call the next operation (depending on call directionality) in a call stack */ +void grpc_call_next_op(grpc_call_element *elem, grpc_call_op *op); +/* Call the next operation (depending on call directionality) in a channel + stack */ +void grpc_channel_next_op(grpc_channel_element *elem, grpc_channel_op *op); + +/* 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_call_op *op); + +void grpc_call_element_send_metadata(grpc_call_element *cur_elem, + grpc_mdelem *elem); +void grpc_call_element_send_cancel(grpc_call_element *cur_elem); + +#ifdef GRPC_CHANNEL_STACK_TRACE +#define GRPC_CALL_LOG_OP(sev, elem, op) grpc_call_log_op(sev, elem, op) +#else +#define GRPC_CALL_LOG_OP(sev, elem, op) \ + do { \ + } while (0) +#endif + +#endif /* __GRPC_INTERNAL_CHANNEL_CHANNEL_STACK_H__ */ diff --git a/src/core/channel/client_channel.c b/src/core/channel/client_channel.c new file mode 100644 index 0000000000..90563683d5 --- /dev/null +++ b/src/core/channel/client_channel.c @@ -0,0 +1,641 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/channel/client_channel.h" + +#include <stdio.h> + +#include "src/core/channel/channel_args.h" +#include "src/core/channel/connected_channel.h" +#include "src/core/channel/metadata_buffer.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string.h> +#include <grpc/support/sync.h> +#include <grpc/support/useful.h> + +/* Link back filter: passes up calls to the client channel, pushes down calls + down */ + +typedef struct { grpc_channel_element *back; } lb_channel_data; + +typedef struct { grpc_call_element *back; } lb_call_data; + +static void lb_call_op(grpc_call_element *elem, grpc_call_op *op) { + lb_call_data *calld = elem->call_data; + + switch (op->dir) { + case GRPC_CALL_UP: + calld->back->filter->call_op(calld->back, op); + break; + case GRPC_CALL_DOWN: + grpc_call_next_op(elem, op); + break; + } +} + +/* Currently we assume all channel operations should just be pushed up. */ +static void lb_channel_op(grpc_channel_element *elem, grpc_channel_op *op) { + lb_channel_data *chand = elem->channel_data; + + switch (op->dir) { + case GRPC_CALL_UP: + chand->back->filter->channel_op(chand->back, op); + break; + case GRPC_CALL_DOWN: + grpc_channel_next_op(elem, op); + break; + } +} + +/* Constructor for call_data */ +static void lb_init_call_elem(grpc_call_element *elem, + const void *server_transport_data) {} + +/* Destructor for call_data */ +static void lb_destroy_call_elem(grpc_call_element *elem) {} + +/* Constructor for channel_data */ +static void lb_init_channel_elem(grpc_channel_element *elem, + const grpc_channel_args *args, + grpc_mdctx *metadata_context, int is_first, + int is_last) { + GPR_ASSERT(is_first); + GPR_ASSERT(!is_last); +} + +/* Destructor for channel_data */ +static void lb_destroy_channel_elem(grpc_channel_element *elem) {} + +static const grpc_channel_filter link_back_filter = { + lb_call_op, lb_channel_op, + + sizeof(lb_call_data), lb_init_call_elem, lb_destroy_call_elem, + + sizeof(lb_channel_data), lb_init_channel_elem, lb_destroy_channel_elem, + + "clientchannel.linkback", +}; + +/* Client channel implementation */ + +typedef struct { + size_t inflight_requests; + grpc_channel_stack *channel_stack; +} child_entry; + +typedef struct call_data call_data; + +typedef struct { + /* protects children, child_count, child_capacity, active_child, + transport_setup_initiated + does not protect channel stacks held by children + transport_setup is assumed to be set once during construction */ + gpr_mu mu; + + /* the sending child (points somewhere in children, or NULL) */ + child_entry *active_child; + /* vector of child channels */ + child_entry *children; + size_t child_count; + size_t child_capacity; + + /* calls waiting for a channel to be ready */ + call_data **waiting_children; + size_t waiting_child_count; + size_t waiting_child_capacity; + + /* transport setup for this channel */ + grpc_transport_setup *transport_setup; + int transport_setup_initiated; + + grpc_channel_args *args; + + /* metadata cache */ + grpc_mdelem *cancel_status; +} channel_data; + +typedef enum { + CALL_CREATED, + CALL_WAITING, + CALL_ACTIVE, + CALL_CANCELLED +} call_state; + +struct call_data { + /* owning element */ + grpc_call_element *elem; + + call_state state; + grpc_metadata_buffer pending_metadata; + gpr_timespec deadline; + union { + struct { + /* our child call stack */ + grpc_call_stack *child_stack; + /* ... and the channel stack associated with it */ + grpc_channel_stack *using_stack; + } active; + struct { + void (*on_complete)(void *user_data, grpc_op_error error); + void *on_complete_user_data; + gpr_uint32 start_flags; + } waiting; + } s; +}; + +static int prepare_activate(call_data *calld, child_entry *on_child) { + grpc_call_element *child_elem; + grpc_channel_stack *use_stack = on_child->channel_stack; + + if (calld->state == CALL_CANCELLED) return 0; + + on_child->inflight_requests++; + + /* no more access to calld->s.waiting allowed */ + GPR_ASSERT(calld->state == CALL_WAITING); + calld->state = CALL_ACTIVE; + + /* create a child stack, and record that we're using a particular channel + stack */ + calld->s.active.child_stack = gpr_malloc(use_stack->call_stack_size); + calld->s.active.using_stack = use_stack; + grpc_call_stack_init(use_stack, NULL, calld->s.active.child_stack); + /* initialize the top level link back element */ + child_elem = grpc_call_stack_element(calld->s.active.child_stack, 0); + GPR_ASSERT(child_elem->filter == &link_back_filter); + ((lb_call_data *)child_elem->call_data)->back = calld->elem; + + return 1; +} + +static void do_nothing(void *ignored, grpc_op_error error) {} + +static void complete_activate(call_data *calld, child_entry *on_child, + grpc_call_op *op) { + grpc_call_element *child_elem = + grpc_call_stack_element(calld->s.active.child_stack, 0); + + GPR_ASSERT(calld->state == CALL_ACTIVE); + + /* sending buffered metadata down the stack before the start call */ + grpc_metadata_buffer_flush(&calld->pending_metadata, child_elem); + + if (gpr_time_cmp(calld->deadline, gpr_inf_future) != 0) { + grpc_call_op dop; + dop.type = GRPC_SEND_DEADLINE; + dop.dir = GRPC_CALL_DOWN; + dop.flags = 0; + dop.data.deadline = calld->deadline; + dop.done_cb = do_nothing; + dop.user_data = NULL; + child_elem->filter->call_op(child_elem, &dop); + } + + /* continue the start call down the stack, this nees to happen after metadata + are flushed*/ + child_elem->filter->call_op(child_elem, op); +} + +static void start_rpc(call_data *calld, channel_data *chand, grpc_call_op *op) { + gpr_mu_lock(&chand->mu); + if (calld->state == CALL_CANCELLED) { + gpr_mu_unlock(&chand->mu); + op->done_cb(op->user_data, GRPC_OP_ERROR); + return; + } + GPR_ASSERT(calld->state == CALL_CREATED); + calld->state = CALL_WAITING; + if (chand->active_child) { + /* channel is connected - use the connected stack */ + if (prepare_activate(calld, chand->active_child)) { + gpr_mu_unlock(&chand->mu); + /* activate the request (pass it down) outside the lock */ + complete_activate(calld, chand->active_child, op); + } else { + gpr_mu_unlock(&chand->mu); + } + } else { + /* check to see if we should initiate a connection (if we're not already), + but don't do so until outside the lock to avoid re-entrancy problems if + the callback is immediate */ + int initiate_transport_setup = 0; + if (!chand->transport_setup_initiated) { + chand->transport_setup_initiated = 1; + initiate_transport_setup = 1; + } + /* add this call to the waiting set to be resumed once we have a child + channel stack, growing the waiting set if needed */ + if (chand->waiting_child_count == chand->waiting_child_capacity) { + chand->waiting_child_capacity = + GPR_MAX(chand->waiting_child_capacity * 2, 8); + chand->waiting_children = + gpr_realloc(chand->waiting_children, + chand->waiting_child_capacity * sizeof(call_data *)); + } + calld->s.waiting.on_complete = op->done_cb; + calld->s.waiting.on_complete_user_data = op->user_data; + calld->s.waiting.start_flags = op->flags; + chand->waiting_children[chand->waiting_child_count++] = calld; + gpr_mu_unlock(&chand->mu); + + /* finally initiate transport setup if needed */ + if (initiate_transport_setup) { + grpc_transport_setup_initiate(chand->transport_setup); + } + } +} + +static void remove_waiting_child(channel_data *chand, call_data *calld) { + size_t new_count; + size_t i; + for (i = 0, new_count = 0; i < chand->waiting_child_count; i++) { + if (chand->waiting_children[i] == calld) continue; + chand->waiting_children[new_count++] = chand->waiting_children[i]; + } + GPR_ASSERT(new_count == chand->waiting_child_count - 1 || + new_count == chand->waiting_child_count); + chand->waiting_child_count = new_count; +} + +static void cancel_rpc(grpc_call_element *elem, grpc_call_op *op) { + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + grpc_call_element *child_elem; + grpc_call_op finish_op; + + gpr_mu_lock(&chand->mu); + switch (calld->state) { + case CALL_ACTIVE: + child_elem = grpc_call_stack_element(calld->s.active.child_stack, 0); + gpr_mu_unlock(&chand->mu); + child_elem->filter->call_op(child_elem, op); + return; /* early out */ + case CALL_WAITING: + remove_waiting_child(chand, calld); + calld->s.waiting.on_complete(calld->s.waiting.on_complete_user_data, + GRPC_OP_ERROR); + /* fallthrough intended */ + case CALL_CREATED: + calld->state = CALL_CANCELLED; + gpr_mu_unlock(&chand->mu); + /* send up a synthesized status */ + finish_op.type = GRPC_RECV_METADATA; + finish_op.dir = GRPC_CALL_UP; + finish_op.flags = 0; + finish_op.data.metadata = grpc_mdelem_ref(chand->cancel_status); + finish_op.done_cb = do_nothing; + finish_op.user_data = NULL; + grpc_call_next_op(elem, &finish_op); + /* send up a finish */ + finish_op.type = GRPC_RECV_FINISH; + finish_op.dir = GRPC_CALL_UP; + finish_op.flags = 0; + finish_op.done_cb = do_nothing; + finish_op.user_data = NULL; + grpc_call_next_op(elem, &finish_op); + return; /* early out */ + case CALL_CANCELLED: + gpr_mu_unlock(&chand->mu); + return; /* early out */ + } + gpr_log(GPR_ERROR, "should never reach here"); + abort(); +} + +static void call_op(grpc_call_element *elem, grpc_call_op *op) { + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + grpc_call_element *child_elem; + GPR_ASSERT(elem->filter == &grpc_client_channel_filter); + GRPC_CALL_LOG_OP(GPR_INFO, elem, op); + + switch (op->type) { + case GRPC_SEND_METADATA: + grpc_metadata_buffer_queue(&calld->pending_metadata, op); + break; + case GRPC_SEND_DEADLINE: + calld->deadline = op->data.deadline; + op->done_cb(op->user_data, GRPC_OP_OK); + break; + case GRPC_SEND_START: + /* filter out the start event to find which child to send on */ + start_rpc(calld, chand, op); + break; + case GRPC_CANCEL_OP: + cancel_rpc(elem, op); + break; + default: + switch (op->dir) { + case GRPC_CALL_UP: + grpc_call_next_op(elem, op); + break; + case GRPC_CALL_DOWN: + child_elem = grpc_call_stack_element(calld->s.active.child_stack, 0); + GPR_ASSERT(calld->state == CALL_ACTIVE); + child_elem->filter->call_op(child_elem, op); + break; + } + break; + } +} + +static void broadcast_channel_op_down(grpc_channel_element *elem, + grpc_channel_op *op) { + channel_data *chand = elem->channel_data; + grpc_channel_element *child_elem; + grpc_channel_stack **children; + size_t child_count; + size_t i; + + /* copy the current set of children, and mark them all as having an inflight + request */ + gpr_mu_lock(&chand->mu); + child_count = chand->child_count; + children = gpr_malloc(sizeof(grpc_channel_stack *) * child_count); + for (i = 0; i < child_count; i++) { + children[i] = chand->children[i].channel_stack; + chand->children[i].inflight_requests++; + } + gpr_mu_unlock(&chand->mu); + + /* send the message down */ + for (i = 0; i < child_count; i++) { + child_elem = grpc_channel_stack_element(children[i], 0); + child_elem->filter->channel_op(child_elem, op); + } + + /* unmark the inflight requests */ + gpr_mu_lock(&chand->mu); + for (i = 0; i < child_count; i++) { + chand->children[i].inflight_requests--; + } + gpr_mu_unlock(&chand->mu); + + gpr_free(children); +} + +static void channel_op(grpc_channel_element *elem, grpc_channel_op *op) { + GPR_ASSERT(elem->filter == &grpc_client_channel_filter); + + switch (op->type) { + default: + switch (op->dir) { + case GRPC_CALL_UP: + grpc_channel_next_op(elem, op); + break; + case GRPC_CALL_DOWN: + broadcast_channel_op_down(elem, op); + break; + } + break; + } +} + +static void error_bad_on_complete(void *arg, grpc_op_error error) { + gpr_log(GPR_ERROR, + "Waiting finished but not started? Bad on_complete callback"); + abort(); +} + +/* Constructor for call_data */ +static void init_call_elem(grpc_call_element *elem, + const void *server_transport_data) { + call_data *calld = elem->call_data; + + GPR_ASSERT(elem->filter == &grpc_client_channel_filter); + GPR_ASSERT(server_transport_data == NULL); + calld->elem = elem; + calld->state = CALL_CREATED; + calld->deadline = gpr_inf_future; + calld->s.waiting.on_complete = error_bad_on_complete; + calld->s.waiting.on_complete_user_data = NULL; + grpc_metadata_buffer_init(&calld->pending_metadata); +} + +/* Destructor for call_data */ +static void destroy_call_elem(grpc_call_element *elem) { + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + size_t i; + + /* if the metadata buffer is not flushed, destroy it here. */ + grpc_metadata_buffer_destroy(&calld->pending_metadata, GRPC_OP_OK); + /* if the call got activated, we need to destroy the child stack also, and + remove it from the in-flight requests tracked by the child_entry we + picked */ + if (calld->state == CALL_ACTIVE) { + grpc_call_stack_destroy(calld->s.active.child_stack); + gpr_free(calld->s.active.child_stack); + + gpr_mu_lock(&chand->mu); + for (i = 0; i < chand->child_count; i++) { + if (chand->children[i].channel_stack == calld->s.active.using_stack) { + chand->children[i].inflight_requests--; + /* TODO(ctiller): garbage collect channels that are not active + and have no inflight requests */ + } + } + gpr_mu_unlock(&chand->mu); + } +} + +/* Constructor for channel_data */ +static void init_channel_elem(grpc_channel_element *elem, + const grpc_channel_args *args, + grpc_mdctx *metadata_context, int is_first, + int is_last) { + channel_data *chand = elem->channel_data; + char temp[16]; + + GPR_ASSERT(!is_first); + GPR_ASSERT(is_last); + GPR_ASSERT(elem->filter == &grpc_client_channel_filter); + + gpr_mu_init(&chand->mu); + chand->active_child = NULL; + chand->children = NULL; + chand->child_count = 0; + chand->child_capacity = 0; + chand->waiting_children = NULL; + chand->waiting_child_count = 0; + chand->waiting_child_capacity = 0; + chand->transport_setup = NULL; + chand->transport_setup_initiated = 0; + chand->args = grpc_channel_args_copy(args); + + sprintf(temp, "%d", GRPC_STATUS_CANCELLED); + chand->cancel_status = + grpc_mdelem_from_strings(metadata_context, "grpc-status", temp); +} + +/* Destructor for channel_data */ +static void destroy_channel_elem(grpc_channel_element *elem) { + channel_data *chand = elem->channel_data; + size_t i; + + grpc_transport_setup_cancel(chand->transport_setup); + + for (i = 0; i < chand->child_count; i++) { + GPR_ASSERT(chand->children[i].inflight_requests == 0); + grpc_channel_stack_destroy(chand->children[i].channel_stack); + gpr_free(chand->children[i].channel_stack); + } + + grpc_channel_args_destroy(chand->args); + grpc_mdelem_unref(chand->cancel_status); + + gpr_mu_destroy(&chand->mu); + GPR_ASSERT(chand->waiting_child_count == 0); + gpr_free(chand->waiting_children); + gpr_free(chand->children); +} + +const grpc_channel_filter grpc_client_channel_filter = { + call_op, channel_op, + + sizeof(call_data), init_call_elem, destroy_call_elem, + + sizeof(channel_data), init_channel_elem, destroy_channel_elem, + + "clientchannel", +}; + +grpc_transport_setup_result grpc_client_channel_transport_setup_complete( + grpc_channel_stack *channel_stack, grpc_transport *transport, + grpc_channel_filter const **channel_filters, size_t num_channel_filters, + grpc_mdctx *mdctx) { + /* we just got a new transport: lets create a child channel stack for it */ + grpc_channel_element *elem = grpc_channel_stack_last_element(channel_stack); + channel_data *chand = elem->channel_data; + grpc_channel_element *lb_elem; + grpc_channel_stack *child_stack; + size_t num_child_filters = 2 + num_channel_filters; + grpc_channel_filter const **child_filters; + grpc_transport_setup_result result; + child_entry *child_ent; + call_data **waiting_children; + size_t waiting_child_count; + size_t i; + grpc_call_op *call_ops; + + /* build the child filter stack */ + child_filters = gpr_malloc(sizeof(grpc_channel_filter *) * num_child_filters); + /* we always need a link back filter to get back to the connected channel */ + child_filters[0] = &link_back_filter; + for (i = 0; i < num_channel_filters; i++) { + child_filters[i + 1] = channel_filters[i]; + } + /* and we always need a connected channel to talk to the transport */ + child_filters[num_child_filters - 1] = &grpc_connected_channel_filter; + + GPR_ASSERT(elem->filter == &grpc_client_channel_filter); + + /* BEGIN LOCKING CHANNEL */ + gpr_mu_lock(&chand->mu); + chand->transport_setup_initiated = 0; + + if (chand->child_count == chand->child_capacity) { + /* realloc will invalidate chand->active_child, but it's reset in the next + stanza anyway */ + chand->child_capacity = + GPR_MAX(2 * chand->child_capacity, chand->child_capacity + 2); + chand->children = gpr_realloc(chand->children, + sizeof(child_entry) * chand->child_capacity); + } + + /* build up the child stack */ + child_stack = + gpr_malloc(grpc_channel_stack_size(child_filters, num_child_filters)); + grpc_channel_stack_init(child_filters, num_child_filters, chand->args, mdctx, + child_stack); + lb_elem = grpc_channel_stack_element(child_stack, 0); + GPR_ASSERT(lb_elem->filter == &link_back_filter); + ((lb_channel_data *)lb_elem->channel_data)->back = elem; + result = grpc_connected_channel_bind_transport(child_stack, transport); + child_ent = &chand->children[chand->child_count++]; + child_ent->channel_stack = child_stack; + child_ent->inflight_requests = 0; + chand->active_child = child_ent; + + /* capture the waiting children - we'll activate them outside the lock + to avoid re-entrancy problems */ + waiting_children = chand->waiting_children; + waiting_child_count = chand->waiting_child_count; + /* bumping up inflight_requests here avoids taking a lock per rpc below */ + + chand->waiting_children = NULL; + chand->waiting_child_count = 0; + chand->waiting_child_capacity = 0; + + call_ops = gpr_malloc(sizeof(grpc_call_op) * waiting_child_count); + + for (i = 0; i < waiting_child_count; i++) { + call_ops[i].type = GRPC_SEND_START; + call_ops[i].dir = GRPC_CALL_DOWN; + call_ops[i].flags = waiting_children[i]->s.waiting.start_flags; + call_ops[i].done_cb = waiting_children[i]->s.waiting.on_complete; + call_ops[i].user_data = + waiting_children[i]->s.waiting.on_complete_user_data; + if (!prepare_activate(waiting_children[i], child_ent)) { + waiting_children[i] = NULL; + call_ops[i].done_cb(call_ops[i].user_data, GRPC_OP_ERROR); + } + } + + /* END LOCKING CHANNEL */ + gpr_mu_unlock(&chand->mu); + + /* activate any pending operations - this is safe to do as we guarantee one + and only one write operation per request at the surface api - if we lose + that guarantee we need to do some curly locking here */ + for (i = 0; i < waiting_child_count; i++) { + if (waiting_children[i]) { + complete_activate(waiting_children[i], child_ent, &call_ops[i]); + } + } + gpr_free(waiting_children); + gpr_free(call_ops); + gpr_free(child_filters); + + return result; +} + +void grpc_client_channel_set_transport_setup(grpc_channel_stack *channel_stack, + grpc_transport_setup *setup) { + /* 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_ASSERT(!chand->transport_setup); + chand->transport_setup = setup; +} diff --git a/src/core/channel/client_channel.h b/src/core/channel/client_channel.h new file mode 100644 index 0000000000..576af64ec7 --- /dev/null +++ b/src/core/channel/client_channel.h @@ -0,0 +1,62 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_CHANNEL_CLIENT_CHANNEL_H__ +#define __GRPC_INTERNAL_CHANNEL_CLIENT_CHANNEL_H__ + +#include "src/core/channel/channel_stack.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_transport_setup(grpc_channel_stack *channel_stack, + grpc_transport_setup *setup); + +/* grpc_transport_setup_callback for binding new transports into a client + channel - user_data should be the channel stack containing the client + channel */ +grpc_transport_setup_result grpc_client_channel_transport_setup_complete( + grpc_channel_stack *channel_stack, grpc_transport *transport, + grpc_channel_filter const **channel_filters, size_t num_channel_filters, + grpc_mdctx *mdctx); + +#endif /* __GRPC_INTERNAL_CHANNEL_CLIENT_CHANNEL_H__ */ diff --git a/src/core/channel/client_setup.c b/src/core/channel/client_setup.c new file mode 100644 index 0000000000..c667e39d9c --- /dev/null +++ b/src/core/channel/client_setup.c @@ -0,0 +1,239 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/channel/client_setup.h" +#include "src/core/channel/channel_args.h" +#include "src/core/channel/channel_stack.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/time.h> + +struct grpc_client_setup { + grpc_transport_setup base; /* must be first */ + void (*initiate)(void *user_data, grpc_client_setup_request *request); + void (*done)(void *user_data); + void *user_data; + grpc_channel_args *args; + grpc_mdctx *mdctx; + grpc_em *em; + grpc_em_alarm backoff_alarm; + gpr_timespec current_backoff_interval; + int in_alarm; + + gpr_mu mu; + grpc_client_setup_request *active_request; + int refs; +}; + +struct grpc_client_setup_request { + /* pointer back to the setup object */ + grpc_client_setup *setup; + gpr_timespec deadline; +}; + +gpr_timespec grpc_client_setup_request_deadline(grpc_client_setup_request *r) { + return r->deadline; +} + +static void destroy_setup(grpc_client_setup *s) { + gpr_mu_destroy(&s->mu); + s->done(s->user_data); + grpc_channel_args_destroy(s->args); + gpr_free(s); +} + +/* initiate handshaking */ +static void setup_initiate(grpc_transport_setup *sp) { + grpc_client_setup *s = (grpc_client_setup *)sp; + grpc_client_setup_request *r = gpr_malloc(sizeof(grpc_client_setup_request)); + int in_alarm = 0; + + r->setup = s; + /* TODO(klempner): Actually set a deadline */ + r->deadline = gpr_inf_future; + + gpr_mu_lock(&s->mu); + GPR_ASSERT(s->refs > 0); + /* there might be more than one request outstanding if the caller calls + initiate in some kind of rapid-fire way: we try to connect each time, + and keep track of the latest request (which is the only one that gets + to finish) */ + if (!s->in_alarm) { + s->active_request = r; + s->refs++; + } else { + /* TODO(klempner): Maybe do something more clever here */ + in_alarm = 1; + } + gpr_mu_unlock(&s->mu); + + if (!in_alarm) { + s->initiate(s->user_data, r); + } else { + gpr_free(r); + } +} + +/* cancel handshaking: cancel all requests, and shutdown (the caller promises + not to initiate again) */ +static void setup_cancel(grpc_transport_setup *sp) { + grpc_client_setup *s = (grpc_client_setup *)sp; + void *ignored; + + gpr_mu_lock(&s->mu); + + GPR_ASSERT(s->refs > 0); + /* effectively cancels the current request (if any) */ + s->active_request = NULL; + if (s->in_alarm) { + grpc_em_alarm_cancel(&s->backoff_alarm, &ignored); + } + if (--s->refs == 0) { + gpr_mu_unlock(&s->mu); + destroy_setup(s); + } else { + gpr_mu_unlock(&s->mu); + } +} + +/* vtable for transport setup */ +static const grpc_transport_setup_vtable setup_vtable = {setup_initiate, + setup_cancel}; + +void grpc_client_setup_create_and_attach( + grpc_channel_stack *newly_minted_channel, const grpc_channel_args *args, + grpc_mdctx *mdctx, + void (*initiate)(void *user_data, grpc_client_setup_request *request), + void (*done)(void *user_data), void *user_data, grpc_em *em) { + grpc_client_setup *s = gpr_malloc(sizeof(grpc_client_setup)); + + s->base.vtable = &setup_vtable; + gpr_mu_init(&s->mu); + s->refs = 1; + s->mdctx = mdctx; + s->initiate = initiate; + s->done = done; + s->user_data = user_data; + s->em = em; + s->active_request = NULL; + s->args = grpc_channel_args_copy(args); + s->current_backoff_interval = gpr_time_from_micros(1000000); + s->in_alarm = 0; + + grpc_client_channel_set_transport_setup(newly_minted_channel, &s->base); +} + +int grpc_client_setup_request_should_continue(grpc_client_setup_request *r) { + int result; + if (gpr_time_cmp(gpr_now(), r->deadline) > 0) { + return 0; + } + gpr_mu_lock(&r->setup->mu); + result = r->setup->active_request == r; + gpr_mu_unlock(&r->setup->mu); + return result; +} + +static void backoff_alarm_done(void *arg /* grpc_client_setup */, + grpc_em_cb_status status) { + grpc_client_setup *s = arg; + grpc_client_setup_request *r = gpr_malloc(sizeof(grpc_client_setup_request)); + r->setup = s; + /* TODO(klempner): Set this to something useful */ + r->deadline = gpr_inf_future; + /* Handle status cancelled? */ + gpr_mu_lock(&s->mu); + s->active_request = r; + s->in_alarm = 0; + if (status != GRPC_CALLBACK_SUCCESS) { + if (0 == --s->refs) { + gpr_mu_unlock(&s->mu); + destroy_setup(s); + gpr_free(r); + return; + } else { + gpr_mu_unlock(&s->mu); + return; + } + } + gpr_mu_unlock(&s->mu); + s->initiate(s->user_data, r); +} + +void grpc_client_setup_request_finish(grpc_client_setup_request *r, + int was_successful) { + int retry = !was_successful; + grpc_client_setup *s = r->setup; + + gpr_mu_lock(&s->mu); + if (s->active_request == r) { + s->active_request = NULL; + } else { + retry = 0; + } + if (!retry && 0 == --s->refs) { + gpr_mu_unlock(&s->mu); + destroy_setup(s); + gpr_free(r); + return; + } + + gpr_free(r); + + if (retry) { + /* TODO(klempner): Replace these values with further consideration. 2x is + probably too aggressive of a backoff. */ + gpr_timespec max_backoff = gpr_time_from_micros(120000000); + GPR_ASSERT(!s->in_alarm); + s->in_alarm = 1; + grpc_em_alarm_init(&s->backoff_alarm, s->em, backoff_alarm_done, s); + grpc_em_alarm_add(&s->backoff_alarm, + gpr_time_add(s->current_backoff_interval, gpr_now())); + s->current_backoff_interval = + gpr_time_add(s->current_backoff_interval, s->current_backoff_interval); + if (gpr_time_cmp(s->current_backoff_interval, max_backoff) > 0) { + s->current_backoff_interval = max_backoff; + } + } + + gpr_mu_unlock(&s->mu); +} + +const grpc_channel_args *grpc_client_setup_get_channel_args( + grpc_client_setup_request *r) { + return r->setup->args; +} + +grpc_mdctx *grpc_client_setup_get_mdctx(grpc_client_setup_request *r) { + return r->setup->mdctx; +} diff --git a/src/core/channel/client_setup.h b/src/core/channel/client_setup.h new file mode 100644 index 0000000000..862c1325a3 --- /dev/null +++ b/src/core/channel/client_setup.h @@ -0,0 +1,68 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_CHANNEL_CLIENT_SETUP_H__ +#define __GRPC_INTERNAL_CHANNEL_CLIENT_SETUP_H__ + +#include "src/core/channel/client_channel.h" +#include "src/core/eventmanager/em.h" +#include "src/core/transport/metadata.h" +#include <grpc/support/time.h> + +/* Convenience API's to simplify transport setup */ + +typedef struct grpc_client_setup grpc_client_setup; +typedef struct grpc_client_setup_request grpc_client_setup_request; + +void grpc_client_setup_create_and_attach( + grpc_channel_stack *newly_minted_channel, const grpc_channel_args *args, + grpc_mdctx *mdctx, + void (*initiate)(void *user_data, grpc_client_setup_request *request), + void (*done)(void *user_data), void *user_data, grpc_em *em); + +/* Check that r is the active request: needs to be performed at each callback. + If this races, we'll have two connection attempts running at once and the + old one will get cleaned up in due course, which is fine. */ +int grpc_client_setup_request_should_continue(grpc_client_setup_request *r); +void grpc_client_setup_request_finish(grpc_client_setup_request *r, + int was_successful); +const grpc_channel_args *grpc_client_setup_get_channel_args( + grpc_client_setup_request *r); + +/* Get the deadline for a request passed in to initiate. Implementations should + make a best effort to honor this deadline. */ +gpr_timespec grpc_client_setup_request_deadline(grpc_client_setup_request *r); + +grpc_mdctx *grpc_client_setup_get_mdctx(grpc_client_setup_request *r); + +#endif /* __GRPC_INTERNAL_CHANNEL_CLIENT_SETUP_H__ */ diff --git a/src/core/channel/connected_channel.c b/src/core/channel/connected_channel.c new file mode 100644 index 0000000000..336472e740 --- /dev/null +++ b/src/core/channel/connected_channel.c @@ -0,0 +1,501 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/channel/connected_channel.h" + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +#include "src/core/transport/transport.h" +#include <grpc/byte_buffer.h> +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/slice_buffer.h> +#include <grpc/support/string.h> + +#define MAX_BUFFER_LENGTH 8192 +/* the protobuf library will (by default) start warning at 100megs */ +#define DEFAULT_MAX_MESSAGE_LENGTH (100 * 1024 * 1024) + +typedef struct { + grpc_transport *transport; + gpr_uint32 max_message_length; +} channel_data; + +typedef struct { + grpc_call_element *elem; + grpc_stream_op_buffer outgoing_sopb; + + gpr_uint32 max_message_length; + gpr_uint32 incoming_message_length; + gpr_uint8 reading_message; + gpr_uint8 got_metadata_boundary; + gpr_uint8 got_read_close; + gpr_slice_buffer incoming_message; + gpr_uint32 outgoing_buffer_length_estimate; +} 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) + +/* Copy the contents of a byte buffer into stream ops */ +static void copy_byte_buffer_to_stream_ops(grpc_byte_buffer *byte_buffer, + grpc_stream_op_buffer *sopb) { + size_t i; + + switch (byte_buffer->type) { + case GRPC_BB_SLICE_BUFFER: + for (i = 0; i < byte_buffer->data.slice_buffer.count; i++) { + gpr_slice slice = byte_buffer->data.slice_buffer.slices[i]; + gpr_slice_ref(slice); + grpc_sopb_add_slice(sopb, slice); + } + break; + } +} + +/* Flush queued stream operations onto the transport */ +static void end_bufferable_op(grpc_call_op *op, channel_data *chand, + call_data *calld, int is_last) { + size_t nops; + + if (op->flags & GRPC_WRITE_BUFFER_HINT) { + if (calld->outgoing_buffer_length_estimate < MAX_BUFFER_LENGTH) { + op->done_cb(op->user_data, GRPC_OP_OK); + return; + } + } + + calld->outgoing_buffer_length_estimate = 0; + grpc_sopb_add_flow_ctl_cb(&calld->outgoing_sopb, op->done_cb, op->user_data); + + nops = calld->outgoing_sopb.nops; + calld->outgoing_sopb.nops = 0; + grpc_transport_send_batch(chand->transport, + TRANSPORT_STREAM_FROM_CALL_DATA(calld), + calld->outgoing_sopb.ops, nops, is_last); +} + +/* Intercept a call operation and either push it directly up or translate it + into transport stream operations */ +static void call_op(grpc_call_element *elem, grpc_call_op *op) { + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + GPR_ASSERT(elem->filter == &grpc_connected_channel_filter); + GRPC_CALL_LOG_OP(GPR_INFO, elem, op); + + switch (op->type) { + case GRPC_SEND_METADATA: + grpc_sopb_add_metadata(&calld->outgoing_sopb, op->data.metadata); + grpc_sopb_add_flow_ctl_cb(&calld->outgoing_sopb, op->done_cb, + op->user_data); + break; + case GRPC_SEND_DEADLINE: + grpc_sopb_add_deadline(&calld->outgoing_sopb, op->data.deadline); + grpc_sopb_add_flow_ctl_cb(&calld->outgoing_sopb, op->done_cb, + op->user_data); + break; + case GRPC_SEND_START: + grpc_sopb_add_metadata_boundary(&calld->outgoing_sopb); + end_bufferable_op(op, chand, calld, 0); + break; + case GRPC_SEND_MESSAGE: + grpc_sopb_add_begin_message(&calld->outgoing_sopb, + grpc_byte_buffer_length(op->data.message), + op->flags); + copy_byte_buffer_to_stream_ops(op->data.message, &calld->outgoing_sopb); + calld->outgoing_buffer_length_estimate += + (5 + grpc_byte_buffer_length(op->data.message)); + end_bufferable_op(op, chand, calld, 0); + break; + case GRPC_SEND_FINISH: + end_bufferable_op(op, chand, calld, 1); + break; + case GRPC_REQUEST_DATA: + /* re-arm window updates if they were disarmed by finish_message */ + grpc_transport_set_allow_window_updates( + chand->transport, TRANSPORT_STREAM_FROM_CALL_DATA(calld), 1); + break; + case GRPC_CANCEL_OP: + grpc_transport_abort_stream(chand->transport, + TRANSPORT_STREAM_FROM_CALL_DATA(calld), + GRPC_STATUS_CANCELLED); + break; + default: + GPR_ASSERT(op->dir == GRPC_CALL_UP); + grpc_call_next_op(elem, op); + break; + } +} + +/* Currently we assume all channel operations should just be pushed up. */ +static void channel_op(grpc_channel_element *elem, grpc_channel_op *op) { + channel_data *chand = elem->channel_data; + GPR_ASSERT(elem->filter == &grpc_connected_channel_filter); + + switch (op->type) { + case GRPC_CHANNEL_SHUTDOWN: + grpc_transport_close(chand->transport); + break; + default: + GPR_ASSERT(op->dir == GRPC_CALL_UP); + grpc_channel_next_op(elem, op); + break; + } +} + +/* Constructor for call_data */ +static void init_call_elem(grpc_call_element *elem, + const void *server_transport_data) { + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + int r; + + GPR_ASSERT(elem->filter == &grpc_connected_channel_filter); + calld->elem = elem; + grpc_sopb_init(&calld->outgoing_sopb); + + calld->reading_message = 0; + calld->got_metadata_boundary = 0; + calld->got_read_close = 0; + calld->outgoing_buffer_length_estimate = 0; + calld->max_message_length = chand->max_message_length; + gpr_slice_buffer_init(&calld->incoming_message); + r = grpc_transport_init_stream(chand->transport, + TRANSPORT_STREAM_FROM_CALL_DATA(calld), + server_transport_data); + GPR_ASSERT(r == 0); +} + +/* Destructor for call_data */ +static void destroy_call_elem(grpc_call_element *elem) { + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + GPR_ASSERT(elem->filter == &grpc_connected_channel_filter); + grpc_sopb_destroy(&calld->outgoing_sopb); + gpr_slice_buffer_destroy(&calld->incoming_message); + grpc_transport_destroy_stream(chand->transport, + TRANSPORT_STREAM_FROM_CALL_DATA(calld)); +} + +/* Constructor for channel_data */ +static void init_channel_elem(grpc_channel_element *elem, + const grpc_channel_args *args, grpc_mdctx *mdctx, + int is_first, int is_last) { + channel_data *cd = (channel_data *)elem->channel_data; + size_t i; + GPR_ASSERT(!is_first); + GPR_ASSERT(is_last); + GPR_ASSERT(elem->filter == &grpc_connected_channel_filter); + cd->transport = NULL; + + cd->max_message_length = DEFAULT_MAX_MESSAGE_LENGTH; + if (args) { + for (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 { + cd->max_message_length = args->args[i].value.integer; + } + } + } + } +} + +/* Destructor for channel_data */ +static void destroy_channel_elem(grpc_channel_element *elem) { + channel_data *cd = (channel_data *)elem->channel_data; + GPR_ASSERT(elem->filter == &grpc_connected_channel_filter); + grpc_transport_destroy(cd->transport); +} + +const grpc_channel_filter grpc_connected_channel_filter = { + call_op, channel_op, + + sizeof(call_data), init_call_elem, destroy_call_elem, + + sizeof(channel_data), init_channel_elem, destroy_channel_elem, + + "connected", +}; + +static gpr_slice alloc_recv_buffer(void *user_data, grpc_transport *transport, + grpc_stream *stream, size_t size_hint) { + return gpr_slice_malloc(size_hint); +} + +/* Transport callback to accept a new stream... calls up to handle it */ +static void accept_stream(void *user_data, grpc_transport *transport, + const void *transport_server_data) { + grpc_channel_element *elem = user_data; + channel_data *chand = elem->channel_data; + grpc_channel_op op; + + GPR_ASSERT(elem->filter == &grpc_connected_channel_filter); + GPR_ASSERT(chand->transport == transport); + + op.type = GRPC_ACCEPT_CALL; + op.dir = GRPC_CALL_UP; + op.data.accept_call.transport = transport; + op.data.accept_call.transport_server_data = transport_server_data; + channel_op(elem, &op); +} + +static void recv_error(channel_data *chand, call_data *calld, int line, + const char *fmt, ...) { + char msg[512]; + va_list a; + + va_start(a, fmt); + vsprintf(msg, fmt, a); + va_end(a); + + gpr_log(__FILE__, line, GPR_LOG_SEVERITY_ERROR, "%s", msg); + + if (chand->transport) { + grpc_transport_abort_stream(chand->transport, + TRANSPORT_STREAM_FROM_CALL_DATA(calld), + GRPC_STATUS_INVALID_ARGUMENT); + } +} + +static void do_nothing(void *calldata, grpc_op_error error) {} + +static void done_message(void *user_data, grpc_op_error error) { + grpc_byte_buffer_destroy(user_data); +} + +static void finish_message(channel_data *chand, call_data *calld) { + grpc_call_element *elem = calld->elem; + grpc_call_op call_op; + call_op.dir = GRPC_CALL_UP; + call_op.flags = 0; + /* if we got all the bytes for this message, call up the stack */ + call_op.type = GRPC_RECV_MESSAGE; + call_op.done_cb = done_message; + /* TODO(ctiller): this could be a lot faster if coded directly */ + call_op.user_data = call_op.data.message = grpc_byte_buffer_create( + calld->incoming_message.slices, calld->incoming_message.count); + gpr_slice_buffer_reset_and_unref(&calld->incoming_message); + + /* disable window updates until we get a request more from above */ + grpc_transport_set_allow_window_updates( + chand->transport, TRANSPORT_STREAM_FROM_CALL_DATA(calld), 0); + + GPR_ASSERT(calld->incoming_message.count == 0); + calld->reading_message = 0; + grpc_call_next_op(elem, &call_op); +} + +/* Handle incoming stream ops from the transport, translating them into + call_ops to pass up the call stack */ +static void recv_batch(void *user_data, grpc_transport *transport, + grpc_stream *stream, grpc_stream_op *ops, + size_t ops_count, grpc_stream_state final_state) { + call_data *calld = CALL_DATA_FROM_TRANSPORT_STREAM(stream); + grpc_call_element *elem = calld->elem; + channel_data *chand = elem->channel_data; + grpc_stream_op *stream_op; + grpc_call_op call_op; + size_t i; + gpr_uint32 length; + + GPR_ASSERT(elem->filter == &grpc_connected_channel_filter); + + for (i = 0; i < ops_count; i++) { + stream_op = ops + i; + switch (stream_op->type) { + case GRPC_OP_FLOW_CTL_CB: + gpr_log(GPR_ERROR, + "should not receive flow control ops from transport"); + abort(); + break; + case GRPC_NO_OP: + break; + case GRPC_OP_METADATA: + call_op.type = GRPC_RECV_METADATA; + call_op.dir = GRPC_CALL_UP; + call_op.flags = 0; + call_op.data.metadata = stream_op->data.metadata; + call_op.done_cb = do_nothing; + call_op.user_data = NULL; + grpc_call_next_op(elem, &call_op); + break; + case GRPC_OP_DEADLINE: + call_op.type = GRPC_RECV_DEADLINE; + call_op.dir = GRPC_CALL_UP; + call_op.flags = 0; + call_op.data.deadline = stream_op->data.deadline; + call_op.done_cb = do_nothing; + call_op.user_data = NULL; + grpc_call_next_op(elem, &call_op); + break; + case GRPC_OP_METADATA_BOUNDARY: + if (!calld->got_metadata_boundary) { + calld->got_metadata_boundary = 1; + call_op.type = GRPC_RECV_END_OF_INITIAL_METADATA; + call_op.dir = GRPC_CALL_UP; + call_op.flags = 0; + call_op.done_cb = do_nothing; + call_op.user_data = NULL; + grpc_call_next_op(elem, &call_op); + } + break; + case GRPC_OP_BEGIN_MESSAGE: + /* can't begin a message when we're still reading a message */ + if (calld->reading_message) { + recv_error(chand, calld, __LINE__, + "Message terminated early; read %d bytes, expected %d", + calld->incoming_message.length, + calld->incoming_message_length); + return; + } + /* stash away parameters, and prepare for incoming slices */ + length = stream_op->data.begin_message.length; + if (length > calld->max_message_length) { + recv_error( + chand, calld, __LINE__, + "Maximum message length of %d exceeded by a message of length %d", + calld->max_message_length, length); + } else if (length > 0) { + calld->reading_message = 1; + calld->incoming_message_length = length; + } else { + finish_message(chand, calld); + } + break; + case GRPC_OP_SLICE: + if (GPR_SLICE_LENGTH(stream_op->data.slice) == 0) { + gpr_slice_unref(stream_op->data.slice); + break; + } + /* we have to be reading a message to know what to do here */ + if (!calld->reading_message) { + recv_error(chand, calld, __LINE__, + "Received payload data while not reading a message"); + return; + } + /* append the slice to the incoming buffer */ + gpr_slice_buffer_add(&calld->incoming_message, stream_op->data.slice); + if (calld->incoming_message.length > calld->incoming_message_length) { + /* if we got too many bytes, complain */ + recv_error(chand, calld, __LINE__, + "Receiving message overflow; read %d bytes, expected %d", + calld->incoming_message.length, + calld->incoming_message_length); + return; + } else if (calld->incoming_message.length == + calld->incoming_message_length) { + finish_message(chand, calld); + } + } + } + /* if the stream closed, then call up the stack to let it know */ + if (!calld->got_read_close && (final_state == GRPC_STREAM_RECV_CLOSED || + final_state == GRPC_STREAM_CLOSED)) { + calld->got_read_close = 1; + if (calld->reading_message) { + recv_error(chand, calld, __LINE__, + "Last message truncated; read %d bytes, expected %d", + calld->incoming_message.length, + calld->incoming_message_length); + return; + } + call_op.type = GRPC_RECV_HALF_CLOSE; + call_op.dir = GRPC_CALL_UP; + call_op.flags = 0; + call_op.done_cb = do_nothing; + call_op.user_data = NULL; + grpc_call_next_op(elem, &call_op); + } + if (final_state == GRPC_STREAM_CLOSED) { + call_op.type = GRPC_RECV_FINISH; + call_op.dir = GRPC_CALL_UP; + call_op.flags = 0; + call_op.done_cb = do_nothing; + call_op.user_data = NULL; + grpc_call_next_op(elem, &call_op); + } +} + +static void transport_closed(void *user_data, grpc_transport *transport) { + /* transport was closed ==> call up and handle it */ + grpc_channel_element *elem = user_data; + channel_data *chand = elem->channel_data; + grpc_channel_op op; + + GPR_ASSERT(elem->filter == &grpc_connected_channel_filter); + GPR_ASSERT(chand->transport == transport); + + op.type = GRPC_TRANSPORT_CLOSED; + op.dir = GRPC_CALL_UP; + channel_op(elem, &op); +} + +const grpc_transport_callbacks connected_channel_transport_callbacks = { + alloc_recv_buffer, accept_stream, recv_batch, transport_closed, +}; + +grpc_transport_setup_result grpc_connected_channel_bind_transport( + grpc_channel_stack *channel_stack, grpc_transport *transport) { + /* Assumes that the connected channel filter is always the last filter + in a channel stack */ + grpc_channel_element *elem = grpc_channel_stack_last_element(channel_stack); + channel_data *cd = (channel_data *)elem->channel_data; + grpc_transport_setup_result ret; + GPR_ASSERT(elem->filter == &grpc_connected_channel_filter); + GPR_ASSERT(cd->transport == NULL); + cd->transport = transport; + + /* 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(transport); + + ret.user_data = elem; + ret.callbacks = &connected_channel_transport_callbacks; + return ret; +} diff --git a/src/core/channel/connected_channel.h b/src/core/channel/connected_channel.h new file mode 100644 index 0000000000..660ea7ad89 --- /dev/null +++ b/src/core/channel/connected_channel.h @@ -0,0 +1,49 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_CHANNEL_CONNECTED_CHANNEL_H__ +#define __GRPC_INTERNAL_CHANNEL_CONNECTED_CHANNEL_H__ + +#include "src/core/channel/channel_stack.h" + +/* A channel filter representing a channel that is on a connected transport. + This filter performs actual sending and receiving of messages. */ + +extern const grpc_channel_filter grpc_connected_channel_filter; + +/* Post construction fixup: set the transport in the connected channel. + Must be called before any call stack using this filter is used. */ +grpc_transport_setup_result grpc_connected_channel_bind_transport( + grpc_channel_stack *channel_stack, grpc_transport *transport); + +#endif /* __GRPC_INTERNAL_CHANNEL_CONNECTED_CHANNEL_H__ */ diff --git a/src/core/channel/http_client_filter.c b/src/core/channel/http_client_filter.c new file mode 100644 index 0000000000..b82c7352d3 --- /dev/null +++ b/src/core/channel/http_client_filter.c @@ -0,0 +1,143 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/channel/http_client_filter.h" +#include <grpc/support/log.h> + +typedef struct call_data { + int unused; /* C89 requires at least one struct element */ +} call_data; + +typedef struct channel_data { grpc_mdelem *te_trailers; } channel_data; + +/* used to silence 'variable not used' warnings */ +static void ignore_unused(void *ignored) {} + +/* 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 call_op(grpc_call_element *elem, grpc_call_op *op) { + /* grab pointers to our data from the call element */ + call_data *calld = elem->call_data; + channel_data *channeld = elem->channel_data; + GRPC_CALL_LOG_OP(GPR_INFO, elem, op); + + ignore_unused(calld); + + switch (op->type) { + case GRPC_SEND_START: + /* just prior to starting, add a te: trailers header */ + grpc_call_element_send_metadata(elem, channeld->te_trailers); + grpc_call_next_op(elem, op); + break; + default: + /* pass control up or down the stack depending on op->dir */ + grpc_call_next_op(elem, op); + break; + } +} + +/* Called on special channel events, such as disconnection or new incoming + calls on the server */ +static void channel_op(grpc_channel_element *elem, grpc_channel_op *op) { + /* grab pointers to our data from the channel element */ + channel_data *channeld = elem->channel_data; + + ignore_unused(channeld); + + switch (op->type) { + default: + /* pass control up or down the stack depending on op->dir */ + grpc_channel_next_op(elem, op); + break; + } +} + +/* Constructor for call_data */ +static void init_call_elem(grpc_call_element *elem, + const void *server_transport_data) { + /* grab pointers to our data from the call element */ + call_data *calld = elem->call_data; + channel_data *channeld = elem->channel_data; + + ignore_unused(channeld); + + /* initialize members */ + calld->unused = 0; +} + +/* Destructor for call_data */ +static void destroy_call_elem(grpc_call_element *elem) { + /* grab pointers to our data from the call element */ + call_data *calld = elem->call_data; + channel_data *channeld = elem->channel_data; + + ignore_unused(calld); + ignore_unused(channeld); +} + +/* Constructor for channel_data */ +static void init_channel_elem(grpc_channel_element *elem, + const grpc_channel_args *args, grpc_mdctx *mdctx, + int is_first, int is_last) { + /* grab pointers to our data from the channel element */ + channel_data *channeld = 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(!is_first); + GPR_ASSERT(!is_last); + + /* initialize members */ + channeld->te_trailers = grpc_mdelem_from_strings(mdctx, "te", "trailers"); +} + +/* Destructor for channel data */ +static void destroy_channel_elem(grpc_channel_element *elem) { + /* grab pointers to our data from the channel element */ + channel_data *channeld = elem->channel_data; + + grpc_mdelem_unref(channeld->te_trailers); +} + +const grpc_channel_filter grpc_http_client_filter = { + call_op, channel_op, + + sizeof(call_data), init_call_elem, destroy_call_elem, + + sizeof(channel_data), init_channel_elem, destroy_channel_elem, + + "http-client"}; diff --git a/src/core/channel/http_client_filter.h b/src/core/channel/http_client_filter.h new file mode 100644 index 0000000000..f939cbd351 --- /dev/null +++ b/src/core/channel/http_client_filter.h @@ -0,0 +1,42 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_CHANNEL_HTTP_CLIENT_FILTER_H__ +#define __GRPC_INTERNAL_CHANNEL_HTTP_CLIENT_FILTER_H__ + +#include "src/core/channel/channel_stack.h" + +/* Processes metadata on the client side for HTTP2 transports */ +extern const grpc_channel_filter grpc_http_client_filter; + +#endif /* __GRPC_INTERNAL_CHANNEL_HTTP_CLIENT_FILTER_H__ */ diff --git a/src/core/channel/http_filter.c b/src/core/channel/http_filter.c new file mode 100644 index 0000000000..b5c154144e --- /dev/null +++ b/src/core/channel/http_filter.c @@ -0,0 +1,139 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/channel/http_filter.h" +#include <grpc/support/log.h> + +typedef struct call_data { + int unused; /* C89 requires at least one struct element */ +} call_data; + +typedef struct channel_data { + int unused; /* C89 requires at least one struct element */ +} channel_data; + +/* used to silence 'variable not used' warnings */ +static void ignore_unused(void *ignored) {} + +/* 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 call_op(grpc_call_element *elem, grpc_call_op *op) { + /* grab pointers to our data from the call element */ + call_data *calld = elem->call_data; + channel_data *channeld = elem->channel_data; + GRPC_CALL_LOG_OP(GPR_INFO, elem, op); + + ignore_unused(calld); + ignore_unused(channeld); + + switch (op->type) { + default: + /* pass control up or down the stack depending on op->dir */ + grpc_call_next_op(elem, op); + break; + } +} + +/* Called on special channel events, such as disconnection or new incoming + calls on the server */ +static void channel_op(grpc_channel_element *elem, grpc_channel_op *op) { + /* grab pointers to our data from the channel element */ + channel_data *channeld = elem->channel_data; + + ignore_unused(channeld); + + switch (op->type) { + default: + /* pass control up or down the stack depending on op->dir */ + grpc_channel_next_op(elem, op); + break; + } +} + +/* Constructor for call_data */ +static void init_call_elem(grpc_call_element *elem, + const void *server_transport_data) { + /* grab pointers to our data from the call element */ + call_data *calld = elem->call_data; + channel_data *channeld = elem->channel_data; + + /* initialize members */ + calld->unused = channeld->unused; +} + +/* Destructor for call_data */ +static void destroy_call_elem(grpc_call_element *elem) { + /* grab pointers to our data from the call element */ + call_data *calld = elem->call_data; + channel_data *channeld = elem->channel_data; + + ignore_unused(calld); + ignore_unused(channeld); +} + +/* Constructor for channel_data */ +static void init_channel_elem(grpc_channel_element *elem, + const grpc_channel_args *args, grpc_mdctx *mdctx, + int is_first, int is_last) { + /* grab pointers to our data from the channel element */ + channel_data *channeld = 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(!is_first); + GPR_ASSERT(!is_last); + + /* initialize members */ + channeld->unused = 0; +} + +/* Destructor for channel data */ +static void destroy_channel_elem(grpc_channel_element *elem) { + /* grab pointers to our data from the channel element */ + channel_data *channeld = elem->channel_data; + + ignore_unused(channeld); +} + +const grpc_channel_filter grpc_http_filter = { + call_op, channel_op, + + sizeof(call_data), init_call_elem, destroy_call_elem, + + sizeof(channel_data), init_channel_elem, destroy_channel_elem, + + "http"}; diff --git a/src/core/channel/http_filter.h b/src/core/channel/http_filter.h new file mode 100644 index 0000000000..89ad482d35 --- /dev/null +++ b/src/core/channel/http_filter.h @@ -0,0 +1,43 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_CHANNEL_HTTP_FILTER_H__ +#define __GRPC_INTERNAL_CHANNEL_HTTP_FILTER_H__ + +#include "src/core/channel/channel_stack.h" + +/* Processes metadata that is common to both client and server for HTTP2 + transports. */ +extern const grpc_channel_filter grpc_http_filter; + +#endif /* __GRPC_INTERNAL_CHANNEL_HTTP_FILTER_H__ */ diff --git a/src/core/channel/http_server_filter.c b/src/core/channel/http_server_filter.c new file mode 100644 index 0000000000..b176064813 --- /dev/null +++ b/src/core/channel/http_server_filter.c @@ -0,0 +1,150 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/channel/http_server_filter.h" +#include <grpc/support/log.h> + +typedef struct call_data { + int unused; /* C89 requires at least one struct element */ +} call_data; + +typedef struct channel_data { grpc_mdelem *te_trailers; } channel_data; + +/* used to silence 'variable not used' warnings */ +static void ignore_unused(void *ignored) {} + +/* 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 call_op(grpc_call_element *elem, grpc_call_op *op) { + /* grab pointers to our data from the call element */ + call_data *calld = elem->call_data; + channel_data *channeld = elem->channel_data; + GRPC_CALL_LOG_OP(GPR_INFO, elem, op); + + ignore_unused(calld); + ignore_unused(channeld); + + switch (op->type) { + case GRPC_RECV_METADATA: + /* check if it's a te: trailers header */ + if (op->data.metadata == channeld->te_trailers) { + /* swallow it */ + grpc_mdelem_unref(op->data.metadata); + op->done_cb(op->user_data, GRPC_OP_OK); + } else { + /* pass the event up */ + grpc_call_next_op(elem, op); + } + break; + default: + /* pass control up or down the stack depending on op->dir */ + grpc_call_next_op(elem, op); + break; + } +} + +/* Called on special channel events, such as disconnection or new incoming + calls on the server */ +static void channel_op(grpc_channel_element *elem, grpc_channel_op *op) { + /* grab pointers to our data from the channel element */ + channel_data *channeld = elem->channel_data; + + ignore_unused(channeld); + + switch (op->type) { + default: + /* pass control up or down the stack depending on op->dir */ + grpc_channel_next_op(elem, op); + break; + } +} + +/* Constructor for call_data */ +static void init_call_elem(grpc_call_element *elem, + const void *server_transport_data) { + /* grab pointers to our data from the call element */ + call_data *calld = elem->call_data; + channel_data *channeld = elem->channel_data; + + ignore_unused(channeld); + + /* initialize members */ + calld->unused = 0; +} + +/* Destructor for call_data */ +static void destroy_call_elem(grpc_call_element *elem) { + /* grab pointers to our data from the call element */ + call_data *calld = elem->call_data; + channel_data *channeld = elem->channel_data; + + ignore_unused(calld); + ignore_unused(channeld); +} + +/* Constructor for channel_data */ +static void init_channel_elem(grpc_channel_element *elem, + const grpc_channel_args *args, grpc_mdctx *mdctx, + int is_first, int is_last) { + /* grab pointers to our data from the channel element */ + channel_data *channeld = 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(!is_first); + GPR_ASSERT(!is_last); + + /* initialize members */ + channeld->te_trailers = grpc_mdelem_from_strings(mdctx, "te", "trailers"); +} + +/* Destructor for channel data */ +static void destroy_channel_elem(grpc_channel_element *elem) { + /* grab pointers to our data from the channel element */ + channel_data *channeld = elem->channel_data; + + grpc_mdelem_unref(channeld->te_trailers); +} + +const grpc_channel_filter grpc_http_server_filter = { + call_op, channel_op, + + sizeof(call_data), init_call_elem, destroy_call_elem, + + sizeof(channel_data), init_channel_elem, destroy_channel_elem, + + "http-server"}; diff --git a/src/core/channel/http_server_filter.h b/src/core/channel/http_server_filter.h new file mode 100644 index 0000000000..5b475432aa --- /dev/null +++ b/src/core/channel/http_server_filter.h @@ -0,0 +1,42 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_CHANNEL_HTTP_SERVER_FILTER_H__ +#define __GRPC_INTERNAL_CHANNEL_HTTP_SERVER_FILTER_H__ + +#include "src/core/channel/channel_stack.h" + +/* Processes metadata on the client side for HTTP2 transports */ +extern const grpc_channel_filter grpc_http_server_filter; + +#endif /* __GRPC_INTERNAL_CHANNEL_HTTP_SERVER_FILTER_H__ */ diff --git a/src/core/channel/metadata_buffer.c b/src/core/channel/metadata_buffer.c new file mode 100644 index 0000000000..75fd90b707 --- /dev/null +++ b/src/core/channel/metadata_buffer.c @@ -0,0 +1,198 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/channel/metadata_buffer.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/useful.h> + +#include <string.h> + +#define INITIAL_ELEM_CAP 8 + +/* One queued call; we track offsets to string data in a shared buffer to + reduce allocations. See grpc_metadata_buffer_impl for the memory use + strategy */ +typedef struct { + grpc_mdelem *md; + void (*cb)(void *user_data, grpc_op_error error); + void *user_data; + gpr_uint32 flags; +} qelem; + +/* Memory layout: + + grpc_metadata_buffer_impl + followed by an array of qelem */ +struct grpc_metadata_buffer_impl { + /* number of elements in q */ + size_t elems; + /* capacity of q */ + size_t elem_cap; +}; + +#define ELEMS(buffer) ((qelem *)((buffer)+1)) + +void grpc_metadata_buffer_init(grpc_metadata_buffer *buffer) { + /* start buffer as NULL, indicating no elements */ + *buffer = NULL; +} + +void grpc_metadata_buffer_destroy(grpc_metadata_buffer *buffer, + grpc_op_error error) { + size_t i; + qelem *qe; + if (*buffer) { + for (i = 0; i < (*buffer)->elems; i++) { + qe = &ELEMS(*buffer)[i]; + grpc_mdelem_unref(qe->md); + qe->cb(qe->user_data, error); + } + gpr_free(*buffer); + } +} + +void grpc_metadata_buffer_queue(grpc_metadata_buffer *buffer, + grpc_call_op *op) { + grpc_metadata_buffer_impl *impl = *buffer; + qelem *qe; + size_t bytes; + + GPR_ASSERT(op->type == GRPC_SEND_METADATA || op->type == GRPC_RECV_METADATA); + + if (!impl) { + /* this is the first element: allocate enough space to hold the + header object and the initial element capacity of qelems */ + bytes = + sizeof(grpc_metadata_buffer_impl) + INITIAL_ELEM_CAP * sizeof(qelem); + impl = gpr_malloc(bytes); + /* initialize the header object */ + impl->elems = 0; + impl->elem_cap = INITIAL_ELEM_CAP; + } else if (impl->elems == impl->elem_cap) { + /* more qelems than what we can deal with: grow by doubling size */ + impl->elem_cap *= 2; + bytes = sizeof(grpc_metadata_buffer_impl) + impl->elem_cap * sizeof(qelem); + impl = gpr_realloc(impl, bytes); + } + + /* append an element to the queue */ + qe = &ELEMS(impl)[impl->elems]; + impl->elems++; + + qe->md = op->data.metadata; + qe->cb = op->done_cb; + qe->user_data = op->user_data; + qe->flags = op->flags; + + /* header object may have changed location: store it back */ + *buffer = impl; +} + +void grpc_metadata_buffer_flush(grpc_metadata_buffer *buffer, + grpc_call_element *elem) { + grpc_metadata_buffer_impl *impl = *buffer; + grpc_call_op op; + qelem *qe; + size_t i; + + if (!impl) { + /* nothing to send */ + return; + } + + /* construct call_op's, and push them down the stack */ + op.type = GRPC_SEND_METADATA; + op.dir = GRPC_CALL_DOWN; + for (i = 0; i < impl->elems; i++) { + qe = &ELEMS(impl)[i]; + op.done_cb = qe->cb; + op.user_data = qe->user_data; + op.flags = qe->flags; + op.data.metadata = qe->md; + grpc_call_next_op(elem, &op); + } + + /* free data structures and reset to NULL: we can only flush once */ + gpr_free(impl); + *buffer = NULL; +} + +size_t grpc_metadata_buffer_count(const grpc_metadata_buffer *buffer) { + return *buffer ? (*buffer)->elems : 0; +} + +typedef struct { grpc_metadata_buffer_impl *impl; } elems_hdr; + +grpc_metadata *grpc_metadata_buffer_extract_elements( + grpc_metadata_buffer *buffer) { + grpc_metadata_buffer_impl *impl; + elems_hdr *hdr; + qelem *src; + grpc_metadata *out; + size_t i; + + impl = *buffer; + + if (!impl) { + return NULL; + } + + hdr = gpr_malloc(sizeof(elems_hdr) + impl->elems * sizeof(grpc_metadata)); + src = ELEMS(impl); + out = (grpc_metadata *)(hdr + 1); + + hdr->impl = impl; + for (i = 0; i < impl->elems; i++) { + out[i].key = (char *)grpc_mdstr_as_c_string(src[i].md->key); + out[i].value = (char *)grpc_mdstr_as_c_string(src[i].md->value); + out[i].value_length = GPR_SLICE_LENGTH(src[i].md->value->slice); + } + + /* clear out buffer (it's not possible to extract elements twice */ + *buffer = NULL; + + return out; +} + +void grpc_metadata_buffer_cleanup_elements(void *elements, + grpc_op_error error) { + elems_hdr *hdr = ((elems_hdr *)elements) - 1; + + if (!elements) { + return; + } + + grpc_metadata_buffer_destroy(&hdr->impl, error); + gpr_free(hdr); +} diff --git a/src/core/channel/metadata_buffer.h b/src/core/channel/metadata_buffer.h new file mode 100644 index 0000000000..818b290ce2 --- /dev/null +++ b/src/core/channel/metadata_buffer.h @@ -0,0 +1,70 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_CHANNEL_METADATA_BUFFER_H__ +#define __GRPC_INTERNAL_CHANNEL_METADATA_BUFFER_H__ + +#include "src/core/channel/channel_stack.h" + +/* Utility code to buffer GRPC_SEND_METADATA calls and pass them down the stack + all at once at some otherwise-determined time. Useful for implementing + filters that want to queue metadata until a START event chooses some + underlying filter stack to send an rpc on. */ + +/* Clients should declare a member of grpc_metadata_buffer. This may at some + point become a typedef for a struct, but for now a pointer suffices */ +typedef struct grpc_metadata_buffer_impl grpc_metadata_buffer_impl; +typedef grpc_metadata_buffer_impl *grpc_metadata_buffer; + +/* Initializes the metadata buffer. Allocates no memory. */ +void grpc_metadata_buffer_init(grpc_metadata_buffer *buffer); +/* Destroy the metadata buffer. */ +void grpc_metadata_buffer_destroy(grpc_metadata_buffer *buffer, + grpc_op_error error); +/* Append a call to the end of a metadata buffer: may allocate memory */ +void grpc_metadata_buffer_queue(grpc_metadata_buffer *buffer, grpc_call_op *op); +/* Flush all queued operations from the metadata buffer to the element below + self */ +void grpc_metadata_buffer_flush(grpc_metadata_buffer *buffer, + grpc_call_element *self); +/* Count the number of queued elements in the buffer. */ +size_t grpc_metadata_buffer_count(const grpc_metadata_buffer *buffer); +/* Extract elements as a grpc_metadata*, for presentation to applications. + The returned buffer must be freed with + grpc_metadata_buffer_cleanup_elements. + Clears the metadata buffer (this is a one-shot operation) */ +grpc_metadata *grpc_metadata_buffer_extract_elements( + grpc_metadata_buffer *buffer); +void grpc_metadata_buffer_cleanup_elements(void *elements, grpc_op_error error); + +#endif /* __GRPC_INTERNAL_CHANNEL_METADATA_BUFFER_H__ */ diff --git a/src/core/channel/noop_filter.c b/src/core/channel/noop_filter.c new file mode 100644 index 0000000000..705df4a707 --- /dev/null +++ b/src/core/channel/noop_filter.c @@ -0,0 +1,138 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/channel/noop_filter.h" +#include <grpc/support/log.h> + +typedef struct call_data { + int unused; /* C89 requires at least one struct element */ +} call_data; + +typedef struct channel_data { + int unused; /* C89 requires at least one struct element */ +} channel_data; + +/* used to silence 'variable not used' warnings */ +static void ignore_unused(void *ignored) {} + +/* 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 call_op(grpc_call_element *elem, grpc_call_op *op) { + /* grab pointers to our data from the call element */ + call_data *calld = elem->call_data; + channel_data *channeld = elem->channel_data; + + ignore_unused(calld); + ignore_unused(channeld); + + switch (op->type) { + default: + /* pass control up or down the stack depending on op->dir */ + grpc_call_next_op(elem, op); + break; + } +} + +/* Called on special channel events, such as disconnection or new incoming + calls on the server */ +static void channel_op(grpc_channel_element *elem, grpc_channel_op *op) { + /* grab pointers to our data from the channel element */ + channel_data *channeld = elem->channel_data; + + ignore_unused(channeld); + + switch (op->type) { + default: + /* pass control up or down the stack depending on op->dir */ + grpc_channel_next_op(elem, op); + break; + } +} + +/* Constructor for call_data */ +static void init_call_elem(grpc_call_element *elem, + const void *server_transport_data) { + /* grab pointers to our data from the call element */ + call_data *calld = elem->call_data; + channel_data *channeld = elem->channel_data; + + /* initialize members */ + calld->unused = channeld->unused; +} + +/* Destructor for call_data */ +static void destroy_call_elem(grpc_call_element *elem) { + /* grab pointers to our data from the call element */ + call_data *calld = elem->call_data; + channel_data *channeld = elem->channel_data; + + ignore_unused(calld); + ignore_unused(channeld); +} + +/* Constructor for channel_data */ +static void init_channel_elem(grpc_channel_element *elem, + const grpc_channel_args *args, grpc_mdctx *mdctx, + int is_first, int is_last) { + /* grab pointers to our data from the channel element */ + channel_data *channeld = 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(!is_first); + GPR_ASSERT(!is_last); + + /* initialize members */ + channeld->unused = 0; +} + +/* Destructor for channel data */ +static void destroy_channel_elem(grpc_channel_element *elem) { + /* grab pointers to our data from the channel element */ + channel_data *channeld = elem->channel_data; + + ignore_unused(channeld); +} + +const grpc_channel_filter grpc_no_op_filter = { + call_op, channel_op, + + sizeof(call_data), init_call_elem, destroy_call_elem, + + sizeof(channel_data), init_channel_elem, destroy_channel_elem, + + "no-op"}; diff --git a/src/core/channel/noop_filter.h b/src/core/channel/noop_filter.h new file mode 100644 index 0000000000..4057ff7ac9 --- /dev/null +++ b/src/core/channel/noop_filter.h @@ -0,0 +1,44 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_CHANNEL_NOOP_FILTER_H__ +#define __GRPC_INTERNAL_CHANNEL_NOOP_FILTER_H__ + +#include "src/core/channel/channel_stack.h" + +/* No-op filter: simply takes everything it's given, and passes it on to the + next filter. Exists simply as a starting point that others can take and + customize for their own filters */ +extern const grpc_channel_filter grpc_no_op_filter; + +#endif /* __GRPC_INTERNAL_CHANNEL_NOOP_FILTER_H__ */ diff --git a/src/core/compression/algorithm.c b/src/core/compression/algorithm.c new file mode 100644 index 0000000000..0b5576f70a --- /dev/null +++ b/src/core/compression/algorithm.c @@ -0,0 +1,49 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/compression/algorithm.h" + +const char *grpc_compression_algorithm_name( + grpc_compression_algorithm algorithm) { + switch (algorithm) { + case GRPC_COMPRESS_NONE: + return "none"; + case GRPC_COMPRESS_DEFLATE: + return "deflate"; + case GRPC_COMPRESS_GZIP: + return "gzip"; + case GRPC_COMPRESS_ALGORITHMS_COUNT: + return "error"; + } + return "error"; +} diff --git a/src/core/compression/algorithm.h b/src/core/compression/algorithm.h new file mode 100644 index 0000000000..05895a889e --- /dev/null +++ b/src/core/compression/algorithm.h @@ -0,0 +1,49 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_COMPRESSION_ALGORITHM_H__ +#define __GRPC_INTERNAL_COMPRESSION_ALGORITHM_H__ + +/* The various compression algorithms supported by GRPC */ +typedef enum { + GRPC_COMPRESS_NONE = 0, + GRPC_COMPRESS_DEFLATE, + GRPC_COMPRESS_GZIP, + /* TODO(ctiller): snappy */ + GRPC_COMPRESS_ALGORITHMS_COUNT +} grpc_compression_algorithm; + +const char *grpc_compression_algorithm_name( + grpc_compression_algorithm algorithm); + +#endif /* __GRPC_INTERNAL_COMPRESSION_ALGORITHM_H__ */ diff --git a/src/core/compression/message_compress.c b/src/core/compression/message_compress.c new file mode 100644 index 0000000000..1787ccd7d8 --- /dev/null +++ b/src/core/compression/message_compress.c @@ -0,0 +1,193 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/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; + size_t output_bytes = 0; + gpr_slice outbuf = gpr_slice_malloc(OUTPUT_BLOCK_SIZE); + + zs->avail_out = 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; + zs->avail_in = GPR_SLICE_LENGTH(input->slices[i]); + zs->next_in = GPR_SLICE_START_PTR(input->slices[i]); + do { + if (zs->avail_out == 0) { + output_bytes += GPR_SLICE_LENGTH(outbuf); + gpr_slice_buffer_add_indexed(output, outbuf); + outbuf = gpr_slice_malloc(OUTPUT_BLOCK_SIZE); + zs->avail_out = GPR_SLICE_LENGTH(outbuf); + zs->next_out = GPR_SLICE_START_PTR(outbuf); + } + r = flate(zs, flush); + if (r == Z_STREAM_ERROR) { + gpr_log(GPR_INFO, "zlib: stream error"); + 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; + output_bytes += GPR_SLICE_LENGTH(outbuf); + gpr_slice_buffer_add_indexed(output, outbuf); + + return 1; + +error: + gpr_slice_unref(outbuf); + return 0; +} + +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)); + r = deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 | (gzip ? 16 : 0), + 8, Z_DEFAULT_STRATEGY); + if (r != Z_OK) { + gpr_log(GPR_ERROR, "deflateInit2 returns %d", r); + return 0; + } + 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)); + r = inflateInit2(&zs, 15 | (gzip ? 16 : 0)); + if (r != Z_OK) { + gpr_log(GPR_ERROR, "inflateInit2 returns %d", r); + return 0; + } + 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; +} + +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/compression/message_compress.h b/src/core/compression/message_compress.h new file mode 100644 index 0000000000..af8a0a5d75 --- /dev/null +++ b/src/core/compression/message_compress.h @@ -0,0 +1,52 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_COMPRESSION_MESSAGE_COMPRESS_H__ +#define __GRPC_INTERNAL_COMPRESSION_MESSAGE_COMPRESS_H__ + +#include "src/core/compression/algorithm.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_INTERNAL_COMPRESSION_MESSAGE_COMPRESS_H__ */ diff --git a/src/core/endpoint/endpoint.c b/src/core/endpoint/endpoint.c new file mode 100644 index 0000000000..07353751b0 --- /dev/null +++ b/src/core/endpoint/endpoint.c @@ -0,0 +1,49 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/endpoint/endpoint.h" + +void grpc_endpoint_notify_on_read(grpc_endpoint *ep, grpc_endpoint_read_cb cb, + void *user_data, gpr_timespec deadline) { + ep->vtable->notify_on_read(ep, cb, user_data, deadline); +} + +grpc_endpoint_write_status grpc_endpoint_write( + grpc_endpoint *ep, gpr_slice *slices, size_t nslices, + grpc_endpoint_write_cb cb, void *user_data, gpr_timespec deadline) { + return ep->vtable->write(ep, slices, nslices, cb, user_data, deadline); +} + +void grpc_endpoint_shutdown(grpc_endpoint *ep) { ep->vtable->shutdown(ep); } + +void grpc_endpoint_destroy(grpc_endpoint *ep) { ep->vtable->destroy(ep); } diff --git a/src/core/endpoint/endpoint.h b/src/core/endpoint/endpoint.h new file mode 100644 index 0000000000..14d9a56d55 --- /dev/null +++ b/src/core/endpoint/endpoint.h @@ -0,0 +1,99 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_ENDPOINT_ENDPOINT_H__ +#define __GRPC_INTERNAL_ENDPOINT_ENDPOINT_H__ + +#include <grpc/support/slice.h> +#include <grpc/support/time.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; + +typedef enum grpc_endpoint_cb_status { + GRPC_ENDPOINT_CB_OK = 0, /* Call completed successfully */ + GRPC_ENDPOINT_CB_EOF, /* Call completed successfully, end of file reached */ + GRPC_ENDPOINT_CB_SHUTDOWN, /* Call interrupted by shutdown */ + GRPC_ENDPOINT_CB_ERROR, /* Call interrupted by socket error */ + GRPC_ENDPOINT_CB_TIMED_OUT /* Call timed out */ +} grpc_endpoint_cb_status; + +typedef enum grpc_endpoint_write_status { + GRPC_ENDPOINT_WRITE_DONE, /* completed immediately, cb won't be called */ + GRPC_ENDPOINT_WRITE_PENDING, /* cb will be called when completed */ + GRPC_ENDPOINT_WRITE_ERROR /* write errored out, cb won't be called */ +} grpc_endpoint_write_status; + +typedef void (*grpc_endpoint_read_cb)(void *user_data, gpr_slice *slices, + size_t nslices, + grpc_endpoint_cb_status error); +typedef void (*grpc_endpoint_write_cb)(void *user_data, + grpc_endpoint_cb_status error); + +struct grpc_endpoint_vtable { + void (*notify_on_read)(grpc_endpoint *ep, grpc_endpoint_read_cb cb, + void *user_data, gpr_timespec deadline); + grpc_endpoint_write_status (*write)(grpc_endpoint *ep, gpr_slice *slices, + size_t nslices, grpc_endpoint_write_cb cb, + void *user_data, gpr_timespec deadline); + void (*shutdown)(grpc_endpoint *ep); + void (*destroy)(grpc_endpoint *ep); +}; + +/* When data is available on the connection, calls the callback with slices. */ +void grpc_endpoint_notify_on_read(grpc_endpoint *ep, grpc_endpoint_read_cb cb, + void *user_data, gpr_timespec deadline); + +/* Write slices out to the socket. + + If the connection is ready for more data after the end of the call, it + returns GRPC_ENDPOINT_WRITE_DONE. + Otherwise it returns GRPC_ENDPOINT_WRITE_PENDING and calls cb when the + connection is ready for more data. */ +grpc_endpoint_write_status grpc_endpoint_write( + grpc_endpoint *ep, gpr_slice *slices, size_t nslices, + grpc_endpoint_write_cb cb, void *user_data, gpr_timespec deadline); + +/* Causes any pending read/write callbacks to run immediately with + GRPC_ENDPOINT_CB_SHUTDOWN status */ +void grpc_endpoint_shutdown(grpc_endpoint *ep); +void grpc_endpoint_destroy(grpc_endpoint *ep); + +struct grpc_endpoint { + const grpc_endpoint_vtable *vtable; +}; + +#endif /* __GRPC_INTERNAL_ENDPOINT_ENDPOINT_H__ */ diff --git a/src/core/endpoint/resolve_address.c b/src/core/endpoint/resolve_address.c new file mode 100644 index 0000000000..aa21954c6d --- /dev/null +++ b/src/core/endpoint/resolve_address.c @@ -0,0 +1,195 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#define _POSIX_SOURCE + +#include "src/core/endpoint/resolve_address.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <unistd.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/string.h> +#include <grpc/support/log.h> +#include <grpc/support/thd.h> + +typedef struct { + char *name; + char *default_port; + grpc_resolve_cb cb; + void *arg; +} request; + +static void 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; + } + if (rbracket[1] == '\0') { + /* ]<end> */ + port_start = NULL; + } else if (rbracket[1] == ':') { + /* ]:<port?> */ + port_start = rbracket + 2; + } else { + /* ]<invalid> */ + return; + } + host_start = name + 1; + host_len = 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; + } + } 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 = 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); + } +} + +grpc_resolved_addresses *grpc_blocking_resolve_address( + 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 */ + 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 */ + + s = getaddrinfo(host, port, &hints, &result); + 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; +} + +/* Thread function to asynch-ify grpc_blocking_resolve_address */ +static void do_request(void *rp) { + request *r = rp; + r->cb(r->arg, grpc_blocking_resolve_address(r->name, r->default_port)); + gpr_free(r->name); + gpr_free(r->default_port); + 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)); + gpr_thd_id id; + r->name = gpr_strdup(name); + r->default_port = gpr_strdup(default_port); + r->cb = cb; + r->arg = arg; + gpr_thd_new(&id, do_request, r, NULL); +} diff --git a/src/core/endpoint/resolve_address.h b/src/core/endpoint/resolve_address.h new file mode 100644 index 0000000000..cc32c47cef --- /dev/null +++ b/src/core/endpoint/resolve_address.h @@ -0,0 +1,67 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_ENDPOINT_RESOLVE_ADDRESS_H__ +#define __GRPC_INTERNAL_ENDPOINT_RESOLVE_ADDRESS_H__ + +#include <sys/socket.h> + +typedef struct { + struct sockaddr_storage addr; + int 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)(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. */ +grpc_resolved_addresses *grpc_blocking_resolve_address( + const char *addr, const char *default_port); + +#endif /* __GRPC_INTERNAL_ENDPOINT_RESOLVE_ADDRESS_H__ */ diff --git a/src/core/endpoint/secure_endpoint.c b/src/core/endpoint/secure_endpoint.c new file mode 100644 index 0000000000..4fab0faa03 --- /dev/null +++ b/src/core/endpoint/secure_endpoint.c @@ -0,0 +1,335 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/endpoint/secure_endpoint.h" +#include "src/core/tsi/transport_security_interface.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/string.h> +#include <grpc/support/sync.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_endpoint_read_cb read_cb; + void *read_user_data; + /* saved handshaker leftover data to unprotect. */ + gpr_slice_buffer leftover_bytes; + /* buffers for read and write */ + gpr_slice read_staging_buffer; + gpr_slice_buffer input_buffer; + + gpr_slice write_staging_buffer; + gpr_slice_buffer output_buffer; + + gpr_refcount ref; +} secure_endpoint; + +static void secure_endpoint_ref(secure_endpoint *ep) { gpr_ref(&ep->ref); } + +static void destroy(secure_endpoint *secure_ep) { + secure_endpoint *ep = (secure_endpoint *)secure_ep; + grpc_endpoint_destroy(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_buffer_destroy(&ep->input_buffer); + gpr_slice_unref(ep->write_staging_buffer); + gpr_slice_buffer_destroy(&ep->output_buffer); + gpr_mu_destroy(&ep->protector_mu); + gpr_free(ep); +} + +static void secure_endpoint_unref(secure_endpoint *ep) { + if (gpr_unref(&ep->ref)) { + destroy(ep); + } +} + +static void flush_read_staging_buffer(secure_endpoint *ep, gpr_uint8 **cur, + gpr_uint8 **end) { + gpr_slice_buffer_add(&ep->input_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(secure_endpoint *ep, gpr_slice *slices, size_t nslices, + grpc_endpoint_cb_status error) { +#ifdef GRPC_TRACE_SECURE_TRANSPORT + size_t i; + for (i = 0; i < nslices; i++) { + char *data = + gpr_hexdump((char*)GPR_SLICE_START_PTR(slices[i]), + GPR_SLICE_LENGTH(slices[i]), GPR_HEXDUMP_PLAINTEXT); + gpr_log(GPR_DEBUG, "READ %p: %s", ep, data); + gpr_free(data); + } +#endif + ep->read_cb(ep->read_user_data, slices, nslices, error); + secure_endpoint_unref(ep); +} + +static void on_read(void *user_data, gpr_slice *slices, size_t nslices, + grpc_endpoint_cb_status error) { + int i = 0; + gpr_uint8 keep_looping = 0; + int input_buffer_count = 0; + tsi_result result = TSI_OK; + secure_endpoint *ep = (secure_endpoint *)user_data; + gpr_uint8 *cur = GPR_SLICE_START_PTR(ep->read_staging_buffer); + gpr_uint8 *end = GPR_SLICE_END_PTR(ep->read_staging_buffer); + + /* TODO(yangg) check error, maybe bail out early */ + for (i = 0; i < nslices; i++) { + gpr_slice encrypted = slices[i]; + gpr_uint8 *message_bytes = GPR_SLICE_START_PTR(encrypted); + size_t message_size = GPR_SLICE_LENGTH(encrypted); + + while (message_size > 0 || keep_looping) { + gpr_uint32 unprotected_buffer_size_written = end - cur; + gpr_uint32 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->input_buffer, + gpr_slice_split_head( + &ep->read_staging_buffer, + 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 */ + for (i = 0; i < nslices; i++) { + gpr_slice_unref(slices[i]); + } + + if (result != TSI_OK) { + gpr_slice_buffer_reset_and_unref(&ep->input_buffer); + call_read_cb(ep, NULL, 0, GRPC_ENDPOINT_CB_ERROR); + return; + } + /* The upper level will unref the slices. */ + input_buffer_count = ep->input_buffer.count; + ep->input_buffer.count = 0; + call_read_cb(ep, ep->input_buffer.slices, input_buffer_count, error); +} + +static void notify_on_read(grpc_endpoint *secure_ep, grpc_endpoint_read_cb cb, + void *user_data, gpr_timespec deadline) { + secure_endpoint *ep = (secure_endpoint *)secure_ep; + ep->read_cb = cb; + ep->read_user_data = user_data; + + secure_endpoint_ref(ep); + + if (ep->leftover_bytes.count) { + size_t leftover_nslices = ep->leftover_bytes.count; + ep->leftover_bytes.count = 0; + on_read(ep, ep->leftover_bytes.slices, leftover_nslices, + GRPC_ENDPOINT_CB_OK); + return; + } + + grpc_endpoint_notify_on_read(ep->wrapped_ep, on_read, ep, deadline); +} + +static void flush_write_staging_buffer(secure_endpoint *ep, gpr_uint8 **cur, + gpr_uint8 **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 grpc_endpoint_write_status write(grpc_endpoint *secure_ep, + gpr_slice *slices, size_t nslices, + grpc_endpoint_write_cb cb, + void *user_data, + gpr_timespec deadline) { + int i = 0; + int output_buffer_count = 0; + tsi_result result = TSI_OK; + secure_endpoint *ep = (secure_endpoint *)secure_ep; + gpr_uint8 *cur = GPR_SLICE_START_PTR(ep->write_staging_buffer); + gpr_uint8 *end = GPR_SLICE_END_PTR(ep->write_staging_buffer); + GPR_ASSERT(ep->output_buffer.count == 0); + +#ifdef GRPC_TRACE_SECURE_TRANSPORT + for (i = 0; i < nslices; i++) { + char *data = + gpr_hexdump((char*)GPR_SLICE_START_PTR(slices[i]), + GPR_SLICE_LENGTH(slices[i]), GPR_HEXDUMP_PLAINTEXT); + gpr_log(GPR_DEBUG, "WRITE %p: %s", ep, data); + gpr_free(data); + } +#endif + + for (i = 0; i < nslices; i++) { + gpr_slice plain = slices[i]; + gpr_uint8 *message_bytes = GPR_SLICE_START_PTR(plain); + size_t message_size = GPR_SLICE_LENGTH(plain); + while (message_size > 0) { + gpr_uint32 protected_buffer_size_to_send = end - cur; + gpr_uint32 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) { + gpr_uint32 still_pending_size; + do { + gpr_uint32 protected_buffer_size_to_send = 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, + cur - GPR_SLICE_START_PTR(ep->write_staging_buffer))); + } + } + + for (i = 0; i < nslices; i++) { + gpr_slice_unref(slices[i]); + } + + if (result != TSI_OK) { + /* TODO(yangg) do different things according to the error type? */ + gpr_slice_buffer_reset_and_unref(&ep->output_buffer); + return GRPC_ENDPOINT_WRITE_ERROR; + } + + /* clear output_buffer and let the lower level handle its slices. */ + output_buffer_count = ep->output_buffer.count; + ep->output_buffer.count = 0; + return grpc_endpoint_write(ep->wrapped_ep, ep->output_buffer.slices, + output_buffer_count, cb, user_data, deadline); +} + +static void shutdown(grpc_endpoint *secure_ep) { + secure_endpoint *ep = (secure_endpoint *)secure_ep; + grpc_endpoint_shutdown(ep->wrapped_ep); +} + +static void unref(grpc_endpoint *secure_ep) { + secure_endpoint *ep = (secure_endpoint *)secure_ep; + secure_endpoint_unref(ep); +} + +static const grpc_endpoint_vtable vtable = {notify_on_read, write, shutdown, + unref}; + +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->input_buffer); + gpr_slice_buffer_init(&ep->output_buffer); + gpr_mu_init(&ep->protector_mu); + gpr_ref_init(&ep->ref, 1); + return &ep->base; +} diff --git a/src/core/endpoint/secure_endpoint.h b/src/core/endpoint/secure_endpoint.h new file mode 100644 index 0000000000..971170afed --- /dev/null +++ b/src/core/endpoint/secure_endpoint.h @@ -0,0 +1,47 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_ENDPOINT_SECURE_ENDPOINT_H__ +#define __GRPC_INTERNAL_ENDPOINT_SECURE_ENDPOINT_H__ + +#include <grpc/support/slice.h> +#include "src/core/endpoint/endpoint.h" + +struct tsi_frame_protector; + +/* 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_INTERNAL_ENDPOINT_SECURE_ENDPOINT_H__ */ diff --git a/src/core/endpoint/socket_utils.c b/src/core/endpoint/socket_utils.c new file mode 100644 index 0000000000..9c2540bcbf --- /dev/null +++ b/src/core/endpoint/socket_utils.c @@ -0,0 +1,105 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/endpoint/socket_utils.h" + +#include <limits.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> +#include <string.h> +#include <errno.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; +} + +/* 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 == 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 == val; +} diff --git a/src/core/endpoint/socket_utils.h b/src/core/endpoint/socket_utils.h new file mode 100644 index 0000000000..545d678eab --- /dev/null +++ b/src/core/endpoint/socket_utils.h @@ -0,0 +1,58 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_ENDPOINT_SOCKET_UTILS_H__ +#define __GRPC_INTERNAL_ENDPOINT_SOCKET_UTILS_H__ + +#include <unistd.h> +#include <sys/socket.h> + +struct sockaddr; + +/* 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); + +#endif /* __GRPC_INTERNAL_ENDPOINT_SOCKET_UTILS_H__ */ diff --git a/src/core/endpoint/socket_utils_linux.c b/src/core/endpoint/socket_utils_linux.c new file mode 100644 index 0000000000..479675ec7d --- /dev/null +++ b/src/core/endpoint/socket_utils_linux.c @@ -0,0 +1,52 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#define _GNU_SOURCE +#include <grpc/support/port_platform.h> + +#ifdef GPR_LINUX + +#include "src/core/endpoint/socket_utils.h" + +#include <sys/types.h> +#include <sys/socket.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/endpoint/socket_utils_posix.c b/src/core/endpoint/socket_utils_posix.c new file mode 100644 index 0000000000..262d606af9 --- /dev/null +++ b/src/core/endpoint/socket_utils_posix.c @@ -0,0 +1,61 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 + +#define _BSD_SOURCE +#include "src/core/endpoint/socket_utils.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) { + flags = fcntl(fd, F_GETFL, 0); + flags |= nonblock ? O_NONBLOCK : 0; + flags |= cloexec ? FD_CLOEXEC : 0; + GPR_ASSERT(fcntl(fd, F_SETFL, flags) == 0); + } + return fd; +} + +#endif /* GPR_POSIX_SOCKETUTILS */ diff --git a/src/core/endpoint/tcp.c b/src/core/endpoint/tcp.c new file mode 100644 index 0000000000..39367e80f6 --- /dev/null +++ b/src/core/endpoint/tcp.c @@ -0,0 +1,570 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/endpoint/tcp.h" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> + +#include "src/core/eventmanager/em.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/slice.h> +#include <grpc/support/string.h> +#include <grpc/support/sync.h> +#include <grpc/support/time.h> + +/* Holds a slice array and associated state. */ +typedef struct grpc_tcp_slice_state { + gpr_slice *slices; /* Array of slices */ + size_t nslices; /* Size of slices array. */ + ssize_t first_slice; /* First valid slice in array */ + ssize_t last_slice; /* Last valid slice in array */ + gpr_slice working_slice; /* pointer to original final slice */ + int working_slice_valid; /* True if there is a working slice */ + int memory_owned; /* True if slices array is owned */ +} grpc_tcp_slice_state; + +static void slice_state_init(grpc_tcp_slice_state *state, gpr_slice *slices, + size_t nslices, size_t valid_slices) { + state->slices = slices; + state->nslices = nslices; + if (valid_slices == 0) { + state->first_slice = -1; + } else { + state->first_slice = 0; + } + state->last_slice = valid_slices - 1; + state->working_slice_valid = 0; + state->memory_owned = 0; +} + +/* Returns true if there is still available data */ +static int slice_state_has_available(grpc_tcp_slice_state *state) { + return state->first_slice != -1 && state->last_slice >= state->first_slice; +} + +static ssize_t slice_state_slices_allocated(grpc_tcp_slice_state *state) { + if (state->first_slice == -1) { + return 0; + } else { + return state->last_slice - state->first_slice + 1; + } +} + +static void slice_state_realloc(grpc_tcp_slice_state *state, size_t new_size) { + /* TODO(klempner): use realloc instead when first_slice is 0 */ + /* TODO(klempner): Avoid a realloc in cases where it is unnecessary */ + gpr_slice *slices = state->slices; + size_t original_size = slice_state_slices_allocated(state); + size_t i; + gpr_slice *new_slices = gpr_malloc(sizeof(gpr_slice) * new_size); + + for (i = 0; i < original_size; ++i) { + new_slices[i] = slices[i + state->first_slice]; + } + + state->slices = new_slices; + state->last_slice = original_size - 1; + if (original_size > 0) { + state->first_slice = 0; + } else { + state->first_slice = -1; + } + state->nslices = new_size; + + if (state->memory_owned) { + gpr_free(slices); + } + state->memory_owned = 1; +} + +static void slice_state_remove_prefix(grpc_tcp_slice_state *state, + size_t prefix_bytes) { + gpr_slice *current_slice = &state->slices[state->first_slice]; + size_t current_slice_size; + + while (slice_state_has_available(state)) { + current_slice_size = GPR_SLICE_LENGTH(*current_slice); + if (current_slice_size > prefix_bytes) { + /* TODO(klempner): Get rid of the extra refcount created here by adding a + native "trim the first N bytes" operation to splice */ + /* TODO(klempner): This really shouldn't be modifying the current slice + unless we own the slices array. */ + *current_slice = gpr_slice_split_tail(current_slice, prefix_bytes); + gpr_slice_unref(*current_slice); + return; + } else { + gpr_slice_unref(*current_slice); + ++state->first_slice; + ++current_slice; + prefix_bytes -= current_slice_size; + } + } +} + +static void slice_state_destroy(grpc_tcp_slice_state *state) { + while (slice_state_has_available(state)) { + gpr_slice_unref(state->slices[state->first_slice]); + ++state->first_slice; + } + + if (state->memory_owned) { + gpr_free(state->slices); + state->memory_owned = 0; + } +} + +void slice_state_transfer_ownership(grpc_tcp_slice_state *state, + gpr_slice **slices, size_t *nslices) { + *slices = state->slices + state->first_slice; + *nslices = state->last_slice - state->first_slice + 1; + + state->first_slice = -1; + state->last_slice = -1; +} + +/* Fills iov with the first min(iov_size, available) slices, returns number + filled */ +static size_t slice_state_to_iovec(grpc_tcp_slice_state *state, + struct iovec *iov, size_t iov_size) { + size_t nslices = state->last_slice - state->first_slice + 1; + gpr_slice *slices = state->slices + state->first_slice; + size_t i; + if (nslices < iov_size) { + iov_size = nslices; + } + + for (i = 0; i < iov_size; ++i) { + iov[i].iov_base = GPR_SLICE_START_PTR(slices[i]); + iov[i].iov_len = GPR_SLICE_LENGTH(slices[i]); + } + return iov_size; +} + +/* Makes n blocks available at the end of state, writes them into iov, and + returns the number of bytes allocated */ +static size_t slice_state_append_blocks_into_iovec(grpc_tcp_slice_state *state, + struct iovec *iov, size_t n, + size_t slice_size) { + size_t target_size; + size_t i; + size_t allocated_bytes; + ssize_t allocated_slices = slice_state_slices_allocated(state); + + if (n - state->working_slice_valid >= state->nslices - state->last_slice) { + /* Need to grow the slice array */ + target_size = state->nslices; + do { + target_size = target_size * 2; + } while (target_size < allocated_slices + n - state->working_slice_valid); + /* TODO(klempner): If this ever needs to support both prefix removal and + append, we should be smarter about the growth logic here */ + slice_state_realloc(state, target_size); + } + + i = 0; + allocated_bytes = 0; + + if (state->working_slice_valid) { + iov[0].iov_base = GPR_SLICE_END_PTR(state->slices[state->last_slice]); + iov[0].iov_len = GPR_SLICE_LENGTH(state->working_slice) - + GPR_SLICE_LENGTH(state->slices[state->last_slice]); + allocated_bytes += iov[0].iov_len; + ++i; + state->slices[state->last_slice] = state->working_slice; + state->working_slice_valid = 0; + } + + for (; i < n; ++i) { + ++state->last_slice; + state->slices[state->last_slice] = gpr_slice_malloc(slice_size); + iov[i].iov_base = GPR_SLICE_START_PTR(state->slices[state->last_slice]); + iov[i].iov_len = slice_size; + allocated_bytes += slice_size; + } + if (state->first_slice == -1) { + state->first_slice = 0; + } + return allocated_bytes; +} + +/* Remove the last n bytes from state */ +/* TODO(klempner): Consider having this defer actual deletion until later */ +static void slice_state_remove_last(grpc_tcp_slice_state *state, size_t bytes) { + while (bytes > 0 && slice_state_has_available(state)) { + if (GPR_SLICE_LENGTH(state->slices[state->last_slice]) > bytes) { + state->working_slice = state->slices[state->last_slice]; + state->working_slice_valid = 1; + /* TODO(klempner): Combine these into a single operation that doesn't need + to refcount */ + gpr_slice_unref(gpr_slice_split_tail( + &state->slices[state->last_slice], + GPR_SLICE_LENGTH(state->slices[state->last_slice]) - bytes)); + bytes = 0; + } else { + bytes -= GPR_SLICE_LENGTH(state->slices[state->last_slice]); + gpr_slice_unref(state->slices[state->last_slice]); + --state->last_slice; + if (state->last_slice == -1) { + state->first_slice = -1; + } + } + } +} + +typedef struct { + grpc_endpoint base; + grpc_em *em; + grpc_em_fd em_fd; + int fd; + size_t slice_size; + gpr_refcount refcount; + + grpc_endpoint_read_cb read_cb; + void *read_user_data; + gpr_timespec read_deadline; + grpc_endpoint_write_cb write_cb; + void *write_user_data; + gpr_timespec write_deadline; + + grpc_tcp_slice_state write_state; +} grpc_tcp; + +static void grpc_tcp_handle_read(void *arg /* grpc_tcp */, + grpc_em_cb_status status); +static void grpc_tcp_handle_write(void *arg /* grpc_tcp */, + grpc_em_cb_status status); + +#define DEFAULT_SLICE_SIZE 8192 +grpc_endpoint *grpc_tcp_create(int fd, grpc_em *em) { + return grpc_tcp_create_dbg(fd, em, DEFAULT_SLICE_SIZE); +} + +static void grpc_tcp_shutdown(grpc_endpoint *ep) { + grpc_tcp *tcp = (grpc_tcp *)ep; + grpc_em_fd_shutdown(&tcp->em_fd); +} + +static void grpc_tcp_unref(grpc_tcp *tcp) { + int refcount_zero = gpr_unref(&tcp->refcount); + if (refcount_zero) { + grpc_em_fd_destroy(&tcp->em_fd); + close(tcp->fd); + gpr_free(tcp); + } +} + +static void grpc_tcp_destroy(grpc_endpoint *ep) { + grpc_tcp *tcp = (grpc_tcp *)ep; + grpc_tcp_unref(tcp); +} + +static void call_read_cb(grpc_tcp *tcp, gpr_slice *slices, size_t nslices, + grpc_endpoint_cb_status status) { + grpc_endpoint_read_cb cb = tcp->read_cb; + +#ifdef GRPC_TRACE_TCP + size_t i; + gpr_log(GPR_DEBUG, "read: status=%d", status); + for (i = 0; i < nslices; i++) { + char *dump = + gpr_hexdump((char *)GPR_SLICE_START_PTR(slices[i]), + GPR_SLICE_LENGTH(slices[i]), GPR_HEXDUMP_PLAINTEXT); + gpr_log(GPR_DEBUG, "READ: %s", dump); + gpr_free(dump); + } +#endif + + tcp->read_cb = NULL; + cb(tcp->read_user_data, slices, nslices, status); +} + +#define INLINE_SLICE_BUFFER_SIZE 8 +#define MAX_READ_IOVEC 4 +static void grpc_tcp_handle_read(void *arg /* grpc_tcp */, + grpc_em_cb_status status) { + grpc_tcp *tcp = (grpc_tcp *)arg; + int iov_size = 1; + gpr_slice static_read_slices[INLINE_SLICE_BUFFER_SIZE]; + struct msghdr msg; + struct iovec iov[MAX_READ_IOVEC]; + ssize_t read_bytes; + ssize_t allocated_bytes; + struct grpc_tcp_slice_state read_state; + gpr_slice *final_slices; + size_t final_nslices; + + slice_state_init(&read_state, static_read_slices, INLINE_SLICE_BUFFER_SIZE, + 0); + + if (status == GRPC_CALLBACK_CANCELLED) { + call_read_cb(tcp, NULL, 0, GRPC_ENDPOINT_CB_SHUTDOWN); + grpc_tcp_unref(tcp); + return; + } + + if (status == GRPC_CALLBACK_TIMED_OUT) { + call_read_cb(tcp, NULL, 0, GRPC_ENDPOINT_CB_TIMED_OUT); + grpc_tcp_unref(tcp); + return; + } + + /* TODO(klempner): Limit the amount we read at once. */ + for (;;) { + allocated_bytes = slice_state_append_blocks_into_iovec( + &read_state, iov, iov_size, tcp->slice_size); + + 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; + + do { + read_bytes = recvmsg(tcp->fd, &msg, 0); + } while (read_bytes < 0 && errno == EINTR); + + if (read_bytes < allocated_bytes) { + /* TODO(klempner): Consider a second read first, in hopes of getting a + * quick EAGAIN and saving a bunch of allocations. */ + slice_state_remove_last(&read_state, read_bytes < 0 + ? allocated_bytes + : allocated_bytes - read_bytes); + } + + if (read_bytes < 0) { + /* NB: After calling the user_cb a parallel call of the read handler may + * be running. */ + if (errno == EAGAIN) { + if (slice_state_has_available(&read_state)) { + /* TODO(klempner): We should probably do the call into the application + without all this junk on the stack */ + /* FIXME(klempner): Refcount properly */ + slice_state_transfer_ownership(&read_state, &final_slices, + &final_nslices); + call_read_cb(tcp, final_slices, final_nslices, GRPC_ENDPOINT_CB_OK); + slice_state_destroy(&read_state); + grpc_tcp_unref(tcp); + } else { + /* Spurious read event, consume it here */ + slice_state_destroy(&read_state); + grpc_em_fd_notify_on_read(&tcp->em_fd, grpc_tcp_handle_read, tcp, + tcp->read_deadline); + } + } else { + /* TODO(klempner): Log interesting errors */ + call_read_cb(tcp, NULL, 0, GRPC_ENDPOINT_CB_ERROR); + slice_state_destroy(&read_state); + grpc_tcp_unref(tcp); + } + return; + } else if (read_bytes == 0) { + /* 0 read size ==> end of stream */ + if (slice_state_has_available(&read_state)) { + /* there were bytes already read: pass them up to the application */ + slice_state_transfer_ownership(&read_state, &final_slices, + &final_nslices); + call_read_cb(tcp, final_slices, final_nslices, GRPC_ENDPOINT_CB_EOF); + } else { + call_read_cb(tcp, NULL, 0, GRPC_ENDPOINT_CB_EOF); + } + slice_state_destroy(&read_state); + grpc_tcp_unref(tcp); + return; + } else if (iov_size < MAX_READ_IOVEC) { + ++iov_size; + } + } +} + +static void grpc_tcp_notify_on_read(grpc_endpoint *ep, grpc_endpoint_read_cb cb, + void *user_data, gpr_timespec deadline) { + grpc_tcp *tcp = (grpc_tcp *)ep; + GPR_ASSERT(tcp->read_cb == NULL); + tcp->read_cb = cb; + tcp->read_user_data = user_data; + tcp->read_deadline = deadline; + gpr_ref(&tcp->refcount); + grpc_em_fd_notify_on_read(&tcp->em_fd, grpc_tcp_handle_read, tcp, deadline); +} + +#define MAX_WRITE_IOVEC 16 +static grpc_endpoint_write_status grpc_tcp_flush(grpc_tcp *tcp) { + struct msghdr msg; + struct iovec iov[MAX_WRITE_IOVEC]; + int iov_size; + ssize_t sent_length; + grpc_tcp_slice_state *state = &tcp->write_state; + + for (;;) { + iov_size = slice_state_to_iovec(state, iov, MAX_WRITE_IOVEC); + + 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; + + do { + /* TODO(klempner): Cork if this is a partial write */ + sent_length = sendmsg(tcp->fd, &msg, 0); + } while (sent_length < 0 && errno == EINTR); + + if (sent_length < 0) { + if (errno == EAGAIN) { + return GRPC_ENDPOINT_WRITE_PENDING; + } else { + /* TODO(klempner): Log some of these */ + slice_state_destroy(state); + return GRPC_ENDPOINT_WRITE_ERROR; + } + } + + /* TODO(klempner): Probably better to batch this after we finish flushing */ + slice_state_remove_prefix(state, sent_length); + + if (!slice_state_has_available(state)) { + return GRPC_ENDPOINT_WRITE_DONE; + } + }; +} + +static void grpc_tcp_handle_write(void *arg /* grpc_tcp */, + grpc_em_cb_status status) { + grpc_tcp *tcp = (grpc_tcp *)arg; + grpc_endpoint_write_status write_status; + grpc_endpoint_cb_status cb_status; + grpc_endpoint_write_cb cb; + + cb_status = GRPC_ENDPOINT_CB_OK; + + if (status == GRPC_CALLBACK_CANCELLED) { + cb_status = GRPC_ENDPOINT_CB_SHUTDOWN; + } else if (status == GRPC_CALLBACK_TIMED_OUT) { + cb_status = GRPC_ENDPOINT_CB_TIMED_OUT; + } + + if (cb_status != GRPC_ENDPOINT_CB_OK) { + slice_state_destroy(&tcp->write_state); + cb = tcp->write_cb; + tcp->write_cb = NULL; + cb(tcp->write_user_data, cb_status); + grpc_tcp_unref(tcp); + return; + } + + write_status = grpc_tcp_flush(tcp); + if (write_status == GRPC_ENDPOINT_WRITE_PENDING) { + grpc_em_fd_notify_on_write(&tcp->em_fd, grpc_tcp_handle_write, tcp, + tcp->write_deadline); + } else { + slice_state_destroy(&tcp->write_state); + if (write_status == GRPC_ENDPOINT_WRITE_DONE) { + cb_status = GRPC_ENDPOINT_CB_OK; + } else { + cb_status = GRPC_ENDPOINT_CB_ERROR; + } + cb = tcp->write_cb; + tcp->write_cb = NULL; + cb(tcp->write_user_data, cb_status); + grpc_tcp_unref(tcp); + } +} + +static grpc_endpoint_write_status grpc_tcp_write( + grpc_endpoint *ep, gpr_slice *slices, size_t nslices, + grpc_endpoint_write_cb cb, void *user_data, gpr_timespec deadline) { + grpc_tcp *tcp = (grpc_tcp *)ep; + grpc_endpoint_write_status status; + +#ifdef GRPC_TRACE_TCP + size_t i; + + for (i = 0; i < nslices; i++) { + char *data = + gpr_hexdump((char *)GPR_SLICE_START_PTR(slices[i]), + GPR_SLICE_LENGTH(slices[i]), GPR_HEXDUMP_PLAINTEXT); + gpr_log(GPR_DEBUG, "WRITE %p: %s", tcp, data); + gpr_free(data); + } +#endif + + GPR_ASSERT(tcp->write_cb == NULL); + slice_state_init(&tcp->write_state, slices, nslices, nslices); + + status = grpc_tcp_flush(tcp); + if (status == GRPC_ENDPOINT_WRITE_PENDING) { + /* TODO(klempner): Consider inlining rather than malloc for small nslices */ + slice_state_realloc(&tcp->write_state, nslices); + gpr_ref(&tcp->refcount); + tcp->write_cb = cb; + tcp->write_user_data = user_data; + tcp->write_deadline = deadline; + grpc_em_fd_notify_on_write(&tcp->em_fd, grpc_tcp_handle_write, tcp, + tcp->write_deadline); + } + + return status; +} + +static const grpc_endpoint_vtable vtable = {grpc_tcp_notify_on_read, + grpc_tcp_write, grpc_tcp_shutdown, + grpc_tcp_destroy}; + +grpc_endpoint *grpc_tcp_create_dbg(int fd, grpc_em *em, size_t slice_size) { + grpc_tcp *tcp = (grpc_tcp *)gpr_malloc(sizeof(grpc_tcp)); + tcp->base.vtable = &vtable; + tcp->fd = fd; + tcp->em = em; + tcp->read_cb = NULL; + tcp->write_cb = NULL; + tcp->read_user_data = NULL; + tcp->write_user_data = NULL; + tcp->slice_size = slice_size; + tcp->read_deadline = gpr_inf_future; + tcp->write_deadline = gpr_inf_future; + slice_state_init(&tcp->write_state, NULL, 0, 0); + /* paired with unref in grpc_tcp_destroy */ + gpr_ref_init(&tcp->refcount, 1); + grpc_em_fd_init(&tcp->em_fd, tcp->em, fd); + return &tcp->base; +} diff --git a/src/core/endpoint/tcp.h b/src/core/endpoint/tcp.h new file mode 100644 index 0000000000..6507b2f6ef --- /dev/null +++ b/src/core/endpoint/tcp.h @@ -0,0 +1,55 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_ENDPOINT_TCP_H__ +#define __GRPC_INTERNAL_ENDPOINT_TCP_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/endpoint/endpoint.h" +#include "src/core/eventmanager/em.h" + +/* Create a tcp from an already connected file descriptor. */ +grpc_endpoint *grpc_tcp_create(int fd, grpc_em *em); +/* Special version for debugging slice changes */ +grpc_endpoint *grpc_tcp_create_dbg(int fd, grpc_em *em, size_t slice_size); + +#endif /* __GRPC_INTERNAL_ENDPOINT_TCP_H__ */ diff --git a/src/core/endpoint/tcp_client.c b/src/core/endpoint/tcp_client.c new file mode 100644 index 0000000000..01a0c3f23d --- /dev/null +++ b/src/core/endpoint/tcp_client.c @@ -0,0 +1,170 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/endpoint/tcp_client.h" + +#include <errno.h> +#include <string.h> +#include <unistd.h> + +#include "src/core/endpoint/socket_utils.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/time.h> + +typedef struct { + void (*cb)(void *arg, grpc_endpoint *tcp); + void *cb_arg; + grpc_em_fd fd; + gpr_timespec deadline; +} async_connect; + +static int create_fd(int address_family) { + int fd = socket(address_family, SOCK_STREAM, 0); + if (fd < 0) { + gpr_log(GPR_ERROR, "Unable to create socket: %s", strerror(errno)); + goto error; + } + + if (!grpc_set_socket_nonblocking(fd, 1) || !grpc_set_socket_cloexec(fd, 1) || + !grpc_set_socket_low_latency(fd, 1)) { + gpr_log(GPR_ERROR, "Unable to configure socket %d: %s", fd, + strerror(errno)); + goto error; + } + + return fd; + +error: + if (fd >= 0) { + close(fd); + } + return -1; +} + +static void on_writable(void *acp, grpc_em_cb_status status) { + async_connect *ac = acp; + int so_error = 0; + socklen_t so_error_size; + int err; + int fd = grpc_em_fd_get(&ac->fd); + grpc_em *em = grpc_em_fd_get_em(&ac->fd); + + if (status == GRPC_CALLBACK_SUCCESS) { + do { + so_error_size = sizeof(so_error); + err = getsockopt(fd, SOL_SOCKET, SO_ERROR, &so_error, &so_error_size); + } while (err < 0 && errno == EINTR); + if (err < 0) { + gpr_log(GPR_ERROR, "getsockopt(ERROR): %s", strerror(errno)); + goto error; + } 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"); + grpc_em_fd_notify_on_write(&ac->fd, on_writable, ac, ac->deadline); + return; + } else { + goto error; + } + } else { + goto great_success; + } + } else { + gpr_log(GPR_ERROR, "on_writable failed during connect: status=%d", status); + goto error; + } + + abort(); + +error: + ac->cb(ac->cb_arg, NULL); + grpc_em_fd_destroy(&ac->fd); + gpr_free(ac); + close(fd); + return; + +great_success: + grpc_em_fd_destroy(&ac->fd); + ac->cb(ac->cb_arg, grpc_tcp_create(fd, em)); + gpr_free(ac); +} + +void grpc_tcp_client_connect(void (*cb)(void *arg, grpc_endpoint *ep), + void *arg, grpc_em *em, struct sockaddr *addr, + int len, gpr_timespec deadline) { + int fd = create_fd(addr->sa_family); + int err; + async_connect *ac; + + if (fd < 0) { + cb(arg, NULL); + return; + } + + do { + err = connect(fd, addr, len); + } while (err < 0 && errno == EINTR); + + if (err >= 0) { + cb(arg, grpc_tcp_create(fd, em)); + return; + } + + if (errno != EWOULDBLOCK && errno != EINPROGRESS) { + gpr_log(GPR_ERROR, "connect error: %s", strerror(errno)); + close(fd); + cb(arg, NULL); + return; + } + + ac = gpr_malloc(sizeof(async_connect)); + ac->cb = cb; + ac->cb_arg = arg; + ac->deadline = deadline; + grpc_em_fd_init(&ac->fd, em, fd); + grpc_em_fd_notify_on_write(&ac->fd, on_writable, ac, deadline); +} diff --git a/src/core/endpoint/tcp_client.h b/src/core/endpoint/tcp_client.h new file mode 100644 index 0000000000..2a8b8ee217 --- /dev/null +++ b/src/core/endpoint/tcp_client.h @@ -0,0 +1,50 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_ENDPOINT_TCP_CLIENT_H__ +#define __GRPC_INTERNAL_ENDPOINT_TCP_CLIENT_H__ + +#include "src/core/endpoint/tcp.h" +#include <grpc/support/time.h> + +#include <sys/types.h> +#include <sys/socket.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) */ +void grpc_tcp_client_connect(void (*cb)(void *arg, grpc_endpoint *tcp), + void *arg, grpc_em *em, struct sockaddr *addr, + int len, gpr_timespec deadline); + +#endif /* __GRPC_INTERNAL_ENDPOINT_TCP_CLIENT_H__ */ diff --git a/src/core/endpoint/tcp_server.c b/src/core/endpoint/tcp_server.c new file mode 100644 index 0000000000..2f386ce045 --- /dev/null +++ b/src/core/endpoint/tcp_server.c @@ -0,0 +1,282 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#define _GNU_SOURCE +#include "src/core/endpoint/tcp_server.h" + +#include <limits.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include "src/core/endpoint/socket_utils.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/sync.h> +#include <grpc/support/time.h> + +#define INIT_PORT_CAP 2 +#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 { + int fd; + grpc_em_fd *emfd; + grpc_tcp_server *server; +} server_port; + +/* the overall server */ +struct grpc_tcp_server { + grpc_em *em; + grpc_tcp_server_cb cb; + void *cb_arg; + + gpr_mu mu; + gpr_cv cv; + + /* active port count: how many ports are actually still listening */ + int active_ports; + + /* all listening ports */ + server_port *ports; + size_t nports; + size_t port_capacity; +}; + +grpc_tcp_server *grpc_tcp_server_create(grpc_em *em) { + grpc_tcp_server *s = gpr_malloc(sizeof(grpc_tcp_server)); + gpr_mu_init(&s->mu); + gpr_cv_init(&s->cv); + s->active_ports = 0; + s->em = em; + s->cb = NULL; + s->cb_arg = NULL; + s->ports = gpr_malloc(sizeof(server_port) * INIT_PORT_CAP); + s->nports = 0; + s->port_capacity = INIT_PORT_CAP; + return s; +} + +void grpc_tcp_server_destroy(grpc_tcp_server *s) { + size_t i; + gpr_mu_lock(&s->mu); + /* shutdown all fd's */ + for (i = 0; i < s->nports; i++) { + grpc_em_fd_shutdown(s->ports[i].emfd); + } + /* wait while that happens */ + while (s->active_ports) { + gpr_cv_wait(&s->cv, &s->mu, gpr_inf_future); + } + gpr_mu_unlock(&s->mu); + + /* delete ALL the things */ + for (i = 0; i < s->nports; i++) { + server_port *sp = &s->ports[i]; + grpc_em_fd_destroy(sp->emfd); + gpr_free(sp->emfd); + close(sp->fd); + } + gpr_free(s->ports); + gpr_free(s); +} + +/* get max listen queue size on linux */ +static void init_max_accept_queue_size() { + 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 = 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() { + gpr_once_init(&s_init_max_accept_queue_size, init_max_accept_queue_size); + return s_max_accept_queue_size; +} + +/* create a socket to listen with */ +static int create_listening_socket(struct sockaddr *port, int len) { + int fd = socket(port->sa_family, SOCK_STREAM, 0); + if (fd < 0) { + gpr_log(GPR_ERROR, "Unable to create socket: %s", strerror(errno)); + goto error; + } + + if (!grpc_set_socket_nonblocking(fd, 1) || !grpc_set_socket_cloexec(fd, 1) || + !grpc_set_socket_low_latency(fd, 1) || + !grpc_set_socket_reuse_addr(fd, 1)) { + gpr_log(GPR_ERROR, "Unable to configure socket %d: %s", fd, + strerror(errno)); + goto error; + } + + if (bind(fd, port, len) < 0) { + gpr_log(GPR_ERROR, "bind: %s", strerror(errno)); + goto error; + } + + if (listen(fd, get_max_accept_queue_size()) < 0) { + gpr_log(GPR_ERROR, "listen: %s", strerror(errno)); + goto error; + } + + return fd; + +error: + if (fd >= 0) { + close(fd); + } + return -1; +} + +/* event manager callback when reads are ready */ +static void on_read(void *arg, grpc_em_cb_status status) { + server_port *sp = arg; + + if (status != GRPC_CALLBACK_SUCCESS) { + goto error; + } + + /* loop until accept4 returns EAGAIN, and then re-arm notification */ + for (;;) { + struct sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + int fd = grpc_accept4(sp->fd, (struct sockaddr *)&addr, &addrlen, 1, 1); + if (fd < 0) { + switch (errno) { + case EINTR: + continue; + case EAGAIN: + if (GRPC_EM_OK != grpc_em_fd_notify_on_read(sp->emfd, on_read, sp, + gpr_inf_future)) { + gpr_log(GPR_ERROR, "Failed to register read request with em"); + goto error; + } + return; + default: + gpr_log(GPR_ERROR, "Failed accept4: %s", strerror(errno)); + goto error; + } + } + + sp->server->cb(sp->server->cb_arg, grpc_tcp_create(fd, sp->server->em)); + } + + abort(); + +error: + gpr_mu_lock(&sp->server->mu); + if (0 == --sp->server->active_ports) { + gpr_cv_broadcast(&sp->server->cv); + } + gpr_mu_unlock(&sp->server->mu); +} + +int grpc_tcp_server_add_port(grpc_tcp_server *s, struct sockaddr *port, + int len) { + server_port *sp; + /* create a socket */ + int fd = create_listening_socket(port, len); + if (fd < 0) { + return -1; + } + + gpr_mu_lock(&s->mu); + GPR_ASSERT(!s->cb && "must add ports before starting server"); + /* 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->emfd = gpr_malloc(sizeof(grpc_em_fd)); + sp->fd = fd; + sp->server = s; + /* initialize the em desc */ + if (GRPC_EM_OK != grpc_em_fd_init(sp->emfd, s->em, fd)) { + grpc_em_fd_destroy(sp->emfd); + gpr_free(sp->emfd); + s->nports--; + gpr_mu_unlock(&s->mu); + return -1; + } + gpr_mu_unlock(&s->mu); + + return fd; +} + +void grpc_tcp_server_start(grpc_tcp_server *s, grpc_tcp_server_cb cb, + void *cb_arg) { + size_t i; + GPR_ASSERT(cb); + gpr_mu_lock(&s->mu); + GPR_ASSERT(!s->cb); + GPR_ASSERT(s->active_ports == 0); + s->cb = cb; + s->cb_arg = cb_arg; + for (i = 0; i < s->nports; i++) { + grpc_em_fd_notify_on_read(s->ports[i].emfd, on_read, &s->ports[i], + gpr_inf_future); + s->active_ports++; + } + gpr_mu_unlock(&s->mu); +} diff --git a/src/core/endpoint/tcp_server.h b/src/core/endpoint/tcp_server.h new file mode 100644 index 0000000000..99cb83e181 --- /dev/null +++ b/src/core/endpoint/tcp_server.h @@ -0,0 +1,64 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_ENDPOINT_TCP_SERVER_H__ +#define __GRPC_INTERNAL_ENDPOINT_TCP_SERVER_H__ + +#include <sys/types.h> +#include <sys/socket.h> + +#include "src/core/endpoint/tcp.h" +#include "src/core/eventmanager/em.h" + +/* Forward decl of grpc_tcp_server */ +typedef struct grpc_tcp_server grpc_tcp_server; + +/* New server callback: tcp is the newly connected tcp connection */ +typedef void (*grpc_tcp_server_cb)(void *arg, grpc_endpoint *ep); + +/* Create a server, initially not bound to any ports */ +grpc_tcp_server *grpc_tcp_server_create(grpc_em *em); + +/* Start listening to bound ports */ +void grpc_tcp_server_start(grpc_tcp_server *server, grpc_tcp_server_cb cb, + void *cb_arg); + +/* Add a port to the server, returns a file descriptor on success, or <0 on + failure; the file descriptor remains owned by the server and will be cleaned + up when grpc_tcp_server_destroy is called */ +int grpc_tcp_server_add_port(grpc_tcp_server *server, struct sockaddr *port, + int len); + +void grpc_tcp_server_destroy(grpc_tcp_server *server); + +#endif /* __GRPC_INTERNAL_ENDPOINT_TCP_SERVER_H__ */ diff --git a/src/core/eventmanager/em.c b/src/core/eventmanager/em.c new file mode 100644 index 0000000000..e02d56c0a1 --- /dev/null +++ b/src/core/eventmanager/em.c @@ -0,0 +1,664 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/eventmanager/em.h" + +#include <unistd.h> +#include <fcntl.h> + +#include <grpc/support/atm.h> +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/sync.h> +#include <grpc/support/time.h> +#include <event2/event.h> +#include <event2/thread.h> + +int evthread_use_threads(void); + +#define ALARM_TRIGGER_INIT ((gpr_atm)0) +#define ALARM_TRIGGER_INCREMENT ((gpr_atm)1) +#define DONE_SHUTDOWN ((void *)1) + +#define POLLER_ID_INVALID ((gpr_atm)-1) + +/* ================== grpc_em implementation ===================== */ + +/* If anything is in the work queue, process one item and return 1. + Return 0 if there were no work items to complete. + Requires em->mu locked, may unlock and relock during the call. */ +static int maybe_do_queue_work(grpc_em *em) { + grpc_em_activation_data *work = em->q; + + if (work == NULL) return 0; + + if (work->next == work) { + em->q = NULL; + } else { + em->q = work->next; + em->q->prev = work->prev; + em->q->next->prev = em->q->prev->next = em->q; + } + work->next = work->prev = NULL; + gpr_mu_unlock(&em->mu); + + work->cb(work->arg, work->status); + + gpr_mu_lock(&em->mu); + return 1; +} + +/* Break out of the event loop on timeout */ +static void timer_callback(int fd, short events, void *context) { + event_base_loopbreak((struct event_base *)context); +} + +/* Spend some time polling if no other thread is. + Returns 1 if polling was performed, 0 otherwise. + Requires em->mu locked, may unlock and relock during the call. */ +static int maybe_do_polling_work(grpc_em *em, struct timeval delay) { + int status; + + if (em->num_pollers) return 0; + + em->num_pollers = 1; + gpr_mu_unlock(&em->mu); + + event_add(em->timeout_ev, &delay); + status = event_base_loop(em->event_base, EVLOOP_ONCE); + if (status < 0) { + gpr_log(GPR_ERROR, "event polling loop stops with error status %d", status); + } + event_del(em->timeout_ev); + + gpr_mu_lock(&em->mu); + em->num_pollers = 0; + gpr_cv_broadcast(&em->cv); + return 1; +} + +int grpc_em_work(grpc_em *em, gpr_timespec deadline) { + gpr_timespec delay_timespec = gpr_time_sub(deadline, gpr_now()); + /* poll for no longer than one second */ + gpr_timespec max_delay = {1, 0}; + struct timeval delay; + + GPR_ASSERT(em); + + if (gpr_time_cmp(delay_timespec, gpr_time_0) <= 0) { + return 0; + } + + if (gpr_time_cmp(delay_timespec, max_delay) > 0) { + delay_timespec = max_delay; + } + + delay = gpr_timeval_from_timespec(delay_timespec); + + if (maybe_do_queue_work(em) || maybe_do_polling_work(em, delay)) { + em->last_poll_completed = gpr_now(); + return 1; + } + + return 0; +} + +static void backup_poller_thread(void *p) { + grpc_em *em = p; + int backup_poller_engaged = 0; + /* allow no pollers for 100 milliseconds, then engage backup polling */ + gpr_timespec allow_no_pollers = gpr_time_from_micros(100 * 1000); + + gpr_mu_lock(&em->mu); + while (!em->shutdown_backup_poller) { + if (em->num_pollers == 0) { + gpr_timespec now = gpr_now(); + gpr_timespec time_until_engage = gpr_time_sub( + allow_no_pollers, gpr_time_sub(now, em->last_poll_completed)); + if (gpr_time_cmp(time_until_engage, gpr_time_0) <= 0) { + if (!backup_poller_engaged) { + gpr_log(GPR_DEBUG, "No pollers for a while - engaging backup poller"); + backup_poller_engaged = 1; + } + if (!maybe_do_queue_work(em)) { + struct timeval tv = {1, 0}; + maybe_do_polling_work(em, tv); + } + } else { + if (backup_poller_engaged) { + gpr_log(GPR_DEBUG, "Backup poller disengaged"); + backup_poller_engaged = 0; + } + gpr_mu_unlock(&em->mu); + gpr_sleep_until(gpr_time_add(now, time_until_engage)); + gpr_mu_lock(&em->mu); + } + } else { + if (backup_poller_engaged) { + gpr_log(GPR_DEBUG, "Backup poller disengaged"); + backup_poller_engaged = 0; + } + gpr_cv_wait(&em->cv, &em->mu, gpr_inf_future); + } + } + gpr_mu_unlock(&em->mu); + + gpr_event_set(&em->backup_poller_done, (void *)1); +} + +grpc_em_error grpc_em_init(grpc_em *em) { + gpr_thd_id backup_poller_id; + + if (evthread_use_threads() != 0) { + gpr_log(GPR_ERROR, "Failed to initialize libevent thread support!"); + return GRPC_EM_ERROR; + } + + gpr_mu_init(&em->mu); + gpr_cv_init(&em->cv); + em->q = NULL; + em->num_pollers = 0; + em->num_fds = 0; + em->last_poll_completed = gpr_now(); + em->shutdown_backup_poller = 0; + + gpr_event_init(&em->backup_poller_done); + + em->event_base = NULL; + em->timeout_ev = NULL; + + em->event_base = event_base_new(); + if (!em->event_base) { + gpr_log(GPR_ERROR, "Failed to create the event base"); + return GRPC_EM_ERROR; + } + + if (evthread_make_base_notifiable(em->event_base) != 0) { + gpr_log(GPR_ERROR, "Couldn't make event base notifiable cross threads!"); + return GRPC_EM_ERROR; + } + + em->timeout_ev = evtimer_new(em->event_base, timer_callback, em->event_base); + + gpr_thd_new(&backup_poller_id, backup_poller_thread, em, NULL); + + return GRPC_EM_OK; +} + +grpc_em_error grpc_em_destroy(grpc_em *em) { + gpr_timespec fd_shutdown_deadline = + gpr_time_add(gpr_now(), gpr_time_from_micros(10 * 1000 * 1000)); + + /* broadcast shutdown */ + gpr_mu_lock(&em->mu); + while (em->num_fds) { + gpr_log(GPR_INFO, + "waiting for %d fds to be destroyed before closing event manager", + em->num_fds); + if (gpr_cv_wait(&em->cv, &em->mu, fd_shutdown_deadline)) { + gpr_log(GPR_ERROR, + "not all fds destroyed before shutdown deadline: memory leaks " + "are likely"); + break; + } else if (em->num_fds == 0) { + gpr_log(GPR_INFO, "all fds closed"); + } + } + + em->shutdown_backup_poller = 1; + gpr_cv_broadcast(&em->cv); + gpr_mu_unlock(&em->mu); + + gpr_event_wait(&em->backup_poller_done, gpr_inf_future); + + /* drain pending work */ + gpr_mu_lock(&em->mu); + while (maybe_do_queue_work(em)) + ; + gpr_mu_unlock(&em->mu); + + /* complete shutdown */ + gpr_mu_destroy(&em->mu); + gpr_cv_destroy(&em->cv); + + if (em->timeout_ev != NULL) { + event_free(em->timeout_ev); + } + + if (em->event_base != NULL) { + event_base_free(em->event_base); + em->event_base = NULL; + } + + return GRPC_EM_OK; +} + +static void add_task(grpc_em *em, grpc_em_activation_data *adata) { + gpr_mu_lock(&em->mu); + if (em->q) { + adata->next = em->q; + adata->prev = adata->next->prev; + adata->next->prev = adata->prev->next = adata; + } else { + em->q = adata; + adata->next = adata->prev = adata; + } + gpr_cv_broadcast(&em->cv); + gpr_mu_unlock(&em->mu); +} + +/* ===============grpc_em_alarm implementation==================== */ + +/* The following function frees up the alarm's libevent structure and + should always be invoked just before calling the alarm's callback */ +static void alarm_ev_destroy(grpc_em_alarm *alarm) { + grpc_em_activation_data *adata = &alarm->task.activation[GRPC_EM_TA_ONLY]; + if (adata->ev != NULL) { + event_free(adata->ev); + adata->ev = NULL; + } +} +/* Proxy callback triggered by alarm->ev to call alarm->cb */ +static void libevent_alarm_cb(int fd, short what, void *arg /*=alarm*/) { + grpc_em_alarm *alarm = arg; + grpc_em_activation_data *adata = &alarm->task.activation[GRPC_EM_TA_ONLY]; + int trigger_old; + + /* First check if this alarm has been canceled, atomically */ + trigger_old = + gpr_atm_full_fetch_add(&alarm->triggered, ALARM_TRIGGER_INCREMENT); + if (trigger_old == ALARM_TRIGGER_INIT) { + /* Before invoking user callback, destroy the libevent structure */ + alarm_ev_destroy(alarm); + adata->status = GRPC_CALLBACK_SUCCESS; + add_task(alarm->task.em, adata); + } +} + +grpc_em_error grpc_em_alarm_init(grpc_em_alarm *alarm, grpc_em *em, + grpc_em_cb_func alarm_cb, void *alarm_cb_arg) { + grpc_em_activation_data *adata = &alarm->task.activation[GRPC_EM_TA_ONLY]; + alarm->task.type = GRPC_EM_TASK_ALARM; + alarm->task.em = em; + gpr_atm_rel_store(&alarm->triggered, ALARM_TRIGGER_INIT); + adata->cb = alarm_cb; + adata->arg = alarm_cb_arg; + adata->prev = NULL; + adata->next = NULL; + adata->ev = NULL; + return GRPC_EM_OK; +} + +grpc_em_error grpc_em_alarm_add(grpc_em_alarm *alarm, gpr_timespec deadline) { + grpc_em_activation_data *adata = &alarm->task.activation[GRPC_EM_TA_ONLY]; + gpr_timespec delay_timespec = gpr_time_sub(deadline, gpr_now()); + struct timeval delay = gpr_timeval_from_timespec(delay_timespec); + if (adata->ev) { + event_free(adata->ev); + gpr_log(GPR_INFO, "Adding an alarm that already has an event."); + adata->ev = NULL; + } + adata->ev = evtimer_new(alarm->task.em->event_base, libevent_alarm_cb, alarm); + /* Set the trigger field to untriggered. Do this as the last store since + it is a release of previous stores. */ + gpr_atm_rel_store(&alarm->triggered, ALARM_TRIGGER_INIT); + + if (adata->ev != NULL && evtimer_add(adata->ev, &delay) == 0) { + return GRPC_EM_OK; + } else { + return GRPC_EM_ERROR; + } +} + +grpc_em_error grpc_em_alarm_cancel(grpc_em_alarm *alarm, void **arg) { + grpc_em_activation_data *adata = &alarm->task.activation[GRPC_EM_TA_ONLY]; + int trigger_old; + + *arg = adata->arg; + + /* First check if this alarm has been triggered, atomically */ + trigger_old = + gpr_atm_full_fetch_add(&alarm->triggered, ALARM_TRIGGER_INCREMENT); + if (trigger_old == ALARM_TRIGGER_INIT) { + /* We need to make sure that we only invoke the callback if it hasn't + already been invoked */ + /* First remove this event from libevent. This returns success even if the + event has gone active or invoked its callback. */ + if (evtimer_del(adata->ev) != 0) { + /* The delete was unsuccessful for some reason. */ + gpr_log(GPR_ERROR, "Attempt to delete alarm event was unsuccessful"); + return GRPC_EM_ERROR; + } + /* Free up the event structure before invoking callback */ + alarm_ev_destroy(alarm); + adata->status = GRPC_CALLBACK_CANCELLED; + add_task(alarm->task.em, adata); + } + return GRPC_EM_OK; +} + +/* ==================== grpc_em_fd implementation =================== */ + +/* Proxy callback to call a gRPC read/write callback */ +static void em_fd_cb(int fd, short what, void *arg /*=em_fd*/) { + grpc_em_fd *em_fd = arg; + grpc_em_cb_status status = GRPC_CALLBACK_SUCCESS; + int run_read_cb = 0; + int run_write_cb = 0; + grpc_em_activation_data *rdata, *wdata; + + gpr_mu_lock(&em_fd->mu); + /* TODO(klempner): We need to delete the event here too so we avoid spurious + shutdowns. */ + if (em_fd->shutdown_started) { + status = GRPC_CALLBACK_CANCELLED; + } else if (status == GRPC_CALLBACK_SUCCESS && (what & EV_TIMEOUT)) { + status = GRPC_CALLBACK_TIMED_OUT; + /* TODO(klempner): This is broken if we are monitoring both read and write + events on the same fd -- generating a spurious event is okay, but + generating a spurious timeout is not. */ + what |= (EV_READ | EV_WRITE); + } + + if (what & EV_READ) { + switch (em_fd->read_state) { + case GRPC_EM_FD_WAITING: + run_read_cb = 1; + em_fd->read_state = GRPC_EM_FD_IDLE; + break; + case GRPC_EM_FD_IDLE: + case GRPC_EM_FD_CACHED: + em_fd->read_state = GRPC_EM_FD_CACHED; + } + } + if (what & EV_WRITE) { + switch (em_fd->write_state) { + case GRPC_EM_FD_WAITING: + run_write_cb = 1; + em_fd->write_state = GRPC_EM_FD_IDLE; + break; + case GRPC_EM_FD_IDLE: + case GRPC_EM_FD_CACHED: + em_fd->write_state = GRPC_EM_FD_CACHED; + } + } + + if (run_read_cb) { + rdata = &(em_fd->task.activation[GRPC_EM_TA_READ]); + rdata->status = status; + add_task(em_fd->task.em, rdata); + } else if (run_write_cb) { + wdata = &(em_fd->task.activation[GRPC_EM_TA_WRITE]); + wdata->status = status; + add_task(em_fd->task.em, wdata); + } + gpr_mu_unlock(&em_fd->mu); +} + +static void em_fd_shutdown_cb(int fd, short what, void *arg /*=em_fd*/) { + /* TODO(klempner): This could just run directly in the calling thread, except + that libevent's handling of event_active() on an event which is already in + flight on a different thread is racy and easily triggers TSAN. + */ + grpc_em_fd *em_fd = arg; + gpr_mu_lock(&em_fd->mu); + em_fd->shutdown_started = 1; + if (em_fd->read_state == GRPC_EM_FD_WAITING) { + event_active(em_fd->task.activation[GRPC_EM_TA_READ].ev, EV_READ, 1); + } + if (em_fd->write_state == GRPC_EM_FD_WAITING) { + event_active(em_fd->task.activation[GRPC_EM_TA_WRITE].ev, EV_WRITE, 1); + } + gpr_mu_unlock(&em_fd->mu); +} + +grpc_em_error grpc_em_fd_init(grpc_em_fd *em_fd, grpc_em *em, int fd) { + int flags; + grpc_em_activation_data *rdata, *wdata; + + gpr_mu_lock(&em->mu); + em->num_fds++; + gpr_mu_unlock(&em->mu); + + em_fd->shutdown_ev = NULL; + gpr_mu_init(&em_fd->mu); + + flags = fcntl(fd, F_GETFL, 0); + if ((flags & O_NONBLOCK) == 0) { + gpr_log(GPR_ERROR, "File descriptor %d is blocking", fd); + return GRPC_EM_INVALID_ARGUMENTS; + } + + em_fd->task.type = GRPC_EM_TASK_FD; + em_fd->task.em = em; + em_fd->fd = fd; + + rdata = &(em_fd->task.activation[GRPC_EM_TA_READ]); + rdata->ev = NULL; + rdata->cb = NULL; + rdata->arg = NULL; + rdata->status = GRPC_CALLBACK_SUCCESS; + rdata->prev = NULL; + rdata->next = NULL; + + wdata = &(em_fd->task.activation[GRPC_EM_TA_WRITE]); + wdata->ev = NULL; + wdata->cb = NULL; + wdata->arg = NULL; + wdata->status = GRPC_CALLBACK_SUCCESS; + wdata->prev = NULL; + wdata->next = NULL; + + em_fd->read_state = GRPC_EM_FD_IDLE; + em_fd->write_state = GRPC_EM_FD_IDLE; + + /* TODO(chenw): detect platforms where only level trigger is supported, + and set the event to non-persist. */ + rdata->ev = event_new(em->event_base, em_fd->fd, EV_ET | EV_PERSIST | EV_READ, + em_fd_cb, em_fd); + if (!rdata->ev) { + gpr_log(GPR_ERROR, "Failed to create read event"); + return GRPC_EM_ERROR; + } + + wdata->ev = event_new(em->event_base, em_fd->fd, + EV_ET | EV_PERSIST | EV_WRITE, em_fd_cb, em_fd); + if (!wdata->ev) { + gpr_log(GPR_ERROR, "Failed to create write event"); + return GRPC_EM_ERROR; + } + + em_fd->shutdown_ev = + event_new(em->event_base, -1, EV_READ, em_fd_shutdown_cb, em_fd); + + if (!em_fd->shutdown_ev) { + gpr_log(GPR_ERROR, "Failed to create shutdown event"); + return GRPC_EM_ERROR; + } + + em_fd->shutdown_started = 0; + return GRPC_EM_OK; +} + +void grpc_em_fd_destroy(grpc_em_fd *em_fd) { + grpc_em_task_activity_type type; + grpc_em_activation_data *adata; + grpc_em *em = em_fd->task.em; + + /* ensure anyone holding the lock has left - it's the callers responsibility + to ensure that no new users enter */ + gpr_mu_lock(&em_fd->mu); + gpr_mu_unlock(&em_fd->mu); + + for (type = GRPC_EM_TA_READ; type < GRPC_EM_TA_COUNT; type++) { + adata = &(em_fd->task.activation[type]); + GPR_ASSERT(adata->next == NULL); + if (adata->ev != NULL) { + event_free(adata->ev); + adata->ev = NULL; + } + } + + if (em_fd->shutdown_ev != NULL) { + event_free(em_fd->shutdown_ev); + em_fd->shutdown_ev = NULL; + } + gpr_mu_destroy(&em_fd->mu); + + gpr_mu_lock(&em->mu); + em->num_fds--; + gpr_cv_broadcast(&em->cv); + gpr_mu_unlock(&em->mu); +} + +int grpc_em_fd_get(struct grpc_em_fd *em_fd) { return em_fd->fd; } + +/* Returns the event manager associated with *em_fd. */ +grpc_em *grpc_em_fd_get_em(grpc_em_fd *em_fd) { return em_fd->task.em; } + +/* TODO(chenw): should we enforce the contract that notify_on_read cannot be + called when the previously registered callback has not been called yet. */ +grpc_em_error grpc_em_fd_notify_on_read(grpc_em_fd *em_fd, + grpc_em_cb_func read_cb, + void *read_cb_arg, + gpr_timespec deadline) { + int force_event = 0; + grpc_em_activation_data *rdata; + grpc_em_error result = GRPC_EM_OK; + gpr_timespec delay_timespec = gpr_time_sub(deadline, gpr_now()); + struct timeval delay = gpr_timeval_from_timespec(delay_timespec); + struct timeval *delayp = + gpr_time_cmp(deadline, gpr_inf_future) ? &delay : NULL; + + rdata = &em_fd->task.activation[GRPC_EM_TA_READ]; + + gpr_mu_lock(&em_fd->mu); + rdata->cb = read_cb; + rdata->arg = read_cb_arg; + + force_event = + (em_fd->shutdown_started || em_fd->read_state == GRPC_EM_FD_CACHED); + em_fd->read_state = GRPC_EM_FD_WAITING; + + if (force_event) { + event_active(rdata->ev, EV_READ, 1); + } else if (event_add(rdata->ev, delayp) == -1) { + result = GRPC_EM_ERROR; + } + gpr_mu_unlock(&em_fd->mu); + return result; +} + +grpc_em_error grpc_em_fd_notify_on_write(grpc_em_fd *em_fd, + grpc_em_cb_func write_cb, + void *write_cb_arg, + gpr_timespec deadline) { + int force_event = 0; + grpc_em_activation_data *wdata; + grpc_em_error result = GRPC_EM_OK; + gpr_timespec delay_timespec = gpr_time_sub(deadline, gpr_now()); + struct timeval delay = gpr_timeval_from_timespec(delay_timespec); + struct timeval *delayp = + gpr_time_cmp(deadline, gpr_inf_future) ? &delay : NULL; + + wdata = &em_fd->task.activation[GRPC_EM_TA_WRITE]; + + gpr_mu_lock(&em_fd->mu); + wdata->cb = write_cb; + wdata->arg = write_cb_arg; + + force_event = + (em_fd->shutdown_started || em_fd->write_state == GRPC_EM_FD_CACHED); + em_fd->write_state = GRPC_EM_FD_WAITING; + + if (force_event) { + event_active(wdata->ev, EV_WRITE, 1); + } else if (event_add(wdata->ev, delayp) == -1) { + result = GRPC_EM_ERROR; + } + gpr_mu_unlock(&em_fd->mu); + return result; +} + +void grpc_em_fd_shutdown(grpc_em_fd *em_fd) { + event_active(em_fd->shutdown_ev, EV_READ, 1); +} + +/*====================== Other callback functions ======================*/ + +/* Sometimes we want a followup callback: something to be added from the + current callback for the EM to invoke once this callback is complete. + This is implemented by inserting an entry into an EM queue. */ + +/* The following structure holds the field needed for adding the + followup callback. These are the argument for the followup callback, + the function to use for the followup callback, and the + activation data pointer used for the queues (to free in the CB) */ +struct followup_callback_arg { + grpc_em_cb_func func; + void *cb_arg; + grpc_em_activation_data adata; +}; + +static void followup_proxy_callback(void *cb_arg, grpc_em_cb_status status) { + struct followup_callback_arg *fcb_arg = cb_arg; + /* Invoke the function */ + fcb_arg->func(fcb_arg->cb_arg, status); + gpr_free(fcb_arg); +} + +grpc_em_error grpc_em_add_callback(grpc_em *em, grpc_em_cb_func cb, + void *cb_arg) { + grpc_em_activation_data *adptr; + struct followup_callback_arg *fcb_arg; + + fcb_arg = gpr_malloc(sizeof(*fcb_arg)); + if (fcb_arg == NULL) { + return GRPC_EM_ERROR; + } + /* Set up the activation data and followup callback argument structures */ + adptr = &fcb_arg->adata; + adptr->ev = NULL; + adptr->cb = followup_proxy_callback; + adptr->arg = fcb_arg; + adptr->status = GRPC_CALLBACK_SUCCESS; + adptr->prev = NULL; + adptr->next = NULL; + + fcb_arg->func = cb; + fcb_arg->cb_arg = cb_arg; + + /* Insert an activation data for the specified em */ + add_task(em, adptr); + return GRPC_EM_OK; +} diff --git a/src/core/eventmanager/em.h b/src/core/eventmanager/em.h new file mode 100644 index 0000000000..32d37a5b98 --- /dev/null +++ b/src/core/eventmanager/em.h @@ -0,0 +1,350 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_EVENTMANAGER_EM_H__ +#define __GRPC_INTERNAL_EVENTMANAGER_EM_H__ +/* grpc_em is an event manager wrapping event loop with multithread support. + It executes a callback function when a specific event occurs on a file + descriptor or after a timeout has passed. + All methods are threadsafe and can be called from any thread. + + To use the event manager, a grpc_em instance needs to be initialized to + maintains the internal states. The grpc_em instance can be used to + initialize file descriptor instance of grpc_em_fd, or alarm instance of + grpc_em_alarm. The former is used to register a callback with a IO event. + The later is used to schedule an alarm. + + Instantiating any of these data structures requires including em_internal.h + A typical usage example is shown in the end of that header file. */ + +#include <grpc/support/atm.h> +#include <grpc/support/sync.h> +#include <grpc/support/thd.h> +#include <grpc/support/time.h> + +/* =============== Enums used in GRPC event manager API ==================== */ + +/* Result of a grpc_em operation */ +typedef enum grpc_em_error { + GRPC_EM_OK = 0, /* everything went ok */ + GRPC_EM_ERROR, /* internal errors not caused by the caller */ + GRPC_EM_INVALID_ARGUMENTS /* invalid arguments from the caller */ +} grpc_em_error; + +/* Status passed to callbacks for grpc_em_fd_notify_on_read and + grpc_em_fd_notify_on_write. */ +typedef enum grpc_em_cb_status { + GRPC_CALLBACK_SUCCESS = 0, + GRPC_CALLBACK_TIMED_OUT, + GRPC_CALLBACK_CANCELLED, + GRPC_CALLBACK_DO_NOT_USE +} grpc_em_cb_status; + +/* ======= Useful forward struct typedefs for GRPC event manager API ======= */ + +struct grpc_em; +struct grpc_em_alarm; +struct grpc_fd; + +typedef struct grpc_em grpc_em; +typedef struct grpc_em_alarm grpc_em_alarm; +typedef struct grpc_em_fd grpc_em_fd; + +/* gRPC Callback definition */ +typedef void (*grpc_em_cb_func)(void *arg, grpc_em_cb_status status); + +/* ============================ grpc_em =============================== */ +/* Initialize *em and start polling, return GRPC_EM_OK on success, return + GRPC_EM_ERROR on failure. Upon failure, caller should call grpc_em_destroy() + to clean partially initialized *em. + + Requires: *em uninitialized. */ +grpc_em_error grpc_em_init(grpc_em *em); + +/* Stop polling and cause *em no longer to be initialized. + Return GRPC_EM_OK if event polling is cleanly stopped. + Otherwise, return GRPC_EM_ERROR if polling is shutdown with errors. + Requires: *em initialized; no other concurrent operation on *em. */ +grpc_em_error grpc_em_destroy(grpc_em *em); + +/* do some work; assumes em->mu locked; may unlock and relock em->mu */ +int grpc_em_work(grpc_em *em, gpr_timespec deadline); + +/* =========================== grpc_em_am ============================== */ +/* Initialize *alarm. When expired or canceled, alarm_cb will be called with + *alarm_cb_arg and status to indicate if it expired (SUCCESS) or was + canceled (CANCELLED). alarm_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. */ +grpc_em_error grpc_em_alarm_init(grpc_em_alarm *alarm, grpc_em *em, + grpc_em_cb_func alarm_cb, void *alarm_cb_arg); + +/* Note that there is no alarm destroy function. This is because the + alarm 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 alarm event management state is destroyed just before + that callback is invoked. If the user has additional state associated with + the alarm, the user is responsible for determining when it is safe to + destroy that state. */ + +/* Schedule *alarm to expire at deadline. If *alarm is + re-added before expiration, the *delay is simply reset to the new value. + Return GRPC_EM_OK on success, or GRPC_EM_ERROR on failure. + Upon failure, caller should abort further operations on *alarm */ +grpc_em_error grpc_em_alarm_add(grpc_em_alarm *alarm, gpr_timespec deadline); + +/* Cancel an *alarm. + There are three cases: + 1. We normally cancel the alarm + 2. The alarm has already run + 3. We can't cancel the alarm because it is "in flight". + + In all of these cases, the cancellation is still considered successful. + They are essentially distinguished in that the alarm_cb will be run + exactly once from either the cancellation (with status CANCELLED) + or from the activation (with status SUCCESS) + + Requires: cancel() must happen after add() on a given alarm */ +grpc_em_error grpc_em_alarm_cancel(grpc_em_alarm *alarm, void **arg); + +/* ========================== grpc_em_fd ============================= */ + +/* Initialize *em_fd, return GRPM_EM_OK on success, GRPC_EM_ERROR on internal + errors, or GRPC_EM_INVALID_ARGUMENTS if fd is a blocking file descriptor. + Upon failure, caller should call grpc_em_fd_destroy() to clean partially + initialized *em_fd. + fd is a non-blocking file descriptor. + + Requires: *em_fd uninitialized. fd is a non-blocking file descriptor. */ +grpc_em_error grpc_em_fd_init(grpc_em_fd *em_fd, grpc_em *em, int fd); + +/* Cause *em_fd no longer to be initialized. + Requires: *em_fd initialized; no outstanding notify_on_read or + notify_on_write. */ +void grpc_em_fd_destroy(grpc_em_fd *em_fd); + +/* Returns the file descriptor associated with *em_fd. */ +int grpc_em_fd_get(grpc_em_fd *em_fd); + +/* Returns the event manager associated with *em_fd. */ +grpc_em *grpc_em_fd_get_em(grpc_em_fd *em_fd); + +/* Register read interest, causing read_cb to be called once when em_fd becomes + readable, on deadline specified by deadline, or on shutdown triggered by + grpc_em_fd_shutdown. + Return GRPC_EM_OK on success, or GRPC_EM_ERROR on failure. + Upon Failure, caller should abort further operations on *em_fd except + grpc_em_fd_shutdown(). + read_cb will be called with read_cb_arg when *em_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 em_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 em_fd */ +grpc_em_error grpc_em_fd_notify_on_read(grpc_em_fd *em_fd, + grpc_em_cb_func read_cb, + void *read_cb_arg, + gpr_timespec deadline); + +/* Exactly the same semantics as above, except based on writable events. */ +grpc_em_error grpc_em_fd_notify_on_write(grpc_em_fd *fd, + grpc_em_cb_func write_cb, + void *write_cb_arg, + gpr_timespec deadline); + +/* Cause any current and all future read/write callbacks to error out with + GRPC_CALLBACK_CANCELLED. */ +void grpc_em_fd_shutdown(grpc_em_fd *em_fd); + +/* ================== Other functions =================== */ + +/* This function is called from within a callback or from anywhere else + and causes the invocation of a callback at some point in the future */ +grpc_em_error grpc_em_add_callback(grpc_em *em, grpc_em_cb_func cb, + void *cb_arg); + +/* ========== Declarations related to queue management (non-API) =========== */ + +/* Forward declarations */ +struct grpc_em_activation_data; + +/* ================== Actual structure definitions ========================= */ +/* gRPC event manager handle. + The handle is used to initialize both grpc_em_alarm and grpc_em_fd. */ +struct em_thread_arg; + +struct grpc_em { + struct event_base *event_base; + + gpr_mu mu; + gpr_cv cv; + struct grpc_em_activation_data *q; + int num_pollers; + int num_fds; + gpr_timespec last_poll_completed; + + int shutdown_backup_poller; + gpr_event backup_poller_done; + + struct event *timeout_ev; /* activated to break out of the event loop early */ +}; + +/* gRPC event manager task "base class". This is pretend-inheritance in C89. + This should be the first member of any actual grpc_em task type. + + Memory warning: expanding this will increase memory usage in any derived + class, so be careful. + + For generality, this base can be on multiple task queues and can have + multiple event callbacks registered. Not all "derived classes" will use + this feature. */ + +typedef enum grpc_em_task_type { + GRPC_EM_TASK_ALARM, + GRPC_EM_TASK_FD, + GRPC_EM_TASK_DO_NOT_USE +} grpc_em_task_type; + +/* Different activity types to shape the callback and queueing arrays */ +typedef enum grpc_em_task_activity_type { + GRPC_EM_TA_READ, /* use this also for single-type events */ + GRPC_EM_TA_WRITE, + GRPC_EM_TA_COUNT +} grpc_em_task_activity_type; + +/* Include the following #define for convenience for tasks like alarms that + only have a single type */ +#define GRPC_EM_TA_ONLY GRPC_EM_TA_READ + +typedef struct grpc_em_activation_data { + struct event *ev; /* event activated on this callback type */ + grpc_em_cb_func cb; /* function pointer for callback */ + void *arg; /* argument passed to cb */ + + /* Hold the status associated with the callback when queued */ + grpc_em_cb_status status; + /* Now set up to link activations into scheduler queues */ + struct grpc_em_activation_data *prev; + struct grpc_em_activation_data *next; +} grpc_em_activation_data; + +typedef struct grpc_em_task { + grpc_em_task_type type; + grpc_em *em; + + /* Now have an array of activation data elements: one for each activity + type that could get activated */ + grpc_em_activation_data activation[GRPC_EM_TA_COUNT]; +} grpc_em_task; + +/* gRPC alarm handle. + The handle is used to add an alarm which expires after specified timeout. */ +struct grpc_em_alarm { + grpc_em_task task; /* Include the base class */ + + gpr_atm triggered; /* To be used atomically if alarm triggered */ +}; + +/* =================== Event caching =================== + In order to not miss or double-return edges in the context of edge triggering + and multithreading, we need a per-fd caching layer in the eventmanager itself + to cache relevant events. + + There are two types of events we care about: calls to notify_on_[read|write] + and readable/writable events for the socket from eventfd. There are separate + event caches for read and write. + + There are three states: + 0. "waiting" -- There's been a call to notify_on_[read|write] which has not + had a corresponding event. In other words, we're waiting for an event so we + can run the callback. + 1. "idle" -- We are neither waiting nor have a cached event. + 2. "cached" -- There has been a read/write event without a waiting callback, + so we want to run the event next time the application calls + notify_on_[read|write]. + + The high level state diagram: + + +--------------------------------------------------------------------+ + | WAITING | IDLE | CACHED | + | | | | + | 1. --*-> 2. --+-> 3. --+\ + | | | <--+/ + | | | | + x+-- 6. 5. <-+-- 4. <-*-- | + | | | | + +--------------------------------------------------------------------+ + + Transitions right occur on read|write events. Transitions left occur on + notify_on_[read|write] events. + State transitions: + 1. Read|Write event while waiting -> run the callback and transition to idle. + 2. Read|Write event while idle -> transition to cached. + 3. Read|Write event with one already cached -> still cached. + 4. notify_on_[read|write] with event cached: run callback and transition to + idle. + 5. notify_on_[read|write] when idle: Store callback and transition to + waiting. + 6. notify_on_[read|write] when waiting: invalid. */ + +typedef enum grpc_em_fd_state { + GRPC_EM_FD_WAITING = 0, + GRPC_EM_FD_IDLE = 1, + GRPC_EM_FD_CACHED = 2 +} grpc_em_fd_state; + +/* gRPC file descriptor handle. + The handle is used to register read/write callbacks to a file descriptor */ +struct grpc_em_fd { + grpc_em_task task; /* Base class, callbacks, queues, etc */ + int fd; /* File descriptor */ + + /* Note that the shutdown event is only needed as a workaround for libevent + not properly handling event_active on an in flight event. */ + struct event *shutdown_ev; /* activated to trigger shutdown */ + + /* protect shutdown_started|read_state|write_state and ensure barriers + between notify_on_[read|write] and read|write callbacks */ + gpr_mu mu; + int shutdown_started; /* 0 -> shutdown not started, 1 -> started */ + grpc_em_fd_state read_state; + grpc_em_fd_state write_state; + /* activated after some timeout to activate shutdown_ev */ +}; + +#endif /* __GRPC_INTERNAL_EVENTMANAGER_EM_H__ */ diff --git a/src/core/eventmanager/em_posix.c b/src/core/eventmanager/em_posix.c new file mode 100644 index 0000000000..af449342f0 --- /dev/null +++ b/src/core/eventmanager/em_posix.c @@ -0,0 +1,56 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 grpc event manager support code. */ +#include <grpc/support/log.h> +#include <grpc/support/sync.h> +#include <event2/thread.h> + +static int error_code = 0; +static gpr_once threads_once = GPR_ONCE_INIT; +static void evthread_threads_initialize(void) { + error_code = evthread_use_pthreads(); + if (error_code) { + gpr_log(GPR_ERROR, "Failed to initialize libevent thread support!"); + } +} + +/* Notify LibEvent that Posix pthread is used. */ +int evthread_use_threads() { + gpr_once_init(&threads_once, &evthread_threads_initialize); + /* For Pthreads or Windows threads, Libevent provides simple APIs to set + mutexes and conditional variables to support cross thread operations. + For other platforms, LibEvent provide callback APIs to hook mutexes and + conditional variables. */ + return error_code; +} diff --git a/src/core/eventmanager/em_win32.c b/src/core/eventmanager/em_win32.c new file mode 100644 index 0000000000..4d5c3b5126 --- /dev/null +++ b/src/core/eventmanager/em_win32.c @@ -0,0 +1,38 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 event manager support code. */ +#include <event2/thread.h> + +/* Notify LibEvent that Windows thread is used. */ +int evthread_use_threads() { return evthread_use_windows_threads(); } diff --git a/src/core/httpcli/format_request.c b/src/core/httpcli/format_request.c new file mode 100644 index 0000000000..7a44f1266f --- /dev/null +++ b/src/core/httpcli/format_request.c @@ -0,0 +1,121 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/httpcli/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/useful.h> + +typedef struct { + size_t length; + size_t capacity; + char *data; +} sbuf; + +static void sbuf_append(sbuf *buf, const char *bytes, size_t len) { + if (buf->length + len > buf->capacity) { + buf->capacity = GPR_MAX(buf->length + len, buf->capacity * 3 / 2); + buf->data = gpr_realloc(buf->data, buf->capacity); + } + memcpy(buf->data + buf->length, bytes, len); + buf->length += len; +} + +static void sbprintf(sbuf *buf, const char *fmt, ...) { + char temp[GRPC_HTTPCLI_MAX_HEADER_LENGTH]; + size_t len; + va_list args; + + va_start(args, fmt); + len = vsprintf(temp, fmt, args); + va_end(args); + + sbuf_append(buf, temp, len); +} + +static void fill_common_header(const grpc_httpcli_request *request, sbuf *buf) { + size_t i; + sbprintf(buf, "%s HTTP/1.0\r\n", request->path); + /* just in case some crazy server really expects HTTP/1.1 */ + sbprintf(buf, "Host: %s\r\n", request->host); + sbprintf(buf, "Connection: close\r\n"); + sbprintf(buf, "User-Agent: %s\r\n", GRPC_HTTPCLI_USER_AGENT); + /* user supplied headers */ + for (i = 0; i < request->hdr_count; i++) { + sbprintf(buf, "%s: %s\r\n", request->hdrs[i].key, request->hdrs[i].value); + } +} + +gpr_slice grpc_httpcli_format_get_request(const grpc_httpcli_request *request) { + sbuf out = {0, 0, NULL}; + + sbprintf(&out, "GET "); + fill_common_header(request, &out); + sbprintf(&out, "\r\n"); + + return gpr_slice_new(out.data, out.length, gpr_free); +} + +gpr_slice grpc_httpcli_format_post_request(const grpc_httpcli_request *request, + const char *body_bytes, + size_t body_size) { + sbuf out = {0, 0, NULL}; + size_t i; + + sbprintf(&out, "POST "); + fill_common_header(request, &out); + if (body_bytes) { + gpr_uint8 has_content_type = 0; + for (i = 0; i < request->hdr_count; i++) { + if (strcmp(request->hdrs[i].key, "Content-Type") == 0) { + has_content_type = 1; + break; + } + } + if (!has_content_type) { + sbprintf(&out, "Content-Type: text/plain\r\n"); + } + sbprintf(&out, "Content-Length: %lu\r\n", (unsigned long)body_size); + } + sbprintf(&out, "\r\n"); + if (body_bytes) { + sbuf_append(&out, body_bytes, body_size); + } + + return gpr_slice_new(out.data, out.length, gpr_free); +} diff --git a/src/core/httpcli/format_request.h b/src/core/httpcli/format_request.h new file mode 100644 index 0000000000..988f872563 --- /dev/null +++ b/src/core/httpcli/format_request.h @@ -0,0 +1,45 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_HTTPCLI_FORMAT_REQUEST_H__ +#define __GRPC_INTERNAL_HTTPCLI_FORMAT_REQUEST_H__ + +#include "src/core/httpcli/httpcli.h" +#include <grpc/support/slice.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_INTERNAL_HTTPCLI_FORMAT_REQUEST_H__ */ diff --git a/src/core/httpcli/httpcli.c b/src/core/httpcli/httpcli.c new file mode 100644 index 0000000000..6c0a688390 --- /dev/null +++ b/src/core/httpcli/httpcli.c @@ -0,0 +1,259 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/httpcli/httpcli.h" + +#include <string.h> + +#include "src/core/endpoint/endpoint.h" +#include "src/core/endpoint/resolve_address.h" +#include "src/core/endpoint/tcp_client.h" +#include "src/core/httpcli/format_request.h" +#include "src/core/httpcli/httpcli_security_context.h" +#include "src/core/httpcli/parser.h" +#include "src/core/security/security_context.h" +#include "src/core/security/google_root_certs.h" +#include "src/core/security/secure_transport_setup.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string.h> + +typedef struct { + gpr_slice request_text; + grpc_httpcli_parser parser; + grpc_resolved_addresses *addresses; + size_t next_address; + grpc_endpoint *ep; + grpc_em *em; + char *host; + gpr_timespec deadline; + int have_read_byte; + int use_ssl; + grpc_httpcli_response_cb on_response; + void *user_data; +} internal_request; + +static void next_address(internal_request *req); + +static void finish(internal_request *req, int success) { + gpr_log(GPR_DEBUG, "%s", __FUNCTION__); + req->on_response(req->user_data, success ? &req->parser.r : NULL); + grpc_httpcli_parser_destroy(&req->parser); + if (req->addresses != NULL) { + grpc_resolved_addresses_destroy(req->addresses); + } + if (req->ep != NULL) { + grpc_endpoint_destroy(req->ep); + } + gpr_slice_unref(req->request_text); + gpr_free(req->host); + gpr_free(req); +} + +static void on_read(void *user_data, gpr_slice *slices, size_t nslices, + grpc_endpoint_cb_status status) { + internal_request *req = user_data; + size_t i; + + gpr_log(GPR_DEBUG, "%s nslices=%d status=%d", __FUNCTION__, nslices, status); + + for (i = 0; i < nslices; i++) { + if (GPR_SLICE_LENGTH(slices[i])) { + req->have_read_byte = 1; + if (!grpc_httpcli_parser_parse(&req->parser, slices[i])) { + finish(req, 0); + goto done; + } + } + } + + switch (status) { + case GRPC_ENDPOINT_CB_OK: + grpc_endpoint_notify_on_read(req->ep, on_read, req, gpr_inf_future); + break; + case GRPC_ENDPOINT_CB_EOF: + case GRPC_ENDPOINT_CB_ERROR: + case GRPC_ENDPOINT_CB_SHUTDOWN: + case GRPC_ENDPOINT_CB_TIMED_OUT: + if (!req->have_read_byte) { + next_address(req); + } else { + finish(req, grpc_httpcli_parser_eof(&req->parser)); + } + break; + } + +done: + for (i = 0; i < nslices; i++) { + gpr_slice_unref(slices[i]); + } +} + +static void on_written(internal_request *req) { + gpr_log(GPR_DEBUG, "%s", __FUNCTION__); + grpc_endpoint_notify_on_read(req->ep, on_read, req, gpr_inf_future); +} + +static void done_write(void *arg, grpc_endpoint_cb_status status) { + internal_request *req = arg; + gpr_log(GPR_DEBUG, "%s", __FUNCTION__); + switch (status) { + case GRPC_ENDPOINT_CB_OK: + on_written(req); + break; + case GRPC_ENDPOINT_CB_EOF: + case GRPC_ENDPOINT_CB_SHUTDOWN: + case GRPC_ENDPOINT_CB_ERROR: + case GRPC_ENDPOINT_CB_TIMED_OUT: + next_address(req); + break; + } +} + +static void start_write(internal_request *req) { + gpr_slice_ref(req->request_text); + gpr_log(GPR_DEBUG, "%s", __FUNCTION__); + switch (grpc_endpoint_write(req->ep, &req->request_text, 1, done_write, req, + gpr_inf_future)) { + case GRPC_ENDPOINT_WRITE_DONE: + on_written(req); + break; + case GRPC_ENDPOINT_WRITE_PENDING: + break; + case GRPC_ENDPOINT_WRITE_ERROR: + finish(req, 0); + break; + } +} + +static void on_secure_transport_setup_done(void *rp, + grpc_security_status status, + grpc_endpoint *secure_endpoint) { + internal_request *req = rp; + gpr_log(GPR_DEBUG, "%s", __FUNCTION__); + if (status != GRPC_SECURITY_OK) { + gpr_log(GPR_ERROR, "Secure transport setup failed with error %d.", status); + finish(req, 0); + } else { + req->ep = secure_endpoint; + start_write(req); + } +} + +static void on_connected(void *arg, grpc_endpoint *tcp) { + internal_request *req = arg; + + gpr_log(GPR_DEBUG, "%s", __FUNCTION__); + if (!tcp) { + next_address(req); + return; + } + req->ep = tcp; + if (req->use_ssl) { + grpc_channel_security_context *ctx = NULL; + GPR_ASSERT(grpc_httpcli_ssl_channel_security_context_create( + grpc_google_root_certs, grpc_google_root_certs_size, + req->host, &ctx) == GRPC_SECURITY_OK); + grpc_setup_secure_transport(&ctx->base, tcp, on_secure_transport_setup_done, + req); + grpc_security_context_unref(&ctx->base); + } else { + start_write(req); + } +} + +static void next_address(internal_request *req) { + grpc_resolved_address *addr; + gpr_log(GPR_DEBUG, "%s", __FUNCTION__); + if (req->next_address == req->addresses->naddrs) { + finish(req, 0); + return; + } + addr = &req->addresses->addrs[req->next_address++]; + grpc_tcp_client_connect(on_connected, req, req->em, + (struct sockaddr *)&addr->addr, addr->len, + req->deadline); +} + +static void on_resolved(void *arg, grpc_resolved_addresses *addresses) { + internal_request *req = arg; + gpr_log(GPR_DEBUG, "%s", __FUNCTION__); + if (!addresses) { + finish(req, 0); + } + req->addresses = addresses; + req->next_address = 0; + next_address(req); +} + +void grpc_httpcli_get(const grpc_httpcli_request *request, + gpr_timespec deadline, grpc_em *em, + grpc_httpcli_response_cb on_response, void *user_data) { + internal_request *req = gpr_malloc(sizeof(internal_request)); + memset(req, 0, sizeof(*req)); + req->request_text = grpc_httpcli_format_get_request(request); + grpc_httpcli_parser_init(&req->parser); + req->on_response = on_response; + req->user_data = user_data; + req->em = em; + req->deadline = deadline; + req->use_ssl = request->use_ssl; + if (req->use_ssl) { + req->host = gpr_strdup(request->host); + } + + grpc_resolve_address(request->host, req->use_ssl ? "https" : "http", + on_resolved, req); +} + +void grpc_httpcli_post(const grpc_httpcli_request *request, + const char *body_bytes, size_t body_size, + gpr_timespec deadline, grpc_em *em, + grpc_httpcli_response_cb on_response, void *user_data) { + internal_request *req = gpr_malloc(sizeof(internal_request)); + memset(req, 0, sizeof(*req)); + req->request_text = + grpc_httpcli_format_post_request(request, body_bytes, body_size); + grpc_httpcli_parser_init(&req->parser); + req->on_response = on_response; + req->user_data = user_data; + req->em = em; + req->deadline = deadline; + req->use_ssl = request->use_ssl; + if (req->use_ssl) { + req->host = gpr_strdup(request->host); + } + + grpc_resolve_address(request->host, req->use_ssl ? "https" : "http", + on_resolved, req); +} diff --git a/src/core/httpcli/httpcli.h b/src/core/httpcli/httpcli.h new file mode 100644 index 0000000000..aef0edfdd4 --- /dev/null +++ b/src/core/httpcli/httpcli.h @@ -0,0 +1,104 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_HTTPCLI_HTTPCLI_H__ +#define __GRPC_INTERNAL_HTTPCLI_HTTPCLI_H__ + +#include <stddef.h> + +#include "src/core/eventmanager/em.h" +#include <grpc/support/time.h> + +/* User agent this library reports */ +#define GRPC_HTTPCLI_USER_AGENT "grpc-httpcli/0.0" +/* Maximum length of a header string of the form 'Key: Value\r\n' */ +#define GRPC_HTTPCLI_MAX_HEADER_LENGTH 4096 + +/* A single header to be passed in a request */ +typedef struct grpc_httpcli_header { + char *key; + char *value; +} grpc_httpcli_header; + +/* A request */ +typedef struct grpc_httpcli_request { + /* The host name to connect to */ + char *host; + /* The path of the resource to fetch */ + char *path; + /* Additional headers: count and key/values; the following are supplied + automatically and MUST NOT be set here: + Host, Connection, User-Agent */ + size_t hdr_count; + grpc_httpcli_header *hdrs; + /* whether to use ssl for the request */ + int use_ssl; +} grpc_httpcli_request; + +/* A response */ +typedef struct grpc_httpcli_response { + /* HTTP status code */ + int status; + /* Headers: count and key/values */ + size_t hdr_count; + grpc_httpcli_header *hdrs; + /* Body: length and contents; contents are NOT null-terminated */ + size_t body_length; + char *body; +} grpc_httpcli_response; + +/* Callback for grpc_httpcli_get */ +typedef void (*grpc_httpcli_response_cb)(void *user_data, + const grpc_httpcli_response *response); + +/* Asynchronously perform a HTTP GET. + '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) + '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) */ +void grpc_httpcli_get(const grpc_httpcli_request *request, + gpr_timespec deadline, grpc_em *em, + grpc_httpcli_response_cb on_response, void *user_data); + +/* Asynchronously perform a HTTP POST. + When there is no body, pass in NULL as body_bytes. + Does not support ?var1=val1&var2=val2 in the path. */ +void grpc_httpcli_post(const grpc_httpcli_request *request, + const char *body_bytes, size_t body_size, + gpr_timespec deadline, grpc_em *em, + grpc_httpcli_response_cb on_response, void *user_data); + +#endif /* __GRPC_INTERNAL_HTTPCLI_HTTPCLI_H__ */ diff --git a/src/core/httpcli/httpcli_security_context.c b/src/core/httpcli/httpcli_security_context.c new file mode 100644 index 0000000000..c7b9b330f0 --- /dev/null +++ b/src/core/httpcli/httpcli_security_context.c @@ -0,0 +1,128 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/httpcli/httpcli_security_context.h" + +#include <string.h> + +#include "src/core/security/secure_transport_setup.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string.h> +#include "src/core/tsi/ssl_transport_security.h" + +typedef struct { + grpc_channel_security_context base; + tsi_ssl_handshaker_factory *handshaker_factory; + char *secure_peer_name; +} grpc_httpcli_ssl_channel_security_context; + +static void httpcli_ssl_destroy(grpc_security_context *ctx) { + grpc_httpcli_ssl_channel_security_context *c = + (grpc_httpcli_ssl_channel_security_context *)ctx; + 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(ctx); +} + +static grpc_security_status httpcli_ssl_create_handshaker( + grpc_security_context *ctx, tsi_handshaker **handshaker) { + grpc_httpcli_ssl_channel_security_context *c = + (grpc_httpcli_ssl_channel_security_context *)ctx; + tsi_result result = TSI_OK; + if (c->handshaker_factory == NULL) return GRPC_SECURITY_ERROR; + 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)); + return GRPC_SECURITY_ERROR; + } + return GRPC_SECURITY_OK; +} + +static grpc_security_status httpcli_ssl_check_peer( + grpc_security_context *ctx, const tsi_peer *peer, + grpc_security_check_peer_cb cb, void *user_data) { + grpc_httpcli_ssl_channel_security_context *c = + (grpc_httpcli_ssl_channel_security_context *)ctx; + + /* 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); + return GRPC_SECURITY_ERROR; + } + return GRPC_SECURITY_OK; +} + +static grpc_security_context_vtable httpcli_ssl_vtable = { + httpcli_ssl_destroy, httpcli_ssl_create_handshaker, httpcli_ssl_check_peer}; + +grpc_security_status grpc_httpcli_ssl_channel_security_context_create( + const unsigned char *pem_root_certs, size_t pem_root_certs_size, + const char *secure_peer_name, grpc_channel_security_context **ctx) { + tsi_result result = TSI_OK; + grpc_httpcli_ssl_channel_security_context *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_context)); + memset(c, 0, sizeof(grpc_httpcli_ssl_channel_security_context)); + + gpr_ref_init(&c->base.base.refcount, 1); + c->base.base.is_client_side = 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); + *ctx = NULL; + return GRPC_SECURITY_ERROR; + } + *ctx = &c->base; + return GRPC_SECURITY_OK; +} diff --git a/src/core/httpcli/httpcli_security_context.h b/src/core/httpcli/httpcli_security_context.h new file mode 100644 index 0000000000..c267622e60 --- /dev/null +++ b/src/core/httpcli/httpcli_security_context.h @@ -0,0 +1,43 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_HTTPCLI_HTTPCLI_SECURITY_CONTEXT_H__ +#define __GRPC_INTERNAL_HTTPCLI_HTTPCLI_SECURITY_CONTEXT_H__ + +#include "src/core/security/security_context.h" + +grpc_security_status grpc_httpcli_ssl_channel_security_context_create( + const unsigned char *pem_root_certs, size_t pem_root_certs_size, + const char *secure_peer_name, grpc_channel_security_context **ctx); + +#endif /* __GRPC_INTERNAL_HTTPCLI_HTTPCLI_SECURITY_CONTEXT_H__ */ diff --git a/src/core/httpcli/parser.c b/src/core/httpcli/parser.c new file mode 100644 index 0000000000..1f0c5167de --- /dev/null +++ b/src/core/httpcli/parser.c @@ -0,0 +1,212 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/httpcli/parser.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/useful.h> + +static int handle_response_line(grpc_httpcli_parser *parser) { + gpr_uint8 *beg = parser->cur_line; + gpr_uint8 *cur = beg; + gpr_uint8 *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->r.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 char *buf2str(void *buffer, size_t length) { + char *out = gpr_malloc(length + 1); + memcpy(out, buffer, length); + out[length] = 0; + return out; +} + +static int add_header(grpc_httpcli_parser *parser) { + gpr_uint8 *beg = parser->cur_line; + gpr_uint8 *cur = beg; + gpr_uint8 *end = beg + parser->cur_line_length; + grpc_httpcli_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; + } + hdr.key = buf2str(beg, cur - beg); + cur++; /* skip : */ + + while (cur != end && (*cur == ' ' || *cur == '\t')) { + cur++; + } + hdr.value = buf2str(cur, end - cur - 2); + + if (parser->r.hdr_count == parser->hdr_capacity) { + parser->hdr_capacity = + GPR_MAX(parser->hdr_capacity + 1, parser->hdr_capacity * 3 / 2); + parser->r.hdrs = gpr_realloc( + parser->r.hdrs, parser->hdr_capacity * sizeof(*parser->r.hdrs)); + } + parser->r.hdrs[parser->r.hdr_count++] = hdr; + return 1; + +error: + gpr_free(hdr.key); + gpr_free(hdr.value); + return 0; +} + +static int finish_line(grpc_httpcli_parser *parser) { + switch (parser->state) { + case GRPC_HTTPCLI_INITIAL_RESPONSE: + if (!handle_response_line(parser)) { + return 0; + } + parser->state = GRPC_HTTPCLI_HEADERS; + break; + case GRPC_HTTPCLI_HEADERS: + if (parser->cur_line_length == 2) { + parser->state = GRPC_HTTPCLI_BODY; + break; + } + if (!add_header(parser)) { + return 0; + } + break; + case GRPC_HTTPCLI_BODY: + gpr_log(GPR_ERROR, "should never reach here"); + abort(); + } + + parser->cur_line_length = 0; + return 1; +} + +static int addbyte(grpc_httpcli_parser *parser, gpr_uint8 byte) { + switch (parser->state) { + case GRPC_HTTPCLI_INITIAL_RESPONSE: + case GRPC_HTTPCLI_HEADERS: + if (parser->cur_line_length >= GRPC_HTTPCLI_MAX_HEADER_LENGTH) { + gpr_log(GPR_ERROR, "HTTP client max line length (%d) exceeded", + GRPC_HTTPCLI_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_log(GPR_ERROR, "should never reach here"); + abort(); + case GRPC_HTTPCLI_BODY: + if (parser->r.body_length == parser->body_capacity) { + parser->body_capacity = GPR_MAX(8, parser->body_capacity * 3 / 2); + parser->r.body = + gpr_realloc((void *)parser->r.body, parser->body_capacity); + } + ((char *)parser->r.body)[parser->r.body_length] = byte; + parser->r.body_length++; + return 1; + } + gpr_log(GPR_ERROR, "should never reach here"); + abort(); +} + +void grpc_httpcli_parser_init(grpc_httpcli_parser *parser) { + memset(parser, 0, sizeof(*parser)); + parser->state = GRPC_HTTPCLI_INITIAL_RESPONSE; + parser->r.status = 500; +} + +void grpc_httpcli_parser_destroy(grpc_httpcli_parser *parser) { + size_t i; + gpr_free(parser->r.body); + for (i = 0; i < parser->r.hdr_count; i++) { + gpr_free(parser->r.hdrs[i].key); + gpr_free(parser->r.hdrs[i].value); + } + gpr_free(parser->r.hdrs); +} + +int grpc_httpcli_parser_parse(grpc_httpcli_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_httpcli_parser_eof(grpc_httpcli_parser *parser) { + return parser->state == GRPC_HTTPCLI_BODY; +} diff --git a/src/core/httpcli/parser.h b/src/core/httpcli/parser.h new file mode 100644 index 0000000000..e2c24a9993 --- /dev/null +++ b/src/core/httpcli/parser.h @@ -0,0 +1,64 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_HTTPCLI_PARSER_H__ +#define __GRPC_INTERNAL_HTTPCLI_PARSER_H__ + +#include "src/core/httpcli/httpcli.h" +#include <grpc/support/port_platform.h> +#include <grpc/support/slice.h> + +typedef enum { + GRPC_HTTPCLI_INITIAL_RESPONSE, + GRPC_HTTPCLI_HEADERS, + GRPC_HTTPCLI_BODY +} grpc_httpcli_parser_state; + +typedef struct { + grpc_httpcli_parser_state state; + + grpc_httpcli_response r; + size_t body_capacity; + size_t hdr_capacity; + + gpr_uint8 cur_line[GRPC_HTTPCLI_MAX_HEADER_LENGTH]; + size_t cur_line_length; +} grpc_httpcli_parser; + +void grpc_httpcli_parser_init(grpc_httpcli_parser *parser); +void grpc_httpcli_parser_destroy(grpc_httpcli_parser *parser); + +int grpc_httpcli_parser_parse(grpc_httpcli_parser *parser, gpr_slice slice); +int grpc_httpcli_parser_eof(grpc_httpcli_parser *parser); + +#endif /* __GRPC_INTERNAL_HTTPCLI_PARSER_H__ */ diff --git a/src/core/security/auth.c b/src/core/security/auth.c new file mode 100644 index 0000000000..6480dd2239 --- /dev/null +++ b/src/core/security/auth.c @@ -0,0 +1,160 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/security/auth.h" + +#include <string.h> + +#include "src/core/security/security_context.h" +#include "src/core/security/credentials.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +/* We can have a per-call credentials. */ +typedef struct { + grpc_credentials *creds; + grpc_call_op op; +} call_data; + +/* We can have a per-channel credentials. */ +typedef struct { + grpc_channel_security_context *security_context; +} channel_data; + +static void on_credentials_metadata(void *user_data, grpc_mdelem **md_elems, + size_t num_md, + grpc_credentials_status status) { + grpc_call_element *elem = (grpc_call_element *)user_data; + size_t i; + for (i = 0; i < num_md; i++) { + grpc_call_element_send_metadata(elem, md_elems[i]); + } + grpc_call_next_op(elem, &((call_data *)elem->call_data)->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 call_op(grpc_call_element *elem, grpc_call_op *op) { + /* grab pointers to our data from the call element */ + call_data *calld = elem->call_data; + channel_data *channeld = elem->channel_data; + + switch (op->type) { + case GRPC_SEND_START: { + grpc_credentials *channel_creds = + channeld->security_context->request_metadata_only_creds; + /* TODO(jboeuf): + Decide on the policy in this case: + - populate both channel and call? + - the call takes precedence over the channel? + - leave this decision up to the channel credentials? */ + if (calld->creds != NULL) { + gpr_log(GPR_ERROR, "Ignoring per call credentials for now."); + } + if (channel_creds != NULL && + grpc_credentials_has_request_metadata(channel_creds)) { + calld->op = *op; /* Copy op (originates from the caller's stack). */ + grpc_credentials_get_request_metadata(channel_creds, + on_credentials_metadata, elem); + break; + } + /* FALLTHROUGH INTENDED. */ + } + + default: + /* pass control up or down the stack depending on op->dir */ + grpc_call_next_op(elem, op); + break; + } +} + +/* Called on special channel events, such as disconnection or new incoming + calls on the server */ +static void channel_op(grpc_channel_element *elem, grpc_channel_op *op) { + grpc_channel_next_op(elem, op); +} + +/* Constructor for call_data */ +static void init_call_elem(grpc_call_element *elem, + const void *server_transport_data) { + /* TODO(jboeuf): + Find a way to pass-in the credentials from the caller here. */ + call_data *calld = elem->call_data; + calld->creds = NULL; +} + +/* Destructor for call_data */ +static void destroy_call_elem(grpc_call_element *elem) { + call_data *calld = elem->call_data; + if (calld->creds != NULL) { + grpc_credentials_unref(calld->creds); + } +} + +/* Constructor for channel_data */ +static void init_channel_elem(grpc_channel_element *elem, + const grpc_channel_args *args, + grpc_mdctx *metadata_context, int is_first, + int is_last) { + grpc_security_context *ctx = grpc_find_security_context_in_args(args); + /* grab pointers to our data from the channel element */ + channel_data *channeld = 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(!is_first); + GPR_ASSERT(!is_last); + GPR_ASSERT(ctx != NULL); + + /* initialize members */ + GPR_ASSERT(ctx->is_client_side); + channeld->security_context = + (grpc_channel_security_context *)grpc_security_context_ref(ctx); +} + +/* Destructor for channel data */ +static void destroy_channel_elem(grpc_channel_element *elem) { + /* grab pointers to our data from the channel element */ + channel_data *channeld = elem->channel_data; + grpc_channel_security_context *ctx = channeld->security_context; + if (ctx != NULL) grpc_security_context_unref(&ctx->base); +} + +const grpc_channel_filter grpc_client_auth_filter = { + call_op, channel_op, sizeof(call_data), + init_call_elem, destroy_call_elem, sizeof(channel_data), + init_channel_elem, destroy_channel_elem, "auth"}; diff --git a/src/core/security/auth.h b/src/core/security/auth.h new file mode 100644 index 0000000000..0b279f8740 --- /dev/null +++ b/src/core/security/auth.h @@ -0,0 +1,41 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_SECURITY_AUTH_H__ +#define __GRPC_INTERNAL_SECURITY_AUTH_H__ + +#include "src/core/channel/channel_stack.h" + +extern const grpc_channel_filter grpc_client_auth_filter; + +#endif /* __GRPC_INTERNAL_SECURITY_AUTH_H__ */ diff --git a/src/core/security/credentials.c b/src/core/security/credentials.c new file mode 100644 index 0000000000..969a97369c --- /dev/null +++ b/src/core/security/credentials.c @@ -0,0 +1,534 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/security/credentials.h" + +#include "src/core/httpcli/httpcli.h" +#include "src/core/surface/surface_em.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string.h> +#include <grpc/support/sync.h> +#include <grpc/support/time.h> + +#include "third_party/cJSON/cJSON.h" + +#include <string.h> +#include <stdio.h> + +/* -- Constants. -- */ + +#define GRPC_COMPUTE_ENGINE_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_AUTHORIZATION_METADATA_KEY "Authorization" + +/* -- Common. -- */ + +typedef struct { + grpc_credentials *creds; + grpc_credentials_metadata_cb cb; + void *user_data; +} grpc_credentials_metadata_request; + +static grpc_credentials_metadata_request * +grpc_credentials_metadata_request_create(grpc_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_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_credentials_unref(r->creds); + gpr_free(r); +} + +grpc_credentials *grpc_credentials_ref(grpc_credentials *creds) { + if (creds == NULL) return NULL; + gpr_ref(&creds->refcount); + return creds; +} + +void grpc_credentials_unref(grpc_credentials *creds) { + if (creds == NULL) return; + if (gpr_unref(&creds->refcount)) creds->vtable->destroy(creds); +} + +void grpc_credentials_release(grpc_credentials *creds) { + grpc_credentials_unref(creds); +} + +int grpc_credentials_has_request_metadata(grpc_credentials *creds) { + if (creds == NULL) return 0; + return creds->vtable->has_request_metadata(creds); +} + +int grpc_credentials_has_request_metadata_only(grpc_credentials *creds) { + if (creds == NULL) return 0; + return creds->vtable->has_request_metadata_only(creds); +} + +void grpc_credentials_get_request_metadata(grpc_credentials *creds, + grpc_credentials_metadata_cb cb, + void *user_data) { + if (creds == NULL || !grpc_credentials_has_request_metadata(creds)) return; + creds->vtable->get_request_metadata(creds, cb, user_data); +} + +void grpc_server_credentials_release(grpc_server_credentials *creds) { + if (creds == NULL) return; + creds->vtable->destroy(creds); +} + +/* -- Ssl credentials. -- */ + +typedef struct { + grpc_credentials base; + grpc_ssl_config config; +} grpc_ssl_credentials; + +typedef struct { + grpc_server_credentials base; + grpc_ssl_config config; +} grpc_ssl_server_credentials; + +static void ssl_destroy(grpc_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); + gpr_free(creds); +} + +static void ssl_server_destroy(grpc_server_credentials *creds) { + grpc_ssl_server_credentials *c = (grpc_ssl_server_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); + gpr_free(creds); +} + +static int ssl_has_request_metadata(const grpc_credentials *creds) { return 0; } + +static int ssl_has_request_metadata_only(const grpc_credentials *creds) { + return 0; +} + +static grpc_credentials_vtable ssl_vtable = { + ssl_destroy, ssl_has_request_metadata, ssl_has_request_metadata_only, NULL}; + +static grpc_server_credentials_vtable ssl_server_vtable = {ssl_server_destroy}; + +const grpc_ssl_config *grpc_ssl_credentials_get_config( + const grpc_credentials *creds) { + if (creds == NULL || strcmp(creds->type, GRPC_CREDENTIALS_TYPE_SSL)) { + return NULL; + } else { + grpc_ssl_credentials *c = (grpc_ssl_credentials *)creds; + return &c->config; + } +} + +const grpc_ssl_config *grpc_ssl_server_credentials_get_config( + const grpc_server_credentials *creds) { + if (creds == NULL || strcmp(creds->type, GRPC_CREDENTIALS_TYPE_SSL)) { + return NULL; + } else { + grpc_ssl_server_credentials *c = (grpc_ssl_server_credentials *)creds; + return &c->config; + } +} + +static void ssl_build_config(const unsigned char *pem_root_certs, + size_t pem_root_certs_size, + const unsigned char *pem_private_key, + size_t pem_private_key_size, + const unsigned char *pem_cert_chain, + size_t pem_cert_chain_size, + grpc_ssl_config *config) { + if (pem_root_certs != NULL) { + config->pem_root_certs = gpr_malloc(pem_root_certs_size); + memcpy(config->pem_root_certs, pem_root_certs, pem_root_certs_size); + config->pem_root_certs_size = pem_root_certs_size; + } + if (pem_private_key != NULL) { + config->pem_private_key = gpr_malloc(pem_private_key_size); + memcpy(config->pem_private_key, pem_private_key, pem_private_key_size); + config->pem_private_key_size = pem_private_key_size; + } + if (pem_cert_chain != NULL) { + config->pem_cert_chain = gpr_malloc(pem_cert_chain_size); + memcpy(config->pem_cert_chain, pem_cert_chain, pem_cert_chain_size); + config->pem_cert_chain_size = pem_cert_chain_size; + } +} + +grpc_credentials *grpc_ssl_credentials_create( + const unsigned char *pem_root_certs, size_t pem_root_certs_size, + const unsigned char *pem_private_key, size_t pem_private_key_size, + const unsigned char *pem_cert_chain, size_t pem_cert_chain_size) { + grpc_ssl_credentials *c = gpr_malloc(sizeof(grpc_ssl_credentials)); + memset(c, 0, sizeof(grpc_ssl_credentials)); + c->base.type = GRPC_CREDENTIALS_TYPE_SSL; + c->base.vtable = &ssl_vtable; + gpr_ref_init(&c->base.refcount, 1); + ssl_build_config(pem_root_certs, pem_root_certs_size, pem_private_key, + pem_private_key_size, pem_cert_chain, pem_cert_chain_size, + &c->config); + return &c->base; +} + +grpc_server_credentials *grpc_ssl_server_credentials_create( + const unsigned char *pem_root_certs, size_t pem_root_certs_size, + const unsigned char *pem_private_key, size_t pem_private_key_size, + const unsigned char *pem_cert_chain, size_t pem_cert_chain_size) { + grpc_ssl_server_credentials *c = + gpr_malloc(sizeof(grpc_ssl_server_credentials)); + memset(c, 0, sizeof(grpc_ssl_server_credentials)); + c->base.type = GRPC_CREDENTIALS_TYPE_SSL; + c->base.vtable = &ssl_server_vtable; + ssl_build_config(pem_root_certs, pem_root_certs_size, pem_private_key, + pem_private_key_size, pem_cert_chain, pem_cert_chain_size, + &c->config); + return &c->base; +} + +/* -- ComputeEngine credentials. -- */ + +typedef struct { + grpc_credentials base; + gpr_mu mu; + grpc_mdctx *md_ctx; + grpc_mdelem *access_token_md; + gpr_timespec token_expiration; +} grpc_compute_engine_credentials; + +static void compute_engine_destroy(grpc_credentials *creds) { + grpc_compute_engine_credentials *c = (grpc_compute_engine_credentials *)creds; + if (c->access_token_md != NULL) { + grpc_mdelem_unref(c->access_token_md); + } + gpr_mu_destroy(&c->mu); + grpc_mdctx_orphan(c->md_ctx); + gpr_free(c); +} + +static int compute_engine_has_request_metadata(const grpc_credentials *creds) { + return 1; +} + +static int compute_engine_has_request_metadata_only( + const grpc_credentials *creds) { + return 1; +} + +grpc_credentials_status grpc_compute_engine_credentials_parse_server_response( + const grpc_httpcli_response *response, grpc_mdctx *ctx, + grpc_mdelem **token_elem, gpr_timespec *token_lifetime) { + char *null_terminated_body = NULL; + char *new_access_token = NULL; + grpc_credentials_status status = GRPC_CREDENTIALS_OK; + cJSON *json = NULL; + + if (response->status != 200) { + gpr_log(GPR_ERROR, "Call to metadata server ended with error %d", + response->status); + status = GRPC_CREDENTIALS_ERROR; + goto end; + } else { + cJSON *access_token = NULL; + cJSON *token_type = NULL; + cJSON *expires_in = NULL; + size_t new_access_token_size = 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); + json = cJSON_Parse(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 != cJSON_Object) { + gpr_log(GPR_ERROR, "Response should be a JSON object"); + status = GRPC_CREDENTIALS_ERROR; + goto end; + } + access_token = cJSON_GetObjectItem(json, "access_token"); + if (access_token == NULL || access_token->type != cJSON_String) { + gpr_log(GPR_ERROR, "Missing or invalid access_token in JSON."); + status = GRPC_CREDENTIALS_ERROR; + goto end; + } + token_type = cJSON_GetObjectItem(json, "token_type"); + if (token_type == NULL || token_type->type != cJSON_String) { + gpr_log(GPR_ERROR, "Missing or invalid token_type in JSON."); + status = GRPC_CREDENTIALS_ERROR; + goto end; + } + expires_in = cJSON_GetObjectItem(json, "expires_in"); + if (expires_in == NULL || expires_in->type != cJSON_Number) { + gpr_log(GPR_ERROR, "Missing or invalid expires_in in JSON."); + status = GRPC_CREDENTIALS_ERROR; + goto end; + } + new_access_token_size = strlen(token_type->valuestring) + 1 + + strlen(access_token->valuestring) + 1; + new_access_token = gpr_malloc(new_access_token_size); + /* C89 does not have snprintf :(. */ + sprintf(new_access_token, "%s %s", token_type->valuestring, + access_token->valuestring); + token_lifetime->tv_sec = expires_in->valueint; + token_lifetime->tv_nsec = 0; + if (*token_elem != NULL) grpc_mdelem_unref(*token_elem); + *token_elem = grpc_mdelem_from_strings(ctx, GRPC_AUTHORIZATION_METADATA_KEY, + new_access_token); + status = GRPC_CREDENTIALS_OK; + } + +end: + if (status != GRPC_CREDENTIALS_OK && (*token_elem != NULL)) { + grpc_mdelem_unref(*token_elem); + *token_elem = NULL; + } + if (null_terminated_body != NULL) gpr_free(null_terminated_body); + if (new_access_token != NULL) gpr_free(new_access_token); + if (json != NULL) cJSON_Delete(json); + return status; +} + +static void on_compute_engine_token_response( + void *user_data, const grpc_httpcli_response *response) { + grpc_credentials_metadata_request *r = + (grpc_credentials_metadata_request *)user_data; + grpc_compute_engine_credentials *c = + (grpc_compute_engine_credentials *)r->creds; + gpr_timespec token_lifetime; + grpc_credentials_status status; + + gpr_mu_lock(&c->mu); + status = grpc_compute_engine_credentials_parse_server_response( + response, c->md_ctx, &c->access_token_md, &token_lifetime); + if (status == GRPC_CREDENTIALS_OK) { + c->token_expiration = gpr_time_add(gpr_now(), token_lifetime); + r->cb(r->user_data, &c->access_token_md, 1, status); + } else { + c->token_expiration = gpr_inf_past; + r->cb(r->user_data, NULL, 0, status); + } + gpr_mu_unlock(&c->mu); + grpc_credentials_metadata_request_destroy(r); +} + +static void compute_engine_get_request_metadata(grpc_credentials *creds, + grpc_credentials_metadata_cb cb, + void *user_data) { + grpc_compute_engine_credentials *c = (grpc_compute_engine_credentials *)creds; + gpr_timespec refresh_threshold = { + GRPC_COMPUTE_ENGINE_TOKEN_REFRESH_THRESHOLD_SECS, 0}; + + gpr_mu_lock(&c->mu); + if (c->access_token_md == NULL || + (gpr_time_cmp(gpr_time_sub(gpr_now(), c->token_expiration), + refresh_threshold) < 0)) { + grpc_httpcli_header header = {"Metadata-Flavor", "Google"}; + grpc_httpcli_request request; + request.host = GRPC_COMPUTE_ENGINE_METADATA_HOST; + request.path = GRPC_COMPUTE_ENGINE_METADATA_TOKEN_PATH; + request.hdr_count = 1; + request.hdrs = &header; + grpc_httpcli_get( + &request, gpr_time_add(gpr_now(), refresh_threshold), grpc_surface_em(), + on_compute_engine_token_response, + grpc_credentials_metadata_request_create(creds, cb, user_data)); + } else { + cb(user_data, &c->access_token_md, 1, GRPC_CREDENTIALS_OK); + } + gpr_mu_unlock(&c->mu); +} + +static grpc_credentials_vtable compute_engine_vtable = { + compute_engine_destroy, compute_engine_has_request_metadata, + compute_engine_has_request_metadata_only, + compute_engine_get_request_metadata}; + +grpc_credentials *grpc_compute_engine_credentials_create(void) { + grpc_compute_engine_credentials *c = + gpr_malloc(sizeof(grpc_compute_engine_credentials)); + memset(c, 0, sizeof(grpc_compute_engine_credentials)); + c->base.type = GRPC_CREDENTIALS_TYPE_OAUTH2; + c->base.vtable = &compute_engine_vtable; + gpr_ref_init(&c->base.refcount, 1); + gpr_mu_init(&c->mu); + c->md_ctx = grpc_mdctx_create(); + c->token_expiration = gpr_inf_past; + return &c->base; +} + +/* -- Fake Oauth2 credentials. -- */ + +typedef struct { + grpc_credentials base; + grpc_mdctx *md_ctx; + grpc_mdelem *access_token_md; + int is_async; +} grpc_fake_oauth2_credentials; + +static void fake_oauth2_destroy(grpc_credentials *creds) { + grpc_fake_oauth2_credentials *c = (grpc_fake_oauth2_credentials *)creds; + if (c->access_token_md != NULL) { + grpc_mdelem_unref(c->access_token_md); + } + grpc_mdctx_orphan(c->md_ctx); + gpr_free(c); +} + +static int fake_oauth2_has_request_metadata(const grpc_credentials *creds) { + return 1; +} + +static int fake_oauth2_has_request_metadata_only( + const grpc_credentials *creds) { + return 1; +} + +void on_simulated_token_fetch_done(void *user_data, grpc_em_cb_status status) { + grpc_credentials_metadata_request *r = + (grpc_credentials_metadata_request *)user_data; + grpc_fake_oauth2_credentials *c = (grpc_fake_oauth2_credentials *)r->creds; + GPR_ASSERT(status == GRPC_CALLBACK_SUCCESS); + r->cb(r->user_data, &c->access_token_md, 1, GRPC_CREDENTIALS_OK); + grpc_credentials_metadata_request_destroy(r); +} + +static void fake_oauth2_get_request_metadata(grpc_credentials *creds, + grpc_credentials_metadata_cb cb, + void *user_data) { + grpc_fake_oauth2_credentials *c = (grpc_fake_oauth2_credentials *)creds; + + if (c->is_async) { + GPR_ASSERT(grpc_em_add_callback(grpc_surface_em(), + on_simulated_token_fetch_done, + grpc_credentials_metadata_request_create( + creds, cb, user_data)) == GRPC_EM_OK); + } else { + cb(user_data, &c->access_token_md, 1, GRPC_CREDENTIALS_OK); + } +} + +static grpc_credentials_vtable fake_oauth2_vtable = { + fake_oauth2_destroy, fake_oauth2_has_request_metadata, + fake_oauth2_has_request_metadata_only, fake_oauth2_get_request_metadata}; + +grpc_credentials *grpc_fake_oauth2_credentials_create( + const char *token_md_value, int is_async) { + grpc_fake_oauth2_credentials *c = + gpr_malloc(sizeof(grpc_fake_oauth2_credentials)); + memset(c, 0, sizeof(grpc_fake_oauth2_credentials)); + c->base.type = GRPC_CREDENTIALS_TYPE_OAUTH2; + c->base.vtable = &fake_oauth2_vtable; + gpr_ref_init(&c->base.refcount, 1); + c->md_ctx = grpc_mdctx_create(); + c->access_token_md = grpc_mdelem_from_strings( + c->md_ctx, GRPC_AUTHORIZATION_METADATA_KEY, token_md_value); + c->is_async = is_async; + return &c->base; +} + +/* -- Fake transport security credentials. -- */ + +static void fake_transport_security_credentials_destroy( + grpc_credentials *creds) { + gpr_free(creds); +} + +static void fake_transport_security_server_credentials_destroy( + grpc_server_credentials *creds) { + gpr_free(creds); +} + +static int fake_transport_security_has_request_metadata( + const grpc_credentials *creds) { + return 0; +} + +static int fake_transport_security_has_request_metadata_only( + const grpc_credentials *creds) { + return 0; +} + +static grpc_credentials_vtable fake_transport_security_credentials_vtable = { + fake_transport_security_credentials_destroy, + fake_transport_security_has_request_metadata, + fake_transport_security_has_request_metadata_only, NULL}; + +static grpc_server_credentials_vtable + fake_transport_security_server_credentials_vtable = { + fake_transport_security_server_credentials_destroy}; + +grpc_credentials *grpc_fake_transport_security_credentials_create(void) { + grpc_credentials *c = gpr_malloc(sizeof(grpc_credentials)); + memset(c, 0, sizeof(grpc_credentials)); + c->type = GRPC_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() { + grpc_server_credentials *c = gpr_malloc(sizeof(grpc_server_credentials)); + memset(c, 0, sizeof(grpc_server_credentials)); + c->type = GRPC_CREDENTIALS_TYPE_FAKE_TRANSPORT_SECURITY; + c->vtable = &fake_transport_security_server_credentials_vtable; + return c; +} + + +/* -- Composite credentials TODO(jboeuf). -- */ + +grpc_credentials *grpc_composite_credentials_create(grpc_credentials *creds1, + grpc_credentials *creds2) { + return NULL; +} + +/* -- Default credentials TODO(jboeuf). -- */ + +grpc_credentials *grpc_default_credentials_create(void) { return NULL; } diff --git a/src/core/security/credentials.h b/src/core/security/credentials.h new file mode 100644 index 0000000000..1432611ec6 --- /dev/null +++ b/src/core/security/credentials.h @@ -0,0 +1,125 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_SECURITY_CREDENTIALS_H__ +#define __GRPC_INTERNAL_SECURITY_CREDENTIALS_H__ + +#include "src/core/transport/stream_op.h" +#include <grpc/grpc.h> +#include <grpc/grpc_security.h> +#include <grpc/support/sync.h> + +struct grpc_httpcli_response; + +/* --- Constants. --- */ + +typedef enum { + GRPC_CREDENTIALS_OK = 0, + GRPC_CREDENTIALS_ERROR +} grpc_credentials_status; + +#define GRPC_CREDENTIALS_TYPE_SSL "Ssl" +#define GRPC_CREDENTIALS_TYPE_OAUTH2 "Oauth2" +#define GRPC_CREDENTIALS_TYPE_COMPOSITE "Composite" +#define GRPC_CREDENTIALS_TYPE_FAKE_TRANSPORT_SECURITY "FakeTransportSecurity" + +/* --- grpc_credentials. --- */ + +typedef void (*grpc_credentials_metadata_cb)(void *user_data, + grpc_mdelem **md_elems, + size_t num_md, + grpc_credentials_status status); + +typedef struct { + void (*destroy)(grpc_credentials *c); + int (*has_request_metadata)(const grpc_credentials *c); + int (*has_request_metadata_only)(const grpc_credentials *c); + void (*get_request_metadata)(grpc_credentials *c, + grpc_credentials_metadata_cb cb, + void *user_data); +} grpc_credentials_vtable; + +struct grpc_credentials { + const grpc_credentials_vtable *vtable; + const char *type; + gpr_refcount refcount; +}; + +grpc_credentials *grpc_credentials_ref(grpc_credentials *creds); +void grpc_credentials_unref(grpc_credentials *creds); +int grpc_credentials_has_request_metadata(grpc_credentials *creds); +int grpc_credentials_has_request_metadata_only(grpc_credentials *creds); +void grpc_credentials_get_request_metadata(grpc_credentials *creds, + grpc_credentials_metadata_cb cb, + void *user_data); +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; + +const grpc_ssl_config *grpc_ssl_credentials_get_config( + const grpc_credentials *ssl_creds); + +/* Exposed for testing only. */ +grpc_credentials_status grpc_compute_engine_credentials_parse_server_response( + const struct grpc_httpcli_response *response, grpc_mdctx *ctx, + grpc_mdelem **token_elem, gpr_timespec *token_lifetime); + +/* Simulates an oauth2 token fetch with the specified value for testing. */ +grpc_credentials *grpc_fake_oauth2_credentials_create( + const char *token_md_value, int is_async); + + +/* --- grpc_server_credentials. --- */ + +typedef struct { + void (*destroy)(grpc_server_credentials *c); +} grpc_server_credentials_vtable; + +struct grpc_server_credentials { + const grpc_server_credentials_vtable *vtable; + const char *type; +}; + +/* TODO(jboeuf): Have an ssl_server_config that can contain multiple key/cert + pairs. */ + +const grpc_ssl_config *grpc_ssl_server_credentials_get_config( + const grpc_server_credentials *ssl_creds); + + +#endif /* __GRPC_INTERNAL_SECURITY_CREDENTIALS_H__ */ diff --git a/src/core/security/google_root_certs.c b/src/core/security/google_root_certs.c new file mode 100644 index 0000000000..669d637ddf --- /dev/null +++ b/src/core/security/google_root_certs.c @@ -0,0 +1,11277 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 created by + xxd -i security/cacerts/for_connecting_to_google/roots.pem */ + +unsigned char grpc_google_root_certs[] = { + 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, + 0x3d, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, + 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, + 0x6f, 0x6f, 0x74, 0x20, 0x4f, 0x3d, 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, + 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x4f, 0x55, + 0x3d, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, + 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x53, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x47, 0x54, + 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, + 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, + 0x20, 0x4f, 0x3d, 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x4f, 0x55, 0x3d, 0x47, 0x54, + 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, + 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2c, 0x20, + 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, + 0x3a, 0x20, 0x22, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, + 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, + 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, + 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x34, 0x32, 0x31, 0x0a, 0x23, 0x20, 0x4d, + 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x3a, 0x20, 0x63, 0x61, 0x3a, 0x33, 0x64, 0x3a, 0x64, 0x33, + 0x3a, 0x36, 0x38, 0x3a, 0x66, 0x31, 0x3a, 0x30, 0x33, 0x3a, 0x35, 0x63, + 0x3a, 0x64, 0x30, 0x3a, 0x33, 0x32, 0x3a, 0x66, 0x61, 0x3a, 0x62, 0x38, + 0x3a, 0x32, 0x62, 0x3a, 0x35, 0x39, 0x3a, 0x65, 0x38, 0x3a, 0x35, 0x61, + 0x3a, 0x64, 0x62, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x39, 0x37, 0x3a, 0x38, 0x31, 0x3a, 0x37, 0x39, 0x3a, 0x35, 0x30, 0x3a, + 0x64, 0x38, 0x3a, 0x31, 0x63, 0x3a, 0x39, 0x36, 0x3a, 0x37, 0x30, 0x3a, + 0x63, 0x63, 0x3a, 0x33, 0x34, 0x3a, 0x64, 0x38, 0x3a, 0x30, 0x39, 0x3a, + 0x63, 0x66, 0x3a, 0x37, 0x39, 0x3a, 0x34, 0x34, 0x3a, 0x33, 0x31, 0x3a, + 0x33, 0x36, 0x3a, 0x37, 0x65, 0x3a, 0x66, 0x34, 0x3a, 0x37, 0x34, 0x0a, + 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, + 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x61, 0x35, + 0x3a, 0x33, 0x31, 0x3a, 0x32, 0x35, 0x3a, 0x31, 0x38, 0x3a, 0x38, 0x64, + 0x3a, 0x32, 0x31, 0x3a, 0x31, 0x30, 0x3a, 0x61, 0x61, 0x3a, 0x39, 0x36, + 0x3a, 0x34, 0x62, 0x3a, 0x30, 0x32, 0x3a, 0x63, 0x37, 0x3a, 0x62, 0x37, + 0x3a, 0x63, 0x36, 0x3a, 0x64, 0x61, 0x3a, 0x33, 0x32, 0x3a, 0x30, 0x33, + 0x3a, 0x31, 0x37, 0x3a, 0x30, 0x38, 0x3a, 0x39, 0x34, 0x3a, 0x65, 0x35, + 0x3a, 0x66, 0x62, 0x3a, 0x37, 0x31, 0x3a, 0x66, 0x66, 0x3a, 0x66, 0x62, + 0x3a, 0x36, 0x36, 0x3a, 0x36, 0x37, 0x3a, 0x64, 0x35, 0x3a, 0x65, 0x36, + 0x3a, 0x38, 0x31, 0x3a, 0x30, 0x61, 0x3a, 0x33, 0x36, 0x0a, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, + 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x43, 0x57, 0x6a, 0x43, 0x43, 0x41, 0x63, + 0x4d, 0x43, 0x41, 0x67, 0x47, 0x6c, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, + 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x42, 0x41, + 0x55, 0x41, 0x4d, 0x48, 0x55, 0x78, 0x43, 0x7a, 0x41, 0x4a, 0x42, 0x67, + 0x4e, 0x56, 0x42, 0x41, 0x59, 0x54, 0x41, 0x6c, 0x56, 0x54, 0x4d, 0x52, + 0x67, 0x77, 0x46, 0x67, 0x59, 0x44, 0x0a, 0x56, 0x51, 0x51, 0x4b, 0x45, + 0x77, 0x39, 0x48, 0x56, 0x45, 0x55, 0x67, 0x51, 0x32, 0x39, 0x79, 0x63, + 0x47, 0x39, 0x79, 0x59, 0x58, 0x52, 0x70, 0x62, 0x32, 0x34, 0x78, 0x4a, + 0x7a, 0x41, 0x6c, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, 0x48, + 0x6b, 0x64, 0x55, 0x52, 0x53, 0x42, 0x44, 0x65, 0x57, 0x4a, 0x6c, 0x63, + 0x6c, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x49, 0x46, 0x4e, 0x76, 0x0a, + 0x62, 0x48, 0x56, 0x30, 0x61, 0x57, 0x39, 0x75, 0x63, 0x79, 0x77, 0x67, + 0x53, 0x57, 0x35, 0x6a, 0x4c, 0x6a, 0x45, 0x6a, 0x4d, 0x43, 0x45, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x61, 0x52, 0x31, 0x52, 0x46, + 0x49, 0x45, 0x4e, 0x35, 0x59, 0x6d, 0x56, 0x79, 0x56, 0x48, 0x4a, 0x31, + 0x63, 0x33, 0x51, 0x67, 0x52, 0x32, 0x78, 0x76, 0x59, 0x6d, 0x46, 0x73, + 0x49, 0x46, 0x4a, 0x76, 0x0a, 0x62, 0x33, 0x51, 0x77, 0x48, 0x68, 0x63, + 0x4e, 0x4f, 0x54, 0x67, 0x77, 0x4f, 0x44, 0x45, 0x7a, 0x4d, 0x44, 0x41, + 0x79, 0x4f, 0x54, 0x41, 0x77, 0x57, 0x68, 0x63, 0x4e, 0x4d, 0x54, 0x67, + 0x77, 0x4f, 0x44, 0x45, 0x7a, 0x4d, 0x6a, 0x4d, 0x31, 0x4f, 0x54, 0x41, + 0x77, 0x57, 0x6a, 0x42, 0x31, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, + 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, 0x0a, 0x55, 0x7a, + 0x45, 0x59, 0x4d, 0x42, 0x59, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, + 0x4d, 0x50, 0x52, 0x31, 0x52, 0x46, 0x49, 0x45, 0x4e, 0x76, 0x63, 0x6e, + 0x42, 0x76, 0x63, 0x6d, 0x46, 0x30, 0x61, 0x57, 0x39, 0x75, 0x4d, 0x53, + 0x63, 0x77, 0x4a, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, 0x78, + 0x35, 0x48, 0x56, 0x45, 0x55, 0x67, 0x51, 0x33, 0x6c, 0x69, 0x5a, 0x58, + 0x4a, 0x55, 0x0a, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x54, 0x62, + 0x32, 0x78, 0x31, 0x64, 0x47, 0x6c, 0x76, 0x62, 0x6e, 0x4d, 0x73, 0x49, + 0x45, 0x6c, 0x75, 0x59, 0x79, 0x34, 0x78, 0x49, 0x7a, 0x41, 0x68, 0x42, + 0x67, 0x4e, 0x56, 0x42, 0x41, 0x4d, 0x54, 0x47, 0x6b, 0x64, 0x55, 0x52, + 0x53, 0x42, 0x44, 0x65, 0x57, 0x4a, 0x6c, 0x63, 0x6c, 0x52, 0x79, 0x64, + 0x58, 0x4e, 0x30, 0x49, 0x45, 0x64, 0x73, 0x0a, 0x62, 0x32, 0x4a, 0x68, + 0x62, 0x43, 0x42, 0x53, 0x62, 0x32, 0x39, 0x30, 0x4d, 0x49, 0x47, 0x66, + 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, + 0x44, 0x51, 0x45, 0x42, 0x41, 0x51, 0x55, 0x41, 0x41, 0x34, 0x47, 0x4e, + 0x41, 0x44, 0x43, 0x42, 0x69, 0x51, 0x4b, 0x42, 0x67, 0x51, 0x43, 0x56, + 0x44, 0x36, 0x43, 0x32, 0x38, 0x46, 0x43, 0x63, 0x36, 0x48, 0x72, 0x48, + 0x0a, 0x69, 0x4d, 0x33, 0x64, 0x46, 0x77, 0x34, 0x75, 0x73, 0x4a, 0x54, + 0x51, 0x47, 0x7a, 0x30, 0x4f, 0x39, 0x70, 0x54, 0x41, 0x69, 0x70, 0x54, + 0x48, 0x42, 0x73, 0x69, 0x51, 0x6c, 0x38, 0x69, 0x34, 0x5a, 0x42, 0x70, + 0x36, 0x66, 0x6d, 0x77, 0x38, 0x55, 0x2b, 0x45, 0x33, 0x4b, 0x48, 0x4e, + 0x67, 0x66, 0x37, 0x4b, 0x58, 0x55, 0x77, 0x65, 0x66, 0x55, 0x2f, 0x6c, + 0x74, 0x57, 0x4a, 0x54, 0x53, 0x0a, 0x72, 0x34, 0x31, 0x74, 0x69, 0x47, + 0x65, 0x41, 0x35, 0x75, 0x32, 0x79, 0x6c, 0x63, 0x39, 0x79, 0x4d, 0x63, + 0x71, 0x6c, 0x48, 0x48, 0x4b, 0x36, 0x58, 0x41, 0x4c, 0x6e, 0x5a, 0x45, + 0x4c, 0x6e, 0x2b, 0x61, 0x6b, 0x73, 0x31, 0x6a, 0x6f, 0x4e, 0x72, 0x49, + 0x31, 0x43, 0x71, 0x69, 0x51, 0x42, 0x4f, 0x65, 0x61, 0x63, 0x50, 0x77, + 0x47, 0x46, 0x56, 0x77, 0x31, 0x59, 0x68, 0x30, 0x58, 0x34, 0x0a, 0x30, + 0x34, 0x57, 0x71, 0x6b, 0x32, 0x6b, 0x6d, 0x68, 0x58, 0x42, 0x49, 0x67, + 0x44, 0x38, 0x53, 0x46, 0x63, 0x64, 0x35, 0x74, 0x42, 0x38, 0x46, 0x4c, + 0x7a, 0x74, 0x69, 0x6d, 0x51, 0x49, 0x44, 0x41, 0x51, 0x41, 0x42, 0x4d, + 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, + 0x51, 0x45, 0x42, 0x42, 0x41, 0x55, 0x41, 0x41, 0x34, 0x47, 0x42, 0x41, + 0x47, 0x33, 0x72, 0x0a, 0x47, 0x77, 0x6e, 0x70, 0x58, 0x74, 0x6c, 0x52, + 0x32, 0x32, 0x63, 0x69, 0x59, 0x61, 0x51, 0x71, 0x50, 0x45, 0x68, 0x33, + 0x34, 0x36, 0x42, 0x38, 0x70, 0x74, 0x35, 0x7a, 0x6f, 0x68, 0x51, 0x44, + 0x68, 0x54, 0x33, 0x37, 0x71, 0x77, 0x34, 0x77, 0x78, 0x59, 0x4d, 0x57, + 0x4d, 0x34, 0x45, 0x54, 0x43, 0x4a, 0x35, 0x37, 0x4e, 0x45, 0x37, 0x66, + 0x51, 0x4d, 0x68, 0x30, 0x31, 0x37, 0x6c, 0x39, 0x0a, 0x33, 0x50, 0x52, + 0x32, 0x56, 0x58, 0x32, 0x62, 0x59, 0x31, 0x51, 0x59, 0x36, 0x66, 0x44, + 0x71, 0x38, 0x31, 0x79, 0x78, 0x32, 0x59, 0x74, 0x43, 0x48, 0x72, 0x6e, + 0x41, 0x6c, 0x55, 0x36, 0x36, 0x2b, 0x74, 0x58, 0x69, 0x66, 0x50, 0x56, + 0x6f, 0x59, 0x62, 0x2b, 0x4f, 0x37, 0x41, 0x57, 0x58, 0x58, 0x31, 0x75, + 0x77, 0x31, 0x36, 0x4f, 0x46, 0x4e, 0x4d, 0x51, 0x6b, 0x70, 0x77, 0x30, + 0x50, 0x0a, 0x6c, 0x5a, 0x50, 0x76, 0x79, 0x35, 0x54, 0x59, 0x6e, 0x68, + 0x2b, 0x64, 0x58, 0x49, 0x56, 0x74, 0x78, 0x36, 0x71, 0x75, 0x54, 0x78, + 0x38, 0x69, 0x74, 0x63, 0x32, 0x56, 0x72, 0x62, 0x71, 0x6e, 0x7a, 0x50, + 0x6d, 0x72, 0x43, 0x33, 0x70, 0x2f, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, + 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, + 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x54, + 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x20, 0x43, 0x41, 0x20, 0x4f, 0x3d, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, + 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x74, 0x69, 0x6e, 0x67, 0x20, + 0x63, 0x63, 0x20, 0x4f, 0x55, 0x3d, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x20, 0x44, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, + 0x20, 0x43, 0x4e, 0x3d, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x20, 0x4f, 0x3d, 0x54, + 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, + 0x74, 0x69, 0x6e, 0x67, 0x20, 0x63, 0x63, 0x20, 0x4f, 0x55, 0x3d, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x44, 0x69, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, + 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, + 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x22, 0x0a, 0x23, + 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x31, 0x0a, 0x23, + 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, + 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x63, 0x35, 0x3a, 0x37, 0x30, 0x3a, + 0x63, 0x34, 0x3a, 0x61, 0x32, 0x3a, 0x65, 0x64, 0x3a, 0x35, 0x33, 0x3a, + 0x37, 0x38, 0x3a, 0x30, 0x63, 0x3a, 0x63, 0x38, 0x3a, 0x31, 0x30, 0x3a, + 0x35, 0x33, 0x3a, 0x38, 0x31, 0x3a, 0x36, 0x34, 0x3a, 0x63, 0x62, 0x3a, + 0x64, 0x30, 0x3a, 0x31, 0x64, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, + 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, + 0x3a, 0x20, 0x32, 0x33, 0x3a, 0x65, 0x35, 0x3a, 0x39, 0x34, 0x3a, 0x39, + 0x34, 0x3a, 0x35, 0x31, 0x3a, 0x39, 0x35, 0x3a, 0x66, 0x32, 0x3a, 0x34, + 0x31, 0x3a, 0x34, 0x38, 0x3a, 0x30, 0x33, 0x3a, 0x62, 0x34, 0x3a, 0x64, + 0x35, 0x3a, 0x36, 0x34, 0x3a, 0x64, 0x32, 0x3a, 0x61, 0x33, 0x3a, 0x61, + 0x33, 0x3a, 0x66, 0x35, 0x3a, 0x64, 0x38, 0x3a, 0x38, 0x62, 0x3a, 0x38, + 0x63, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x62, 0x34, 0x3a, 0x34, 0x31, 0x3a, 0x30, 0x62, 0x3a, 0x37, 0x33, 0x3a, + 0x65, 0x32, 0x3a, 0x65, 0x36, 0x3a, 0x65, 0x61, 0x3a, 0x63, 0x61, 0x3a, + 0x34, 0x37, 0x3a, 0x66, 0x62, 0x3a, 0x63, 0x34, 0x3a, 0x32, 0x66, 0x3a, + 0x38, 0x66, 0x3a, 0x61, 0x34, 0x3a, 0x30, 0x31, 0x3a, 0x38, 0x61, 0x3a, + 0x66, 0x34, 0x3a, 0x33, 0x38, 0x3a, 0x31, 0x64, 0x3a, 0x63, 0x35, 0x3a, + 0x34, 0x63, 0x3a, 0x66, 0x61, 0x3a, 0x61, 0x38, 0x3a, 0x34, 0x34, 0x3a, + 0x35, 0x30, 0x3a, 0x34, 0x36, 0x3a, 0x31, 0x65, 0x3a, 0x65, 0x64, 0x3a, + 0x30, 0x39, 0x3a, 0x34, 0x35, 0x3a, 0x34, 0x64, 0x3a, 0x65, 0x39, 0x0a, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, + 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x44, 0x45, 0x7a, 0x43, 0x43, + 0x41, 0x6e, 0x79, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x42, + 0x41, 0x54, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, + 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x51, 0x46, 0x41, 0x44, 0x43, 0x42, + 0x78, 0x44, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x42, 0x68, 0x4d, 0x43, 0x57, 0x6b, 0x45, 0x78, 0x0a, 0x46, 0x54, 0x41, + 0x54, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x67, 0x54, 0x44, 0x46, 0x64, + 0x6c, 0x63, 0x33, 0x52, 0x6c, 0x63, 0x6d, 0x34, 0x67, 0x51, 0x32, 0x46, + 0x77, 0x5a, 0x54, 0x45, 0x53, 0x4d, 0x42, 0x41, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x42, 0x78, 0x4d, 0x4a, 0x51, 0x32, 0x46, 0x77, 0x5a, 0x53, 0x42, + 0x55, 0x62, 0x33, 0x64, 0x75, 0x4d, 0x52, 0x30, 0x77, 0x47, 0x77, 0x59, + 0x44, 0x0a, 0x56, 0x51, 0x51, 0x4b, 0x45, 0x78, 0x52, 0x55, 0x61, 0x47, + 0x46, 0x33, 0x64, 0x47, 0x55, 0x67, 0x51, 0x32, 0x39, 0x75, 0x63, 0x33, + 0x56, 0x73, 0x64, 0x47, 0x6c, 0x75, 0x5a, 0x79, 0x42, 0x6a, 0x59, 0x7a, + 0x45, 0x6f, 0x4d, 0x43, 0x59, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x78, + 0x4d, 0x66, 0x51, 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, + 0x4e, 0x68, 0x64, 0x47, 0x6c, 0x76, 0x0a, 0x62, 0x69, 0x42, 0x54, 0x5a, + 0x58, 0x4a, 0x32, 0x61, 0x57, 0x4e, 0x6c, 0x63, 0x79, 0x42, 0x45, 0x61, + 0x58, 0x5a, 0x70, 0x63, 0x32, 0x6c, 0x76, 0x62, 0x6a, 0x45, 0x5a, 0x4d, + 0x42, 0x63, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x51, 0x56, + 0x47, 0x68, 0x68, 0x64, 0x33, 0x52, 0x6c, 0x49, 0x46, 0x4e, 0x6c, 0x63, + 0x6e, 0x5a, 0x6c, 0x63, 0x69, 0x42, 0x44, 0x51, 0x54, 0x45, 0x6d, 0x0a, + 0x4d, 0x43, 0x51, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, + 0x44, 0x51, 0x45, 0x4a, 0x41, 0x52, 0x59, 0x58, 0x63, 0x32, 0x56, 0x79, + 0x64, 0x6d, 0x56, 0x79, 0x4c, 0x57, 0x4e, 0x6c, 0x63, 0x6e, 0x52, 0x7a, + 0x51, 0x48, 0x52, 0x6f, 0x59, 0x58, 0x64, 0x30, 0x5a, 0x53, 0x35, 0x6a, + 0x62, 0x32, 0x30, 0x77, 0x48, 0x68, 0x63, 0x4e, 0x4f, 0x54, 0x59, 0x77, + 0x4f, 0x44, 0x41, 0x78, 0x0a, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x44, 0x41, + 0x77, 0x57, 0x68, 0x63, 0x4e, 0x4d, 0x6a, 0x41, 0x78, 0x4d, 0x6a, 0x4d, + 0x78, 0x4d, 0x6a, 0x4d, 0x31, 0x4f, 0x54, 0x55, 0x35, 0x57, 0x6a, 0x43, + 0x42, 0x78, 0x44, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x42, 0x68, 0x4d, 0x43, 0x57, 0x6b, 0x45, 0x78, 0x46, 0x54, 0x41, + 0x54, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x67, 0x54, 0x0a, 0x44, 0x46, + 0x64, 0x6c, 0x63, 0x33, 0x52, 0x6c, 0x63, 0x6d, 0x34, 0x67, 0x51, 0x32, + 0x46, 0x77, 0x5a, 0x54, 0x45, 0x53, 0x4d, 0x42, 0x41, 0x47, 0x41, 0x31, + 0x55, 0x45, 0x42, 0x78, 0x4d, 0x4a, 0x51, 0x32, 0x46, 0x77, 0x5a, 0x53, + 0x42, 0x55, 0x62, 0x33, 0x64, 0x75, 0x4d, 0x52, 0x30, 0x77, 0x47, 0x77, + 0x59, 0x44, 0x56, 0x51, 0x51, 0x4b, 0x45, 0x78, 0x52, 0x55, 0x61, 0x47, + 0x46, 0x33, 0x0a, 0x64, 0x47, 0x55, 0x67, 0x51, 0x32, 0x39, 0x75, 0x63, + 0x33, 0x56, 0x73, 0x64, 0x47, 0x6c, 0x75, 0x5a, 0x79, 0x42, 0x6a, 0x59, + 0x7a, 0x45, 0x6f, 0x4d, 0x43, 0x59, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, + 0x78, 0x4d, 0x66, 0x51, 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, + 0x57, 0x4e, 0x68, 0x64, 0x47, 0x6c, 0x76, 0x62, 0x69, 0x42, 0x54, 0x5a, + 0x58, 0x4a, 0x32, 0x61, 0x57, 0x4e, 0x6c, 0x0a, 0x63, 0x79, 0x42, 0x45, + 0x61, 0x58, 0x5a, 0x70, 0x63, 0x32, 0x6c, 0x76, 0x62, 0x6a, 0x45, 0x5a, + 0x4d, 0x42, 0x63, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x51, + 0x56, 0x47, 0x68, 0x68, 0x64, 0x33, 0x52, 0x6c, 0x49, 0x46, 0x4e, 0x6c, + 0x63, 0x6e, 0x5a, 0x6c, 0x63, 0x69, 0x42, 0x44, 0x51, 0x54, 0x45, 0x6d, + 0x4d, 0x43, 0x51, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, + 0x0a, 0x44, 0x51, 0x45, 0x4a, 0x41, 0x52, 0x59, 0x58, 0x63, 0x32, 0x56, + 0x79, 0x64, 0x6d, 0x56, 0x79, 0x4c, 0x57, 0x4e, 0x6c, 0x63, 0x6e, 0x52, + 0x7a, 0x51, 0x48, 0x52, 0x6f, 0x59, 0x58, 0x64, 0x30, 0x5a, 0x53, 0x35, + 0x6a, 0x62, 0x32, 0x30, 0x77, 0x67, 0x5a, 0x38, 0x77, 0x44, 0x51, 0x59, + 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, + 0x42, 0x42, 0x51, 0x41, 0x44, 0x0a, 0x67, 0x59, 0x30, 0x41, 0x4d, 0x49, + 0x47, 0x4a, 0x41, 0x6f, 0x47, 0x42, 0x41, 0x4e, 0x4f, 0x6b, 0x55, 0x47, + 0x37, 0x49, 0x2f, 0x31, 0x5a, 0x72, 0x35, 0x73, 0x39, 0x64, 0x74, 0x75, + 0x6f, 0x4d, 0x61, 0x48, 0x56, 0x48, 0x6f, 0x71, 0x72, 0x43, 0x32, 0x6f, + 0x51, 0x6c, 0x2f, 0x4b, 0x6a, 0x30, 0x52, 0x31, 0x48, 0x61, 0x68, 0x62, + 0x55, 0x67, 0x64, 0x4a, 0x53, 0x47, 0x48, 0x67, 0x39, 0x31, 0x0a, 0x79, + 0x65, 0x6b, 0x49, 0x59, 0x66, 0x55, 0x47, 0x62, 0x54, 0x42, 0x75, 0x46, + 0x52, 0x6b, 0x43, 0x36, 0x56, 0x4c, 0x41, 0x59, 0x74, 0x74, 0x4e, 0x6d, + 0x5a, 0x37, 0x69, 0x61, 0x67, 0x78, 0x45, 0x4f, 0x4d, 0x33, 0x2b, 0x76, + 0x75, 0x4e, 0x6b, 0x43, 0x58, 0x44, 0x46, 0x2f, 0x72, 0x46, 0x72, 0x4b, + 0x62, 0x59, 0x76, 0x53, 0x63, 0x67, 0x37, 0x31, 0x43, 0x63, 0x45, 0x4a, + 0x52, 0x43, 0x58, 0x0a, 0x4c, 0x2b, 0x65, 0x51, 0x62, 0x63, 0x41, 0x6f, + 0x51, 0x70, 0x6e, 0x58, 0x54, 0x45, 0x50, 0x65, 0x77, 0x2f, 0x55, 0x68, + 0x62, 0x56, 0x53, 0x66, 0x58, 0x63, 0x4e, 0x59, 0x34, 0x63, 0x44, 0x6b, + 0x32, 0x56, 0x75, 0x77, 0x75, 0x4e, 0x79, 0x30, 0x65, 0x39, 0x38, 0x32, + 0x4f, 0x73, 0x4b, 0x31, 0x5a, 0x69, 0x49, 0x53, 0x31, 0x6f, 0x63, 0x4e, + 0x41, 0x67, 0x4d, 0x42, 0x41, 0x41, 0x47, 0x6a, 0x0a, 0x45, 0x7a, 0x41, + 0x52, 0x4d, 0x41, 0x38, 0x47, 0x41, 0x31, 0x55, 0x64, 0x45, 0x77, 0x45, + 0x42, 0x2f, 0x77, 0x51, 0x46, 0x4d, 0x41, 0x4d, 0x42, 0x41, 0x66, 0x38, + 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, + 0x4e, 0x41, 0x51, 0x45, 0x45, 0x42, 0x51, 0x41, 0x44, 0x67, 0x59, 0x45, + 0x41, 0x42, 0x2f, 0x70, 0x4d, 0x61, 0x56, 0x7a, 0x37, 0x6c, 0x63, 0x78, + 0x47, 0x0a, 0x37, 0x6f, 0x57, 0x44, 0x54, 0x53, 0x45, 0x77, 0x6a, 0x73, + 0x72, 0x5a, 0x71, 0x47, 0x39, 0x4a, 0x47, 0x75, 0x62, 0x61, 0x55, 0x65, + 0x4e, 0x67, 0x63, 0x47, 0x79, 0x45, 0x59, 0x52, 0x47, 0x68, 0x47, 0x73, + 0x68, 0x49, 0x50, 0x6c, 0x6c, 0x44, 0x66, 0x55, 0x2b, 0x56, 0x50, 0x61, + 0x47, 0x4c, 0x74, 0x77, 0x74, 0x69, 0x6d, 0x48, 0x70, 0x31, 0x69, 0x74, + 0x32, 0x49, 0x54, 0x6b, 0x36, 0x65, 0x0a, 0x51, 0x4e, 0x75, 0x6f, 0x7a, + 0x44, 0x4a, 0x30, 0x75, 0x57, 0x38, 0x4e, 0x78, 0x75, 0x4f, 0x7a, 0x52, + 0x41, 0x76, 0x5a, 0x69, 0x6d, 0x2b, 0x61, 0x4b, 0x5a, 0x75, 0x5a, 0x47, + 0x43, 0x67, 0x37, 0x30, 0x65, 0x4e, 0x41, 0x4b, 0x4a, 0x70, 0x61, 0x50, + 0x4e, 0x57, 0x31, 0x35, 0x79, 0x41, 0x62, 0x69, 0x38, 0x71, 0x6b, 0x71, + 0x34, 0x33, 0x70, 0x55, 0x64, 0x6e, 0x69, 0x54, 0x43, 0x78, 0x5a, 0x0a, + 0x71, 0x64, 0x71, 0x35, 0x73, 0x6e, 0x55, 0x62, 0x39, 0x6b, 0x4c, 0x79, + 0x37, 0x38, 0x66, 0x79, 0x47, 0x50, 0x6d, 0x4a, 0x76, 0x4b, 0x50, 0x2f, + 0x69, 0x69, 0x4d, 0x75, 0x63, 0x45, 0x63, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, + 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, + 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, + 0x3d, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50, 0x72, 0x65, 0x6d, + 0x69, 0x75, 0x6d, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, + 0x41, 0x20, 0x4f, 0x3d, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x43, + 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x63, 0x63, + 0x20, 0x4f, 0x55, 0x3d, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x20, 0x44, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x0a, + 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, + 0x4e, 0x3d, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50, 0x72, 0x65, + 0x6d, 0x69, 0x75, 0x6d, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, + 0x43, 0x41, 0x20, 0x4f, 0x3d, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, + 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x63, + 0x63, 0x20, 0x4f, 0x55, 0x3d, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x20, 0x44, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x54, + 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50, 0x72, 0x65, 0x6d, 0x69, 0x75, + 0x6d, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x22, + 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x31, + 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x30, 0x36, 0x3a, 0x39, + 0x66, 0x3a, 0x36, 0x39, 0x3a, 0x37, 0x39, 0x3a, 0x31, 0x36, 0x3a, 0x36, + 0x36, 0x3a, 0x39, 0x30, 0x3a, 0x30, 0x32, 0x3a, 0x31, 0x62, 0x3a, 0x38, + 0x63, 0x3a, 0x38, 0x63, 0x3a, 0x61, 0x32, 0x3a, 0x63, 0x33, 0x3a, 0x30, + 0x37, 0x3a, 0x36, 0x66, 0x3a, 0x33, 0x61, 0x0a, 0x23, 0x20, 0x53, 0x48, + 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x3a, 0x20, 0x36, 0x32, 0x3a, 0x37, 0x66, 0x3a, 0x38, 0x64, + 0x3a, 0x37, 0x38, 0x3a, 0x32, 0x37, 0x3a, 0x36, 0x35, 0x3a, 0x36, 0x33, + 0x3a, 0x39, 0x39, 0x3a, 0x64, 0x32, 0x3a, 0x37, 0x64, 0x3a, 0x37, 0x66, + 0x3a, 0x39, 0x30, 0x3a, 0x34, 0x34, 0x3a, 0x63, 0x39, 0x3a, 0x66, 0x65, + 0x3a, 0x62, 0x33, 0x3a, 0x66, 0x33, 0x3a, 0x33, 0x65, 0x3a, 0x66, 0x61, + 0x3a, 0x39, 0x61, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, + 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, + 0x3a, 0x20, 0x61, 0x62, 0x3a, 0x37, 0x30, 0x3a, 0x33, 0x36, 0x3a, 0x33, + 0x36, 0x3a, 0x35, 0x63, 0x3a, 0x37, 0x31, 0x3a, 0x35, 0x34, 0x3a, 0x61, + 0x61, 0x3a, 0x32, 0x39, 0x3a, 0x63, 0x32, 0x3a, 0x63, 0x32, 0x3a, 0x39, + 0x66, 0x3a, 0x35, 0x64, 0x3a, 0x34, 0x31, 0x3a, 0x39, 0x31, 0x3a, 0x31, + 0x36, 0x3a, 0x33, 0x62, 0x3a, 0x31, 0x36, 0x3a, 0x32, 0x61, 0x3a, 0x32, + 0x32, 0x3a, 0x32, 0x35, 0x3a, 0x30, 0x31, 0x3a, 0x31, 0x33, 0x3a, 0x35, + 0x37, 0x3a, 0x64, 0x35, 0x3a, 0x36, 0x64, 0x3a, 0x30, 0x37, 0x3a, 0x66, + 0x66, 0x3a, 0x61, 0x37, 0x3a, 0x62, 0x63, 0x3a, 0x31, 0x66, 0x3a, 0x37, + 0x32, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, + 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x44, 0x4a, 0x7a, + 0x43, 0x43, 0x41, 0x70, 0x43, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, + 0x49, 0x42, 0x41, 0x54, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, + 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x51, 0x46, 0x41, 0x44, + 0x43, 0x42, 0x7a, 0x6a, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, + 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x57, 0x6b, 0x45, 0x78, 0x0a, 0x46, + 0x54, 0x41, 0x54, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x67, 0x54, 0x44, + 0x46, 0x64, 0x6c, 0x63, 0x33, 0x52, 0x6c, 0x63, 0x6d, 0x34, 0x67, 0x51, + 0x32, 0x46, 0x77, 0x5a, 0x54, 0x45, 0x53, 0x4d, 0x42, 0x41, 0x47, 0x41, + 0x31, 0x55, 0x45, 0x42, 0x78, 0x4d, 0x4a, 0x51, 0x32, 0x46, 0x77, 0x5a, + 0x53, 0x42, 0x55, 0x62, 0x33, 0x64, 0x75, 0x4d, 0x52, 0x30, 0x77, 0x47, + 0x77, 0x59, 0x44, 0x0a, 0x56, 0x51, 0x51, 0x4b, 0x45, 0x78, 0x52, 0x55, + 0x61, 0x47, 0x46, 0x33, 0x64, 0x47, 0x55, 0x67, 0x51, 0x32, 0x39, 0x75, + 0x63, 0x33, 0x56, 0x73, 0x64, 0x47, 0x6c, 0x75, 0x5a, 0x79, 0x42, 0x6a, + 0x59, 0x7a, 0x45, 0x6f, 0x4d, 0x43, 0x59, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x43, 0x78, 0x4d, 0x66, 0x51, 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, 0x6d, + 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, 0x6c, 0x76, 0x0a, 0x62, 0x69, 0x42, + 0x54, 0x5a, 0x58, 0x4a, 0x32, 0x61, 0x57, 0x4e, 0x6c, 0x63, 0x79, 0x42, + 0x45, 0x61, 0x58, 0x5a, 0x70, 0x63, 0x32, 0x6c, 0x76, 0x62, 0x6a, 0x45, + 0x68, 0x4d, 0x42, 0x38, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, + 0x59, 0x56, 0x47, 0x68, 0x68, 0x64, 0x33, 0x52, 0x6c, 0x49, 0x46, 0x42, + 0x79, 0x5a, 0x57, 0x31, 0x70, 0x64, 0x57, 0x30, 0x67, 0x55, 0x32, 0x56, + 0x79, 0x0a, 0x64, 0x6d, 0x56, 0x79, 0x49, 0x45, 0x4e, 0x42, 0x4d, 0x53, + 0x67, 0x77, 0x4a, 0x67, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, + 0x63, 0x4e, 0x41, 0x51, 0x6b, 0x42, 0x46, 0x68, 0x6c, 0x77, 0x63, 0x6d, + 0x56, 0x74, 0x61, 0x58, 0x56, 0x74, 0x4c, 0x58, 0x4e, 0x6c, 0x63, 0x6e, + 0x5a, 0x6c, 0x63, 0x6b, 0x42, 0x30, 0x61, 0x47, 0x46, 0x33, 0x64, 0x47, + 0x55, 0x75, 0x59, 0x32, 0x39, 0x74, 0x0a, 0x4d, 0x42, 0x34, 0x58, 0x44, + 0x54, 0x6b, 0x32, 0x4d, 0x44, 0x67, 0x77, 0x4d, 0x54, 0x41, 0x77, 0x4d, + 0x44, 0x41, 0x77, 0x4d, 0x46, 0x6f, 0x58, 0x44, 0x54, 0x49, 0x77, 0x4d, + 0x54, 0x49, 0x7a, 0x4d, 0x54, 0x49, 0x7a, 0x4e, 0x54, 0x6b, 0x31, 0x4f, + 0x56, 0x6f, 0x77, 0x67, 0x63, 0x34, 0x78, 0x43, 0x7a, 0x41, 0x4a, 0x42, + 0x67, 0x4e, 0x56, 0x42, 0x41, 0x59, 0x54, 0x41, 0x6c, 0x70, 0x42, 0x0a, + 0x4d, 0x52, 0x55, 0x77, 0x45, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, 0x49, + 0x45, 0x77, 0x78, 0x58, 0x5a, 0x58, 0x4e, 0x30, 0x5a, 0x58, 0x4a, 0x75, + 0x49, 0x45, 0x4e, 0x68, 0x63, 0x47, 0x55, 0x78, 0x45, 0x6a, 0x41, 0x51, + 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x63, 0x54, 0x43, 0x55, 0x4e, 0x68, + 0x63, 0x47, 0x55, 0x67, 0x56, 0x47, 0x39, 0x33, 0x62, 0x6a, 0x45, 0x64, + 0x4d, 0x42, 0x73, 0x47, 0x0a, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, + 0x55, 0x56, 0x47, 0x68, 0x68, 0x64, 0x33, 0x52, 0x6c, 0x49, 0x45, 0x4e, + 0x76, 0x62, 0x6e, 0x4e, 0x31, 0x62, 0x48, 0x52, 0x70, 0x62, 0x6d, 0x63, + 0x67, 0x59, 0x32, 0x4d, 0x78, 0x4b, 0x44, 0x41, 0x6d, 0x42, 0x67, 0x4e, + 0x56, 0x42, 0x41, 0x73, 0x54, 0x48, 0x30, 0x4e, 0x6c, 0x63, 0x6e, 0x52, + 0x70, 0x5a, 0x6d, 0x6c, 0x6a, 0x59, 0x58, 0x52, 0x70, 0x0a, 0x62, 0x32, + 0x34, 0x67, 0x55, 0x32, 0x56, 0x79, 0x64, 0x6d, 0x6c, 0x6a, 0x5a, 0x58, + 0x4d, 0x67, 0x52, 0x47, 0x6c, 0x32, 0x61, 0x58, 0x4e, 0x70, 0x62, 0x32, + 0x34, 0x78, 0x49, 0x54, 0x41, 0x66, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, + 0x4d, 0x54, 0x47, 0x46, 0x52, 0x6f, 0x59, 0x58, 0x64, 0x30, 0x5a, 0x53, + 0x42, 0x51, 0x63, 0x6d, 0x56, 0x74, 0x61, 0x58, 0x56, 0x74, 0x49, 0x46, + 0x4e, 0x6c, 0x0a, 0x63, 0x6e, 0x5a, 0x6c, 0x63, 0x69, 0x42, 0x44, 0x51, + 0x54, 0x45, 0x6f, 0x4d, 0x43, 0x59, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, + 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x4a, 0x41, 0x52, 0x59, 0x5a, 0x63, + 0x48, 0x4a, 0x6c, 0x62, 0x57, 0x6c, 0x31, 0x62, 0x53, 0x31, 0x7a, 0x5a, + 0x58, 0x4a, 0x32, 0x5a, 0x58, 0x4a, 0x41, 0x64, 0x47, 0x68, 0x68, 0x64, + 0x33, 0x52, 0x6c, 0x4c, 0x6d, 0x4e, 0x76, 0x0a, 0x62, 0x54, 0x43, 0x42, + 0x6e, 0x7a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, + 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x45, 0x46, 0x41, 0x41, 0x4f, 0x42, + 0x6a, 0x51, 0x41, 0x77, 0x67, 0x59, 0x6b, 0x43, 0x67, 0x59, 0x45, 0x41, + 0x30, 0x6a, 0x59, 0x32, 0x61, 0x6f, 0x76, 0x58, 0x77, 0x6c, 0x75, 0x65, + 0x32, 0x6f, 0x46, 0x42, 0x59, 0x6f, 0x38, 0x34, 0x37, 0x6b, 0x6b, 0x45, + 0x0a, 0x56, 0x64, 0x62, 0x51, 0x37, 0x78, 0x77, 0x62, 0x6c, 0x52, 0x5a, + 0x48, 0x37, 0x78, 0x68, 0x49, 0x4e, 0x54, 0x70, 0x53, 0x39, 0x43, 0x74, + 0x71, 0x42, 0x6f, 0x38, 0x37, 0x4c, 0x2b, 0x70, 0x57, 0x34, 0x36, 0x2b, + 0x47, 0x6a, 0x5a, 0x34, 0x58, 0x39, 0x35, 0x36, 0x30, 0x5a, 0x58, 0x55, + 0x43, 0x54, 0x65, 0x2f, 0x4c, 0x43, 0x61, 0x49, 0x68, 0x55, 0x64, 0x69, + 0x62, 0x30, 0x47, 0x66, 0x51, 0x0a, 0x75, 0x67, 0x32, 0x53, 0x42, 0x68, + 0x52, 0x7a, 0x31, 0x4a, 0x50, 0x4c, 0x6c, 0x79, 0x6f, 0x41, 0x6e, 0x46, + 0x78, 0x4f, 0x44, 0x4c, 0x7a, 0x36, 0x46, 0x56, 0x4c, 0x38, 0x38, 0x6b, + 0x52, 0x75, 0x32, 0x68, 0x46, 0x4b, 0x62, 0x67, 0x69, 0x66, 0x4c, 0x79, + 0x33, 0x6a, 0x2b, 0x61, 0x6f, 0x36, 0x68, 0x6e, 0x4f, 0x32, 0x52, 0x6c, + 0x4e, 0x59, 0x79, 0x49, 0x6b, 0x46, 0x76, 0x59, 0x4d, 0x52, 0x0a, 0x75, + 0x48, 0x4d, 0x2f, 0x71, 0x67, 0x65, 0x4e, 0x39, 0x45, 0x4a, 0x4e, 0x35, + 0x30, 0x43, 0x64, 0x48, 0x44, 0x63, 0x43, 0x41, 0x77, 0x45, 0x41, 0x41, + 0x61, 0x4d, 0x54, 0x4d, 0x42, 0x45, 0x77, 0x44, 0x77, 0x59, 0x44, 0x56, + 0x52, 0x30, 0x54, 0x41, 0x51, 0x48, 0x2f, 0x42, 0x41, 0x55, 0x77, 0x41, + 0x77, 0x45, 0x42, 0x2f, 0x7a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, + 0x6b, 0x69, 0x47, 0x0a, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x51, 0x46, + 0x41, 0x41, 0x4f, 0x42, 0x67, 0x51, 0x41, 0x6d, 0x53, 0x43, 0x77, 0x57, + 0x77, 0x6c, 0x6a, 0x36, 0x36, 0x42, 0x5a, 0x30, 0x44, 0x4b, 0x71, 0x71, + 0x58, 0x31, 0x51, 0x2f, 0x38, 0x74, 0x66, 0x4a, 0x65, 0x47, 0x42, 0x65, + 0x58, 0x6d, 0x34, 0x33, 0x59, 0x79, 0x4a, 0x33, 0x4e, 0x6e, 0x36, 0x79, + 0x46, 0x38, 0x51, 0x30, 0x75, 0x66, 0x55, 0x49, 0x0a, 0x68, 0x66, 0x7a, + 0x4a, 0x41, 0x54, 0x6a, 0x2f, 0x54, 0x62, 0x37, 0x79, 0x46, 0x6b, 0x4a, + 0x44, 0x35, 0x37, 0x74, 0x61, 0x52, 0x76, 0x76, 0x42, 0x78, 0x68, 0x45, + 0x66, 0x38, 0x55, 0x71, 0x77, 0x4b, 0x45, 0x62, 0x4a, 0x77, 0x38, 0x52, + 0x43, 0x66, 0x62, 0x7a, 0x36, 0x71, 0x31, 0x6c, 0x75, 0x31, 0x62, 0x64, + 0x52, 0x69, 0x42, 0x48, 0x6a, 0x70, 0x49, 0x55, 0x5a, 0x61, 0x34, 0x4a, + 0x4d, 0x0a, 0x70, 0x41, 0x77, 0x53, 0x72, 0x65, 0x6d, 0x6b, 0x72, 0x6a, + 0x2f, 0x78, 0x77, 0x30, 0x6c, 0x6c, 0x6d, 0x6f, 0x7a, 0x46, 0x79, 0x44, + 0x34, 0x6c, 0x74, 0x35, 0x53, 0x5a, 0x75, 0x35, 0x49, 0x79, 0x63, 0x51, + 0x66, 0x77, 0x68, 0x6c, 0x37, 0x74, 0x55, 0x43, 0x65, 0x6d, 0x44, 0x61, + 0x59, 0x6a, 0x2b, 0x62, 0x76, 0x4c, 0x70, 0x67, 0x63, 0x55, 0x51, 0x67, + 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, + 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, + 0x65, 0x72, 0x3a, 0x20, 0x4f, 0x3d, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, + 0x78, 0x20, 0x4f, 0x55, 0x3d, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, + 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x3a, 0x20, 0x4f, 0x3d, 0x45, 0x71, 0x75, 0x69, 0x66, + 0x61, 0x78, 0x20, 0x4f, 0x55, 0x3d, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, + 0x78, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, + 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, + 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x41, 0x22, 0x0a, + 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x39, 0x30, + 0x33, 0x38, 0x30, 0x34, 0x31, 0x31, 0x31, 0x0a, 0x23, 0x20, 0x4d, 0x44, + 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, + 0x74, 0x3a, 0x20, 0x36, 0x37, 0x3a, 0x63, 0x62, 0x3a, 0x39, 0x64, 0x3a, + 0x63, 0x30, 0x3a, 0x31, 0x33, 0x3a, 0x32, 0x34, 0x3a, 0x38, 0x61, 0x3a, + 0x38, 0x32, 0x3a, 0x39, 0x62, 0x3a, 0x62, 0x32, 0x3a, 0x31, 0x37, 0x3a, + 0x31, 0x65, 0x3a, 0x64, 0x31, 0x3a, 0x31, 0x62, 0x3a, 0x65, 0x63, 0x3a, + 0x64, 0x34, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, + 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x64, + 0x32, 0x3a, 0x33, 0x32, 0x3a, 0x30, 0x39, 0x3a, 0x61, 0x64, 0x3a, 0x32, + 0x33, 0x3a, 0x64, 0x33, 0x3a, 0x31, 0x34, 0x3a, 0x32, 0x33, 0x3a, 0x32, + 0x31, 0x3a, 0x37, 0x34, 0x3a, 0x65, 0x34, 0x3a, 0x30, 0x64, 0x3a, 0x37, + 0x66, 0x3a, 0x39, 0x64, 0x3a, 0x36, 0x32, 0x3a, 0x31, 0x33, 0x3a, 0x39, + 0x37, 0x3a, 0x38, 0x36, 0x3a, 0x36, 0x33, 0x3a, 0x33, 0x61, 0x0a, 0x23, + 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, + 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x30, 0x38, 0x3a, + 0x32, 0x39, 0x3a, 0x37, 0x61, 0x3a, 0x34, 0x30, 0x3a, 0x34, 0x37, 0x3a, + 0x64, 0x62, 0x3a, 0x61, 0x32, 0x3a, 0x33, 0x36, 0x3a, 0x38, 0x30, 0x3a, + 0x63, 0x37, 0x3a, 0x33, 0x31, 0x3a, 0x64, 0x62, 0x3a, 0x36, 0x65, 0x3a, + 0x33, 0x31, 0x3a, 0x37, 0x36, 0x3a, 0x35, 0x33, 0x3a, 0x63, 0x61, 0x3a, + 0x37, 0x38, 0x3a, 0x34, 0x38, 0x3a, 0x65, 0x31, 0x3a, 0x62, 0x65, 0x3a, + 0x62, 0x64, 0x3a, 0x33, 0x61, 0x3a, 0x30, 0x62, 0x3a, 0x30, 0x31, 0x3a, + 0x37, 0x39, 0x3a, 0x61, 0x37, 0x3a, 0x30, 0x37, 0x3a, 0x66, 0x39, 0x3a, + 0x32, 0x63, 0x3a, 0x66, 0x31, 0x3a, 0x37, 0x38, 0x0a, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, + 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x0a, 0x4d, 0x49, 0x49, 0x44, 0x49, 0x44, 0x43, 0x43, 0x41, 0x6f, 0x6d, + 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x45, 0x4e, 0x64, 0x37, + 0x30, 0x7a, 0x7a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, + 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x42, + 0x4f, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, + 0x47, 0x45, 0x77, 0x4a, 0x56, 0x0a, 0x55, 0x7a, 0x45, 0x51, 0x4d, 0x41, + 0x34, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x48, 0x52, 0x58, + 0x46, 0x31, 0x61, 0x57, 0x5a, 0x68, 0x65, 0x44, 0x45, 0x74, 0x4d, 0x43, + 0x73, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x78, 0x4d, 0x6b, 0x52, 0x58, + 0x46, 0x31, 0x61, 0x57, 0x5a, 0x68, 0x65, 0x43, 0x42, 0x54, 0x5a, 0x57, + 0x4e, 0x31, 0x63, 0x6d, 0x55, 0x67, 0x51, 0x32, 0x56, 0x79, 0x0a, 0x64, + 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, 0x55, 0x67, 0x51, + 0x58, 0x56, 0x30, 0x61, 0x47, 0x39, 0x79, 0x61, 0x58, 0x52, 0x35, 0x4d, + 0x42, 0x34, 0x58, 0x44, 0x54, 0x6b, 0x34, 0x4d, 0x44, 0x67, 0x79, 0x4d, + 0x6a, 0x45, 0x32, 0x4e, 0x44, 0x45, 0x31, 0x4d, 0x56, 0x6f, 0x58, 0x44, + 0x54, 0x45, 0x34, 0x4d, 0x44, 0x67, 0x79, 0x4d, 0x6a, 0x45, 0x32, 0x4e, + 0x44, 0x45, 0x31, 0x0a, 0x4d, 0x56, 0x6f, 0x77, 0x54, 0x6a, 0x45, 0x4c, + 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, + 0x56, 0x56, 0x4d, 0x78, 0x45, 0x44, 0x41, 0x4f, 0x42, 0x67, 0x4e, 0x56, + 0x42, 0x41, 0x6f, 0x54, 0x42, 0x30, 0x56, 0x78, 0x64, 0x57, 0x6c, 0x6d, + 0x59, 0x58, 0x67, 0x78, 0x4c, 0x54, 0x41, 0x72, 0x42, 0x67, 0x4e, 0x56, + 0x42, 0x41, 0x73, 0x54, 0x4a, 0x45, 0x56, 0x78, 0x0a, 0x64, 0x57, 0x6c, + 0x6d, 0x59, 0x58, 0x67, 0x67, 0x55, 0x32, 0x56, 0x6a, 0x64, 0x58, 0x4a, + 0x6c, 0x49, 0x45, 0x4e, 0x6c, 0x63, 0x6e, 0x52, 0x70, 0x5a, 0x6d, 0x6c, + 0x6a, 0x59, 0x58, 0x52, 0x6c, 0x49, 0x45, 0x46, 0x31, 0x64, 0x47, 0x68, + 0x76, 0x63, 0x6d, 0x6c, 0x30, 0x65, 0x54, 0x43, 0x42, 0x6e, 0x7a, 0x41, + 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, + 0x42, 0x0a, 0x41, 0x51, 0x45, 0x46, 0x41, 0x41, 0x4f, 0x42, 0x6a, 0x51, + 0x41, 0x77, 0x67, 0x59, 0x6b, 0x43, 0x67, 0x59, 0x45, 0x41, 0x77, 0x56, + 0x32, 0x78, 0x57, 0x47, 0x63, 0x49, 0x59, 0x75, 0x36, 0x67, 0x6d, 0x69, + 0x30, 0x66, 0x43, 0x47, 0x32, 0x52, 0x46, 0x47, 0x69, 0x59, 0x43, 0x68, + 0x37, 0x2b, 0x32, 0x67, 0x52, 0x76, 0x45, 0x34, 0x52, 0x69, 0x49, 0x63, + 0x50, 0x52, 0x66, 0x4d, 0x36, 0x66, 0x0a, 0x42, 0x65, 0x43, 0x34, 0x41, + 0x66, 0x42, 0x4f, 0x4e, 0x4f, 0x7a, 0x69, 0x69, 0x70, 0x55, 0x45, 0x5a, + 0x4b, 0x7a, 0x78, 0x61, 0x31, 0x4e, 0x66, 0x42, 0x62, 0x50, 0x4c, 0x5a, + 0x34, 0x43, 0x2f, 0x51, 0x67, 0x4b, 0x4f, 0x2f, 0x74, 0x30, 0x42, 0x43, + 0x65, 0x7a, 0x68, 0x41, 0x42, 0x52, 0x50, 0x2f, 0x50, 0x76, 0x77, 0x44, + 0x4e, 0x31, 0x44, 0x75, 0x6c, 0x73, 0x72, 0x34, 0x52, 0x2b, 0x41, 0x0a, + 0x63, 0x4a, 0x6b, 0x56, 0x56, 0x35, 0x4d, 0x57, 0x38, 0x51, 0x2b, 0x58, + 0x61, 0x72, 0x66, 0x43, 0x61, 0x43, 0x4d, 0x63, 0x7a, 0x45, 0x31, 0x5a, + 0x4d, 0x4b, 0x78, 0x52, 0x48, 0x6a, 0x75, 0x76, 0x4b, 0x39, 0x62, 0x75, + 0x59, 0x30, 0x56, 0x37, 0x78, 0x64, 0x6c, 0x66, 0x55, 0x4e, 0x4c, 0x6a, + 0x55, 0x41, 0x38, 0x36, 0x69, 0x4f, 0x65, 0x2f, 0x46, 0x50, 0x33, 0x67, + 0x78, 0x37, 0x6b, 0x43, 0x0a, 0x41, 0x77, 0x45, 0x41, 0x41, 0x61, 0x4f, + 0x43, 0x41, 0x51, 0x6b, 0x77, 0x67, 0x67, 0x45, 0x46, 0x4d, 0x48, 0x41, + 0x47, 0x41, 0x31, 0x55, 0x64, 0x48, 0x77, 0x52, 0x70, 0x4d, 0x47, 0x63, + 0x77, 0x5a, 0x61, 0x42, 0x6a, 0x6f, 0x47, 0x47, 0x6b, 0x58, 0x7a, 0x42, + 0x64, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, + 0x47, 0x45, 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, 0x51, 0x0a, 0x4d, 0x41, + 0x34, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x48, 0x52, 0x58, + 0x46, 0x31, 0x61, 0x57, 0x5a, 0x68, 0x65, 0x44, 0x45, 0x74, 0x4d, 0x43, + 0x73, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x78, 0x4d, 0x6b, 0x52, 0x58, + 0x46, 0x31, 0x61, 0x57, 0x5a, 0x68, 0x65, 0x43, 0x42, 0x54, 0x5a, 0x57, + 0x4e, 0x31, 0x63, 0x6d, 0x55, 0x67, 0x51, 0x32, 0x56, 0x79, 0x64, 0x47, + 0x6c, 0x6d, 0x0a, 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, 0x55, 0x67, 0x51, + 0x58, 0x56, 0x30, 0x61, 0x47, 0x39, 0x79, 0x61, 0x58, 0x52, 0x35, 0x4d, + 0x51, 0x30, 0x77, 0x43, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, + 0x77, 0x52, 0x44, 0x55, 0x6b, 0x77, 0x78, 0x4d, 0x42, 0x6f, 0x47, 0x41, + 0x31, 0x55, 0x64, 0x45, 0x41, 0x51, 0x54, 0x4d, 0x42, 0x47, 0x42, 0x44, + 0x7a, 0x49, 0x77, 0x4d, 0x54, 0x67, 0x77, 0x0a, 0x4f, 0x44, 0x49, 0x79, + 0x4d, 0x54, 0x59, 0x30, 0x4d, 0x54, 0x55, 0x78, 0x57, 0x6a, 0x41, 0x4c, + 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x38, 0x45, 0x42, 0x41, 0x4d, 0x43, + 0x41, 0x51, 0x59, 0x77, 0x48, 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, 0x6a, + 0x42, 0x42, 0x67, 0x77, 0x46, 0x6f, 0x41, 0x55, 0x53, 0x4f, 0x5a, 0x6f, + 0x2b, 0x53, 0x76, 0x53, 0x73, 0x70, 0x58, 0x58, 0x52, 0x39, 0x67, 0x6a, + 0x0a, 0x49, 0x42, 0x42, 0x50, 0x4d, 0x35, 0x69, 0x51, 0x6e, 0x39, 0x51, + 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, 0x52, 0x30, 0x4f, 0x42, 0x42, 0x59, + 0x45, 0x46, 0x45, 0x6a, 0x6d, 0x61, 0x50, 0x6b, 0x72, 0x30, 0x72, 0x4b, + 0x56, 0x31, 0x30, 0x66, 0x59, 0x49, 0x79, 0x41, 0x51, 0x54, 0x7a, 0x4f, + 0x59, 0x6b, 0x4a, 0x2f, 0x55, 0x4d, 0x41, 0x77, 0x47, 0x41, 0x31, 0x55, + 0x64, 0x45, 0x77, 0x51, 0x46, 0x0a, 0x4d, 0x41, 0x4d, 0x42, 0x41, 0x66, + 0x38, 0x77, 0x47, 0x67, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, + 0x5a, 0x39, 0x42, 0x30, 0x45, 0x41, 0x42, 0x41, 0x30, 0x77, 0x43, 0x78, + 0x73, 0x46, 0x56, 0x6a, 0x4d, 0x75, 0x4d, 0x47, 0x4d, 0x44, 0x41, 0x67, + 0x62, 0x41, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, + 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x42, 0x51, 0x55, 0x41, 0x0a, 0x41, + 0x34, 0x47, 0x42, 0x41, 0x46, 0x6a, 0x4f, 0x4b, 0x65, 0x72, 0x38, 0x39, + 0x39, 0x36, 0x31, 0x7a, 0x67, 0x4b, 0x35, 0x46, 0x37, 0x57, 0x46, 0x30, + 0x62, 0x6e, 0x6a, 0x34, 0x4a, 0x58, 0x4d, 0x4a, 0x54, 0x45, 0x4e, 0x41, + 0x4b, 0x61, 0x53, 0x62, 0x6e, 0x2b, 0x32, 0x6b, 0x6d, 0x4f, 0x65, 0x55, + 0x4a, 0x58, 0x52, 0x6d, 0x6d, 0x2f, 0x6b, 0x45, 0x64, 0x35, 0x6a, 0x68, + 0x57, 0x36, 0x59, 0x0a, 0x37, 0x71, 0x6a, 0x2f, 0x57, 0x73, 0x6a, 0x54, + 0x56, 0x62, 0x4a, 0x6d, 0x63, 0x56, 0x66, 0x65, 0x77, 0x43, 0x48, 0x72, + 0x50, 0x53, 0x71, 0x6e, 0x49, 0x30, 0x6b, 0x42, 0x42, 0x49, 0x5a, 0x43, + 0x65, 0x2f, 0x7a, 0x75, 0x66, 0x36, 0x49, 0x57, 0x55, 0x72, 0x56, 0x6e, + 0x5a, 0x39, 0x4e, 0x41, 0x32, 0x7a, 0x73, 0x6d, 0x57, 0x4c, 0x49, 0x6f, + 0x64, 0x7a, 0x32, 0x75, 0x46, 0x48, 0x64, 0x68, 0x0a, 0x31, 0x76, 0x6f, + 0x71, 0x5a, 0x69, 0x65, 0x67, 0x44, 0x66, 0x71, 0x6e, 0x63, 0x31, 0x7a, + 0x71, 0x63, 0x50, 0x47, 0x55, 0x49, 0x57, 0x56, 0x45, 0x58, 0x2f, 0x72, + 0x38, 0x37, 0x79, 0x6c, 0x6f, 0x71, 0x61, 0x4b, 0x48, 0x65, 0x65, 0x39, + 0x35, 0x37, 0x30, 0x2b, 0x73, 0x42, 0x33, 0x63, 0x34, 0x0a, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, + 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, + 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x4f, + 0x3d, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, + 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x43, 0x6c, 0x61, 0x73, 0x73, + 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, + 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x3a, 0x20, 0x4f, 0x3d, 0x56, 0x65, 0x72, 0x69, 0x53, + 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, + 0x3d, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, + 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x0a, + 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x56, 0x65, + 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, + 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, + 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, + 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x31, 0x34, 0x39, 0x38, 0x34, 0x33, 0x39, + 0x32, 0x39, 0x34, 0x33, 0x35, 0x38, 0x31, 0x38, 0x36, 0x39, 0x32, 0x38, + 0x34, 0x38, 0x30, 0x34, 0x30, 0x33, 0x36, 0x35, 0x37, 0x31, 0x36, 0x38, + 0x35, 0x31, 0x37, 0x30, 0x32, 0x34, 0x36, 0x33, 0x0a, 0x23, 0x20, 0x4d, + 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x3a, 0x20, 0x31, 0x30, 0x3a, 0x66, 0x63, 0x3a, 0x36, 0x33, + 0x3a, 0x35, 0x64, 0x3a, 0x66, 0x36, 0x3a, 0x32, 0x36, 0x3a, 0x33, 0x65, + 0x3a, 0x30, 0x64, 0x3a, 0x66, 0x33, 0x3a, 0x32, 0x35, 0x3a, 0x62, 0x65, + 0x3a, 0x35, 0x66, 0x3a, 0x37, 0x39, 0x3a, 0x63, 0x64, 0x3a, 0x36, 0x37, + 0x3a, 0x36, 0x37, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x37, 0x34, 0x3a, 0x32, 0x63, 0x3a, 0x33, 0x31, 0x3a, 0x39, 0x32, 0x3a, + 0x65, 0x36, 0x3a, 0x30, 0x37, 0x3a, 0x65, 0x34, 0x3a, 0x32, 0x34, 0x3a, + 0x65, 0x62, 0x3a, 0x34, 0x35, 0x3a, 0x34, 0x39, 0x3a, 0x35, 0x34, 0x3a, + 0x32, 0x62, 0x3a, 0x65, 0x31, 0x3a, 0x62, 0x62, 0x3a, 0x63, 0x35, 0x3a, + 0x33, 0x65, 0x3a, 0x36, 0x31, 0x3a, 0x37, 0x34, 0x3a, 0x65, 0x32, 0x0a, + 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, + 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x65, 0x37, + 0x3a, 0x36, 0x38, 0x3a, 0x35, 0x36, 0x3a, 0x33, 0x34, 0x3a, 0x65, 0x66, + 0x3a, 0x61, 0x63, 0x3a, 0x66, 0x36, 0x3a, 0x39, 0x61, 0x3a, 0x63, 0x65, + 0x3a, 0x39, 0x33, 0x3a, 0x39, 0x61, 0x3a, 0x36, 0x62, 0x3a, 0x32, 0x35, + 0x3a, 0x35, 0x62, 0x3a, 0x37, 0x62, 0x3a, 0x34, 0x66, 0x3a, 0x61, 0x62, + 0x3a, 0x65, 0x66, 0x3a, 0x34, 0x32, 0x3a, 0x39, 0x33, 0x3a, 0x35, 0x62, + 0x3a, 0x35, 0x30, 0x3a, 0x61, 0x32, 0x3a, 0x36, 0x35, 0x3a, 0x61, 0x63, + 0x3a, 0x62, 0x35, 0x3a, 0x63, 0x62, 0x3a, 0x36, 0x30, 0x3a, 0x32, 0x37, + 0x3a, 0x65, 0x34, 0x3a, 0x34, 0x65, 0x3a, 0x37, 0x30, 0x0a, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, + 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x43, 0x50, 0x44, 0x43, 0x43, 0x41, 0x61, + 0x55, 0x43, 0x45, 0x48, 0x43, 0x36, 0x35, 0x42, 0x30, 0x51, 0x32, 0x53, + 0x6b, 0x30, 0x74, 0x6a, 0x6a, 0x4b, 0x65, 0x77, 0x50, 0x4d, 0x75, 0x72, + 0x38, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, + 0x63, 0x4e, 0x41, 0x51, 0x45, 0x43, 0x42, 0x51, 0x41, 0x77, 0x58, 0x7a, + 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x0a, 0x41, 0x31, 0x55, 0x45, 0x42, + 0x68, 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, 0x46, 0x7a, 0x41, 0x56, 0x42, + 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, 0x44, 0x6c, 0x5a, 0x6c, 0x63, + 0x6d, 0x6c, 0x54, 0x61, 0x57, 0x64, 0x75, 0x4c, 0x43, 0x42, 0x4a, 0x62, + 0x6d, 0x4d, 0x75, 0x4d, 0x54, 0x63, 0x77, 0x4e, 0x51, 0x59, 0x44, 0x56, + 0x51, 0x51, 0x4c, 0x45, 0x79, 0x35, 0x44, 0x62, 0x47, 0x46, 0x7a, 0x0a, + 0x63, 0x79, 0x41, 0x7a, 0x49, 0x46, 0x42, 0x31, 0x59, 0x6d, 0x78, 0x70, + 0x59, 0x79, 0x42, 0x51, 0x63, 0x6d, 0x6c, 0x74, 0x59, 0x58, 0x4a, 0x35, + 0x49, 0x45, 0x4e, 0x6c, 0x63, 0x6e, 0x52, 0x70, 0x5a, 0x6d, 0x6c, 0x6a, + 0x59, 0x58, 0x52, 0x70, 0x62, 0x32, 0x34, 0x67, 0x51, 0x58, 0x56, 0x30, + 0x61, 0x47, 0x39, 0x79, 0x61, 0x58, 0x52, 0x35, 0x4d, 0x42, 0x34, 0x58, + 0x44, 0x54, 0x6b, 0x32, 0x0a, 0x4d, 0x44, 0x45, 0x79, 0x4f, 0x54, 0x41, + 0x77, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x46, 0x6f, 0x58, 0x44, 0x54, 0x49, + 0x34, 0x4d, 0x44, 0x67, 0x77, 0x4d, 0x54, 0x49, 0x7a, 0x4e, 0x54, 0x6b, + 0x31, 0x4f, 0x56, 0x6f, 0x77, 0x58, 0x7a, 0x45, 0x4c, 0x4d, 0x41, 0x6b, + 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x56, 0x56, 0x4d, + 0x78, 0x46, 0x7a, 0x41, 0x56, 0x42, 0x67, 0x4e, 0x56, 0x0a, 0x42, 0x41, + 0x6f, 0x54, 0x44, 0x6c, 0x5a, 0x6c, 0x63, 0x6d, 0x6c, 0x54, 0x61, 0x57, + 0x64, 0x75, 0x4c, 0x43, 0x42, 0x4a, 0x62, 0x6d, 0x4d, 0x75, 0x4d, 0x54, + 0x63, 0x77, 0x4e, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, 0x79, + 0x35, 0x44, 0x62, 0x47, 0x46, 0x7a, 0x63, 0x79, 0x41, 0x7a, 0x49, 0x46, + 0x42, 0x31, 0x59, 0x6d, 0x78, 0x70, 0x59, 0x79, 0x42, 0x51, 0x63, 0x6d, + 0x6c, 0x74, 0x0a, 0x59, 0x58, 0x4a, 0x35, 0x49, 0x45, 0x4e, 0x6c, 0x63, + 0x6e, 0x52, 0x70, 0x5a, 0x6d, 0x6c, 0x6a, 0x59, 0x58, 0x52, 0x70, 0x62, + 0x32, 0x34, 0x67, 0x51, 0x58, 0x56, 0x30, 0x61, 0x47, 0x39, 0x79, 0x61, + 0x58, 0x52, 0x35, 0x4d, 0x49, 0x47, 0x66, 0x4d, 0x41, 0x30, 0x47, 0x43, + 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x41, + 0x51, 0x55, 0x41, 0x41, 0x34, 0x47, 0x4e, 0x0a, 0x41, 0x44, 0x43, 0x42, + 0x69, 0x51, 0x4b, 0x42, 0x67, 0x51, 0x44, 0x4a, 0x58, 0x46, 0x6d, 0x65, + 0x38, 0x68, 0x75, 0x4b, 0x41, 0x52, 0x53, 0x30, 0x45, 0x4e, 0x38, 0x45, + 0x51, 0x4e, 0x76, 0x6a, 0x56, 0x36, 0x39, 0x71, 0x52, 0x55, 0x43, 0x50, + 0x68, 0x41, 0x77, 0x4c, 0x30, 0x54, 0x50, 0x5a, 0x32, 0x52, 0x48, 0x50, + 0x37, 0x67, 0x4a, 0x59, 0x48, 0x79, 0x58, 0x33, 0x4b, 0x71, 0x68, 0x45, + 0x0a, 0x42, 0x61, 0x72, 0x73, 0x41, 0x78, 0x39, 0x34, 0x66, 0x35, 0x36, + 0x54, 0x75, 0x5a, 0x6f, 0x41, 0x71, 0x69, 0x4e, 0x39, 0x31, 0x71, 0x79, + 0x46, 0x6f, 0x6d, 0x4e, 0x46, 0x78, 0x33, 0x49, 0x6e, 0x7a, 0x50, 0x52, + 0x4d, 0x78, 0x6e, 0x56, 0x78, 0x30, 0x6a, 0x6e, 0x76, 0x54, 0x30, 0x4c, + 0x77, 0x64, 0x64, 0x38, 0x4b, 0x6b, 0x4d, 0x61, 0x4f, 0x49, 0x47, 0x2b, + 0x59, 0x44, 0x2f, 0x69, 0x73, 0x0a, 0x49, 0x31, 0x39, 0x77, 0x4b, 0x54, + 0x61, 0x6b, 0x79, 0x59, 0x62, 0x6e, 0x73, 0x5a, 0x6f, 0x67, 0x79, 0x31, + 0x4f, 0x6c, 0x68, 0x65, 0x63, 0x39, 0x76, 0x6e, 0x32, 0x61, 0x2f, 0x69, + 0x52, 0x46, 0x4d, 0x39, 0x78, 0x32, 0x46, 0x65, 0x30, 0x50, 0x6f, 0x6e, + 0x46, 0x6b, 0x54, 0x47, 0x55, 0x75, 0x67, 0x57, 0x68, 0x46, 0x70, 0x77, + 0x49, 0x44, 0x41, 0x51, 0x41, 0x42, 0x4d, 0x41, 0x30, 0x47, 0x0a, 0x43, + 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x41, + 0x67, 0x55, 0x41, 0x41, 0x34, 0x47, 0x42, 0x41, 0x4c, 0x74, 0x4d, 0x45, + 0x69, 0x76, 0x50, 0x4c, 0x43, 0x59, 0x41, 0x54, 0x78, 0x51, 0x54, 0x33, + 0x61, 0x62, 0x37, 0x2f, 0x41, 0x6f, 0x52, 0x68, 0x49, 0x7a, 0x7a, 0x4b, + 0x42, 0x78, 0x6e, 0x6b, 0x69, 0x39, 0x38, 0x74, 0x73, 0x58, 0x36, 0x33, + 0x2f, 0x44, 0x6f, 0x0a, 0x6c, 0x62, 0x77, 0x64, 0x6a, 0x32, 0x77, 0x73, + 0x71, 0x46, 0x48, 0x4d, 0x63, 0x39, 0x69, 0x6b, 0x77, 0x46, 0x50, 0x77, + 0x54, 0x74, 0x59, 0x6d, 0x77, 0x48, 0x59, 0x42, 0x56, 0x34, 0x47, 0x53, + 0x58, 0x69, 0x48, 0x78, 0x30, 0x62, 0x48, 0x2f, 0x35, 0x39, 0x41, 0x68, + 0x57, 0x4d, 0x31, 0x70, 0x46, 0x2b, 0x4e, 0x45, 0x48, 0x4a, 0x77, 0x5a, + 0x52, 0x44, 0x6d, 0x4a, 0x58, 0x4e, 0x79, 0x63, 0x0a, 0x41, 0x41, 0x39, + 0x57, 0x6a, 0x51, 0x4b, 0x5a, 0x37, 0x61, 0x4b, 0x51, 0x52, 0x55, 0x7a, + 0x6b, 0x75, 0x78, 0x43, 0x6b, 0x50, 0x66, 0x41, 0x79, 0x41, 0x77, 0x37, + 0x78, 0x7a, 0x76, 0x6a, 0x6f, 0x79, 0x56, 0x47, 0x4d, 0x35, 0x6d, 0x4b, + 0x66, 0x35, 0x70, 0x2f, 0x41, 0x66, 0x62, 0x64, 0x79, 0x6e, 0x4d, 0x6b, + 0x32, 0x4f, 0x6d, 0x75, 0x66, 0x54, 0x71, 0x6a, 0x2f, 0x5a, 0x41, 0x31, + 0x6b, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, + 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, + 0x72, 0x3a, 0x20, 0x4f, 0x3d, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, + 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x43, + 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, + 0x47, 0x32, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x38, 0x20, + 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, + 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, + 0x6f, 0x6e, 0x6c, 0x79, 0x2f, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, + 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, + 0x6f, 0x72, 0x6b, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x3a, 0x20, 0x4f, 0x3d, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, + 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x43, + 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, + 0x47, 0x32, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x38, 0x20, + 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, + 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, + 0x6f, 0x6e, 0x6c, 0x79, 0x2f, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, + 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, + 0x6f, 0x72, 0x6b, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x56, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x20, 0x43, + 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, + 0x47, 0x32, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, + 0x3a, 0x20, 0x31, 0x36, 0x37, 0x32, 0x38, 0x35, 0x33, 0x38, 0x30, 0x32, + 0x34, 0x32, 0x33, 0x31, 0x39, 0x36, 0x34, 0x38, 0x34, 0x35, 0x31, 0x31, + 0x35, 0x34, 0x34, 0x37, 0x38, 0x38, 0x30, 0x38, 0x30, 0x33, 0x36, 0x38, + 0x38, 0x31, 0x36, 0x30, 0x36, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, + 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, + 0x20, 0x61, 0x32, 0x3a, 0x33, 0x33, 0x3a, 0x39, 0x62, 0x3a, 0x34, 0x63, + 0x3a, 0x37, 0x34, 0x3a, 0x37, 0x38, 0x3a, 0x37, 0x33, 0x3a, 0x64, 0x34, + 0x3a, 0x36, 0x63, 0x3a, 0x65, 0x37, 0x3a, 0x63, 0x31, 0x3a, 0x66, 0x33, + 0x3a, 0x38, 0x64, 0x3a, 0x63, 0x62, 0x3a, 0x35, 0x63, 0x3a, 0x65, 0x39, + 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, + 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x38, 0x35, 0x3a, + 0x33, 0x37, 0x3a, 0x31, 0x63, 0x3a, 0x61, 0x36, 0x3a, 0x65, 0x35, 0x3a, + 0x35, 0x30, 0x3a, 0x31, 0x34, 0x3a, 0x33, 0x64, 0x3a, 0x63, 0x65, 0x3a, + 0x32, 0x38, 0x3a, 0x30, 0x33, 0x3a, 0x34, 0x37, 0x3a, 0x31, 0x62, 0x3a, + 0x64, 0x65, 0x3a, 0x33, 0x61, 0x3a, 0x30, 0x39, 0x3a, 0x65, 0x38, 0x3a, + 0x66, 0x38, 0x3a, 0x37, 0x37, 0x3a, 0x30, 0x66, 0x0a, 0x23, 0x20, 0x53, + 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x38, 0x33, 0x3a, 0x63, 0x65, + 0x3a, 0x33, 0x63, 0x3a, 0x31, 0x32, 0x3a, 0x32, 0x39, 0x3a, 0x36, 0x38, + 0x3a, 0x38, 0x61, 0x3a, 0x35, 0x39, 0x3a, 0x33, 0x64, 0x3a, 0x34, 0x38, + 0x3a, 0x35, 0x66, 0x3a, 0x38, 0x31, 0x3a, 0x39, 0x37, 0x3a, 0x33, 0x63, + 0x3a, 0x30, 0x66, 0x3a, 0x39, 0x31, 0x3a, 0x39, 0x35, 0x3a, 0x34, 0x33, + 0x3a, 0x31, 0x65, 0x3a, 0x64, 0x61, 0x3a, 0x33, 0x37, 0x3a, 0x63, 0x63, + 0x3a, 0x35, 0x65, 0x3a, 0x33, 0x36, 0x3a, 0x34, 0x33, 0x3a, 0x30, 0x65, + 0x3a, 0x37, 0x39, 0x3a, 0x63, 0x37, 0x3a, 0x61, 0x38, 0x3a, 0x38, 0x38, + 0x3a, 0x36, 0x33, 0x3a, 0x38, 0x62, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, + 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, + 0x49, 0x49, 0x44, 0x41, 0x6a, 0x43, 0x43, 0x41, 0x6d, 0x73, 0x43, 0x45, + 0x48, 0x33, 0x5a, 0x2f, 0x67, 0x66, 0x50, 0x71, 0x42, 0x36, 0x33, 0x45, + 0x48, 0x6c, 0x6e, 0x2b, 0x36, 0x65, 0x4a, 0x4e, 0x4d, 0x59, 0x77, 0x44, + 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, + 0x51, 0x45, 0x46, 0x42, 0x51, 0x41, 0x77, 0x67, 0x63, 0x45, 0x78, 0x43, + 0x7a, 0x41, 0x4a, 0x0a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x59, 0x54, + 0x41, 0x6c, 0x56, 0x54, 0x4d, 0x52, 0x63, 0x77, 0x46, 0x51, 0x59, 0x44, + 0x56, 0x51, 0x51, 0x4b, 0x45, 0x77, 0x35, 0x57, 0x5a, 0x58, 0x4a, 0x70, + 0x55, 0x32, 0x6c, 0x6e, 0x62, 0x69, 0x77, 0x67, 0x53, 0x57, 0x35, 0x6a, + 0x4c, 0x6a, 0x45, 0x38, 0x4d, 0x44, 0x6f, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x43, 0x78, 0x4d, 0x7a, 0x51, 0x32, 0x78, 0x68, 0x0a, 0x63, 0x33, 0x4d, + 0x67, 0x4d, 0x79, 0x42, 0x51, 0x64, 0x57, 0x4a, 0x73, 0x61, 0x57, 0x4d, + 0x67, 0x55, 0x48, 0x4a, 0x70, 0x62, 0x57, 0x46, 0x79, 0x65, 0x53, 0x42, + 0x44, 0x5a, 0x58, 0x4a, 0x30, 0x61, 0x57, 0x5a, 0x70, 0x59, 0x32, 0x46, + 0x30, 0x61, 0x57, 0x39, 0x75, 0x49, 0x45, 0x46, 0x31, 0x64, 0x47, 0x68, + 0x76, 0x63, 0x6d, 0x6c, 0x30, 0x65, 0x53, 0x41, 0x74, 0x49, 0x45, 0x63, + 0x79, 0x0a, 0x4d, 0x54, 0x6f, 0x77, 0x4f, 0x41, 0x59, 0x44, 0x56, 0x51, + 0x51, 0x4c, 0x45, 0x7a, 0x45, 0x6f, 0x59, 0x79, 0x6b, 0x67, 0x4d, 0x54, + 0x6b, 0x35, 0x4f, 0x43, 0x42, 0x57, 0x5a, 0x58, 0x4a, 0x70, 0x55, 0x32, + 0x6c, 0x6e, 0x62, 0x69, 0x77, 0x67, 0x53, 0x57, 0x35, 0x6a, 0x4c, 0x69, + 0x41, 0x74, 0x49, 0x45, 0x5a, 0x76, 0x63, 0x69, 0x42, 0x68, 0x64, 0x58, + 0x52, 0x6f, 0x62, 0x33, 0x4a, 0x70, 0x0a, 0x65, 0x6d, 0x56, 0x6b, 0x49, + 0x48, 0x56, 0x7a, 0x5a, 0x53, 0x42, 0x76, 0x62, 0x6d, 0x78, 0x35, 0x4d, + 0x52, 0x38, 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, + 0x78, 0x5a, 0x57, 0x5a, 0x58, 0x4a, 0x70, 0x55, 0x32, 0x6c, 0x6e, 0x62, + 0x69, 0x42, 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x4f, 0x5a, + 0x58, 0x52, 0x33, 0x62, 0x33, 0x4a, 0x72, 0x4d, 0x42, 0x34, 0x58, 0x0a, + 0x44, 0x54, 0x6b, 0x34, 0x4d, 0x44, 0x55, 0x78, 0x4f, 0x44, 0x41, 0x77, + 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x46, 0x6f, 0x58, 0x44, 0x54, 0x49, 0x34, + 0x4d, 0x44, 0x67, 0x77, 0x4d, 0x54, 0x49, 0x7a, 0x4e, 0x54, 0x6b, 0x31, + 0x4f, 0x56, 0x6f, 0x77, 0x67, 0x63, 0x45, 0x78, 0x43, 0x7a, 0x41, 0x4a, + 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x59, 0x54, 0x41, 0x6c, 0x56, 0x54, + 0x4d, 0x52, 0x63, 0x77, 0x0a, 0x46, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, + 0x4b, 0x45, 0x77, 0x35, 0x57, 0x5a, 0x58, 0x4a, 0x70, 0x55, 0x32, 0x6c, + 0x6e, 0x62, 0x69, 0x77, 0x67, 0x53, 0x57, 0x35, 0x6a, 0x4c, 0x6a, 0x45, + 0x38, 0x4d, 0x44, 0x6f, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x78, 0x4d, + 0x7a, 0x51, 0x32, 0x78, 0x68, 0x63, 0x33, 0x4d, 0x67, 0x4d, 0x79, 0x42, + 0x51, 0x64, 0x57, 0x4a, 0x73, 0x61, 0x57, 0x4d, 0x67, 0x0a, 0x55, 0x48, + 0x4a, 0x70, 0x62, 0x57, 0x46, 0x79, 0x65, 0x53, 0x42, 0x44, 0x5a, 0x58, + 0x4a, 0x30, 0x61, 0x57, 0x5a, 0x70, 0x59, 0x32, 0x46, 0x30, 0x61, 0x57, + 0x39, 0x75, 0x49, 0x45, 0x46, 0x31, 0x64, 0x47, 0x68, 0x76, 0x63, 0x6d, + 0x6c, 0x30, 0x65, 0x53, 0x41, 0x74, 0x49, 0x45, 0x63, 0x79, 0x4d, 0x54, + 0x6f, 0x77, 0x4f, 0x41, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, 0x7a, + 0x45, 0x6f, 0x0a, 0x59, 0x79, 0x6b, 0x67, 0x4d, 0x54, 0x6b, 0x35, 0x4f, + 0x43, 0x42, 0x57, 0x5a, 0x58, 0x4a, 0x70, 0x55, 0x32, 0x6c, 0x6e, 0x62, + 0x69, 0x77, 0x67, 0x53, 0x57, 0x35, 0x6a, 0x4c, 0x69, 0x41, 0x74, 0x49, + 0x45, 0x5a, 0x76, 0x63, 0x69, 0x42, 0x68, 0x64, 0x58, 0x52, 0x6f, 0x62, + 0x33, 0x4a, 0x70, 0x65, 0x6d, 0x56, 0x6b, 0x49, 0x48, 0x56, 0x7a, 0x5a, + 0x53, 0x42, 0x76, 0x62, 0x6d, 0x78, 0x35, 0x0a, 0x4d, 0x52, 0x38, 0x77, + 0x48, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, 0x78, 0x5a, 0x57, + 0x5a, 0x58, 0x4a, 0x70, 0x55, 0x32, 0x6c, 0x6e, 0x62, 0x69, 0x42, 0x55, + 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x4f, 0x5a, 0x58, 0x52, 0x33, + 0x62, 0x33, 0x4a, 0x72, 0x4d, 0x49, 0x47, 0x66, 0x4d, 0x41, 0x30, 0x47, + 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, + 0x0a, 0x41, 0x51, 0x55, 0x41, 0x41, 0x34, 0x47, 0x4e, 0x41, 0x44, 0x43, + 0x42, 0x69, 0x51, 0x4b, 0x42, 0x67, 0x51, 0x44, 0x4d, 0x58, 0x74, 0x45, + 0x52, 0x58, 0x56, 0x78, 0x70, 0x30, 0x4b, 0x76, 0x54, 0x75, 0x57, 0x70, + 0x4d, 0x6d, 0x52, 0x39, 0x5a, 0x6d, 0x44, 0x43, 0x4f, 0x46, 0x6f, 0x55, + 0x67, 0x52, 0x6d, 0x31, 0x48, 0x50, 0x39, 0x53, 0x46, 0x49, 0x49, 0x54, + 0x68, 0x62, 0x62, 0x50, 0x34, 0x0a, 0x70, 0x4f, 0x30, 0x4d, 0x38, 0x52, + 0x63, 0x50, 0x4f, 0x2f, 0x6d, 0x6e, 0x2b, 0x53, 0x58, 0x58, 0x77, 0x63, + 0x2b, 0x45, 0x59, 0x2f, 0x4a, 0x38, 0x59, 0x38, 0x2b, 0x69, 0x52, 0x2f, + 0x4c, 0x47, 0x57, 0x7a, 0x4f, 0x4f, 0x5a, 0x45, 0x41, 0x45, 0x61, 0x4d, + 0x47, 0x41, 0x75, 0x57, 0x51, 0x63, 0x52, 0x58, 0x66, 0x48, 0x32, 0x47, + 0x37, 0x31, 0x6c, 0x53, 0x6b, 0x38, 0x55, 0x4f, 0x67, 0x30, 0x0a, 0x31, + 0x33, 0x67, 0x66, 0x71, 0x4c, 0x70, 0x74, 0x51, 0x35, 0x47, 0x56, 0x6a, + 0x30, 0x56, 0x58, 0x58, 0x6e, 0x37, 0x46, 0x2b, 0x38, 0x71, 0x6b, 0x42, + 0x4f, 0x76, 0x71, 0x6c, 0x7a, 0x64, 0x55, 0x4d, 0x47, 0x2b, 0x37, 0x41, + 0x55, 0x63, 0x79, 0x4d, 0x38, 0x33, 0x63, 0x56, 0x35, 0x74, 0x6b, 0x61, + 0x57, 0x48, 0x34, 0x6d, 0x78, 0x30, 0x63, 0x69, 0x55, 0x39, 0x63, 0x5a, + 0x77, 0x49, 0x44, 0x0a, 0x41, 0x51, 0x41, 0x42, 0x4d, 0x41, 0x30, 0x47, + 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, + 0x42, 0x51, 0x55, 0x41, 0x41, 0x34, 0x47, 0x42, 0x41, 0x46, 0x46, 0x4e, + 0x7a, 0x62, 0x35, 0x63, 0x79, 0x35, 0x67, 0x5a, 0x6e, 0x42, 0x57, 0x79, + 0x41, 0x54, 0x6c, 0x34, 0x4c, 0x6b, 0x30, 0x50, 0x5a, 0x33, 0x42, 0x77, + 0x6d, 0x63, 0x59, 0x51, 0x57, 0x70, 0x53, 0x6b, 0x0a, 0x55, 0x30, 0x31, + 0x55, 0x62, 0x53, 0x75, 0x76, 0x44, 0x56, 0x31, 0x41, 0x69, 0x32, 0x54, + 0x54, 0x31, 0x2b, 0x37, 0x65, 0x56, 0x6d, 0x47, 0x53, 0x58, 0x36, 0x62, + 0x45, 0x48, 0x52, 0x42, 0x68, 0x4e, 0x74, 0x4d, 0x73, 0x4a, 0x7a, 0x7a, + 0x6f, 0x4b, 0x51, 0x6d, 0x35, 0x45, 0x57, 0x52, 0x30, 0x7a, 0x4c, 0x56, + 0x7a, 0x6e, 0x78, 0x78, 0x49, 0x71, 0x62, 0x78, 0x68, 0x41, 0x65, 0x37, + 0x69, 0x0a, 0x46, 0x36, 0x59, 0x4d, 0x34, 0x30, 0x41, 0x49, 0x4f, 0x77, + 0x37, 0x6e, 0x36, 0x30, 0x52, 0x7a, 0x4b, 0x70, 0x72, 0x78, 0x61, 0x5a, + 0x4c, 0x76, 0x63, 0x52, 0x54, 0x44, 0x4f, 0x61, 0x78, 0x78, 0x70, 0x35, + 0x45, 0x4a, 0x62, 0x2b, 0x52, 0x78, 0x42, 0x72, 0x4f, 0x36, 0x57, 0x56, + 0x63, 0x6d, 0x65, 0x51, 0x44, 0x32, 0x2b, 0x41, 0x32, 0x69, 0x4d, 0x7a, + 0x41, 0x6f, 0x31, 0x4b, 0x70, 0x59, 0x0a, 0x6f, 0x4a, 0x32, 0x64, 0x61, + 0x5a, 0x48, 0x39, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, + 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, + 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x47, 0x6c, 0x6f, 0x62, + 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, + 0x43, 0x41, 0x20, 0x4f, 0x3d, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, + 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x20, 0x4f, 0x55, + 0x3d, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x0a, 0x23, 0x20, 0x53, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x47, + 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x52, 0x6f, + 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x4f, 0x3d, 0x47, 0x6c, 0x6f, 0x62, + 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, + 0x20, 0x4f, 0x55, 0x3d, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x0a, + 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x47, 0x6c, + 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, + 0x74, 0x20, 0x43, 0x41, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, + 0x61, 0x6c, 0x3a, 0x20, 0x34, 0x38, 0x33, 0x35, 0x37, 0x30, 0x33, 0x32, + 0x37, 0x38, 0x34, 0x35, 0x39, 0x37, 0x30, 0x37, 0x36, 0x36, 0x39, 0x30, + 0x30, 0x35, 0x32, 0x30, 0x34, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, + 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, + 0x20, 0x33, 0x65, 0x3a, 0x34, 0x35, 0x3a, 0x35, 0x32, 0x3a, 0x31, 0x35, + 0x3a, 0x30, 0x39, 0x3a, 0x35, 0x31, 0x3a, 0x39, 0x32, 0x3a, 0x65, 0x31, + 0x3a, 0x62, 0x37, 0x3a, 0x35, 0x64, 0x3a, 0x33, 0x37, 0x3a, 0x39, 0x66, + 0x3a, 0x62, 0x31, 0x3a, 0x38, 0x37, 0x3a, 0x32, 0x39, 0x3a, 0x38, 0x61, + 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, + 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x62, 0x31, 0x3a, + 0x62, 0x63, 0x3a, 0x39, 0x36, 0x3a, 0x38, 0x62, 0x3a, 0x64, 0x34, 0x3a, + 0x66, 0x34, 0x3a, 0x39, 0x64, 0x3a, 0x36, 0x32, 0x3a, 0x32, 0x61, 0x3a, + 0x61, 0x38, 0x3a, 0x39, 0x61, 0x3a, 0x38, 0x31, 0x3a, 0x66, 0x32, 0x3a, + 0x31, 0x35, 0x3a, 0x30, 0x31, 0x3a, 0x35, 0x32, 0x3a, 0x61, 0x34, 0x3a, + 0x31, 0x64, 0x3a, 0x38, 0x32, 0x3a, 0x39, 0x63, 0x0a, 0x23, 0x20, 0x53, + 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x65, 0x62, 0x3a, 0x64, 0x34, + 0x3a, 0x31, 0x30, 0x3a, 0x34, 0x30, 0x3a, 0x65, 0x34, 0x3a, 0x62, 0x62, + 0x3a, 0x33, 0x65, 0x3a, 0x63, 0x37, 0x3a, 0x34, 0x32, 0x3a, 0x63, 0x39, + 0x3a, 0x65, 0x33, 0x3a, 0x38, 0x31, 0x3a, 0x64, 0x33, 0x3a, 0x31, 0x65, + 0x3a, 0x66, 0x32, 0x3a, 0x61, 0x34, 0x3a, 0x31, 0x61, 0x3a, 0x34, 0x38, + 0x3a, 0x62, 0x36, 0x3a, 0x36, 0x38, 0x3a, 0x35, 0x63, 0x3a, 0x39, 0x36, + 0x3a, 0x65, 0x37, 0x3a, 0x63, 0x65, 0x3a, 0x66, 0x33, 0x3a, 0x63, 0x31, + 0x3a, 0x64, 0x66, 0x3a, 0x36, 0x63, 0x3a, 0x64, 0x34, 0x3a, 0x33, 0x33, + 0x3a, 0x31, 0x63, 0x3a, 0x39, 0x39, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, + 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, + 0x49, 0x49, 0x44, 0x64, 0x54, 0x43, 0x43, 0x41, 0x6c, 0x32, 0x67, 0x41, + 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x4c, 0x42, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x42, 0x46, 0x55, 0x74, 0x61, 0x77, 0x35, 0x51, 0x77, 0x44, + 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, + 0x51, 0x45, 0x46, 0x42, 0x51, 0x41, 0x77, 0x56, 0x7a, 0x45, 0x4c, 0x4d, + 0x41, 0x6b, 0x47, 0x0a, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, + 0x51, 0x6b, 0x55, 0x78, 0x47, 0x54, 0x41, 0x58, 0x42, 0x67, 0x4e, 0x56, + 0x42, 0x41, 0x6f, 0x54, 0x45, 0x45, 0x64, 0x73, 0x62, 0x32, 0x4a, 0x68, + 0x62, 0x46, 0x4e, 0x70, 0x5a, 0x32, 0x34, 0x67, 0x62, 0x6e, 0x59, 0x74, + 0x63, 0x32, 0x45, 0x78, 0x45, 0x44, 0x41, 0x4f, 0x42, 0x67, 0x4e, 0x56, + 0x42, 0x41, 0x73, 0x54, 0x42, 0x31, 0x4a, 0x76, 0x0a, 0x62, 0x33, 0x51, + 0x67, 0x51, 0x30, 0x45, 0x78, 0x47, 0x7a, 0x41, 0x5a, 0x42, 0x67, 0x4e, + 0x56, 0x42, 0x41, 0x4d, 0x54, 0x45, 0x6b, 0x64, 0x73, 0x62, 0x32, 0x4a, + 0x68, 0x62, 0x46, 0x4e, 0x70, 0x5a, 0x32, 0x34, 0x67, 0x55, 0x6d, 0x39, + 0x76, 0x64, 0x43, 0x42, 0x44, 0x51, 0x54, 0x41, 0x65, 0x46, 0x77, 0x30, + 0x35, 0x4f, 0x44, 0x41, 0x35, 0x4d, 0x44, 0x45, 0x78, 0x4d, 0x6a, 0x41, + 0x77, 0x0a, 0x4d, 0x44, 0x42, 0x61, 0x46, 0x77, 0x30, 0x79, 0x4f, 0x44, + 0x41, 0x78, 0x4d, 0x6a, 0x67, 0x78, 0x4d, 0x6a, 0x41, 0x77, 0x4d, 0x44, + 0x42, 0x61, 0x4d, 0x46, 0x63, 0x78, 0x43, 0x7a, 0x41, 0x4a, 0x42, 0x67, + 0x4e, 0x56, 0x42, 0x41, 0x59, 0x54, 0x41, 0x6b, 0x4a, 0x46, 0x4d, 0x52, + 0x6b, 0x77, 0x46, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4b, 0x45, 0x78, + 0x42, 0x48, 0x62, 0x47, 0x39, 0x69, 0x0a, 0x59, 0x57, 0x78, 0x54, 0x61, + 0x57, 0x64, 0x75, 0x49, 0x47, 0x35, 0x32, 0x4c, 0x58, 0x4e, 0x68, 0x4d, + 0x52, 0x41, 0x77, 0x44, 0x67, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, + 0x77, 0x64, 0x53, 0x62, 0x32, 0x39, 0x30, 0x49, 0x45, 0x4e, 0x42, 0x4d, + 0x52, 0x73, 0x77, 0x47, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, + 0x78, 0x4a, 0x48, 0x62, 0x47, 0x39, 0x69, 0x59, 0x57, 0x78, 0x54, 0x0a, + 0x61, 0x57, 0x64, 0x75, 0x49, 0x46, 0x4a, 0x76, 0x62, 0x33, 0x51, 0x67, + 0x51, 0x30, 0x45, 0x77, 0x67, 0x67, 0x45, 0x69, 0x4d, 0x41, 0x30, 0x47, + 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, + 0x41, 0x51, 0x55, 0x41, 0x41, 0x34, 0x49, 0x42, 0x44, 0x77, 0x41, 0x77, + 0x67, 0x67, 0x45, 0x4b, 0x41, 0x6f, 0x49, 0x42, 0x41, 0x51, 0x44, 0x61, + 0x44, 0x75, 0x61, 0x5a, 0x0a, 0x6a, 0x63, 0x36, 0x6a, 0x34, 0x30, 0x2b, + 0x4b, 0x66, 0x76, 0x76, 0x78, 0x69, 0x34, 0x4d, 0x6c, 0x61, 0x2b, 0x70, + 0x49, 0x48, 0x2f, 0x45, 0x71, 0x73, 0x4c, 0x6d, 0x56, 0x45, 0x51, 0x53, + 0x39, 0x38, 0x47, 0x50, 0x52, 0x34, 0x6d, 0x64, 0x6d, 0x7a, 0x78, 0x7a, + 0x64, 0x7a, 0x78, 0x74, 0x49, 0x4b, 0x2b, 0x36, 0x4e, 0x69, 0x59, 0x36, + 0x61, 0x72, 0x79, 0x6d, 0x41, 0x5a, 0x61, 0x76, 0x70, 0x0a, 0x78, 0x79, + 0x30, 0x53, 0x79, 0x36, 0x73, 0x63, 0x54, 0x48, 0x41, 0x48, 0x6f, 0x54, + 0x30, 0x4b, 0x4d, 0x4d, 0x30, 0x56, 0x6a, 0x55, 0x2f, 0x34, 0x33, 0x64, + 0x53, 0x4d, 0x55, 0x42, 0x55, 0x63, 0x37, 0x31, 0x44, 0x75, 0x78, 0x43, + 0x37, 0x33, 0x2f, 0x4f, 0x6c, 0x53, 0x38, 0x70, 0x46, 0x39, 0x34, 0x47, + 0x33, 0x56, 0x4e, 0x54, 0x43, 0x4f, 0x58, 0x6b, 0x4e, 0x7a, 0x38, 0x6b, + 0x48, 0x70, 0x0a, 0x31, 0x57, 0x72, 0x6a, 0x73, 0x6f, 0x6b, 0x36, 0x56, + 0x6a, 0x6b, 0x34, 0x62, 0x77, 0x59, 0x38, 0x69, 0x47, 0x6c, 0x62, 0x4b, + 0x6b, 0x33, 0x46, 0x70, 0x31, 0x53, 0x34, 0x62, 0x49, 0x6e, 0x4d, 0x6d, + 0x2f, 0x6b, 0x38, 0x79, 0x75, 0x58, 0x39, 0x69, 0x66, 0x55, 0x53, 0x50, + 0x4a, 0x4a, 0x34, 0x6c, 0x74, 0x62, 0x63, 0x64, 0x47, 0x36, 0x54, 0x52, + 0x47, 0x48, 0x52, 0x6a, 0x63, 0x64, 0x47, 0x0a, 0x73, 0x6e, 0x55, 0x4f, + 0x68, 0x75, 0x67, 0x5a, 0x69, 0x74, 0x56, 0x74, 0x62, 0x4e, 0x56, 0x34, + 0x46, 0x70, 0x57, 0x69, 0x36, 0x63, 0x67, 0x4b, 0x4f, 0x4f, 0x76, 0x79, + 0x4a, 0x42, 0x4e, 0x50, 0x63, 0x31, 0x53, 0x54, 0x45, 0x34, 0x55, 0x36, + 0x47, 0x37, 0x77, 0x65, 0x4e, 0x4c, 0x57, 0x4c, 0x42, 0x59, 0x79, 0x35, + 0x64, 0x34, 0x75, 0x78, 0x32, 0x78, 0x38, 0x67, 0x6b, 0x61, 0x73, 0x4a, + 0x0a, 0x55, 0x32, 0x36, 0x51, 0x7a, 0x6e, 0x73, 0x33, 0x64, 0x4c, 0x6c, + 0x77, 0x52, 0x35, 0x45, 0x69, 0x55, 0x57, 0x4d, 0x57, 0x65, 0x61, 0x36, + 0x78, 0x72, 0x6b, 0x45, 0x6d, 0x43, 0x4d, 0x67, 0x5a, 0x4b, 0x39, 0x46, + 0x47, 0x71, 0x6b, 0x6a, 0x57, 0x5a, 0x43, 0x72, 0x58, 0x67, 0x7a, 0x54, + 0x2f, 0x4c, 0x43, 0x72, 0x42, 0x62, 0x42, 0x6c, 0x44, 0x53, 0x67, 0x65, + 0x46, 0x35, 0x39, 0x4e, 0x38, 0x0a, 0x39, 0x69, 0x46, 0x6f, 0x37, 0x2b, + 0x72, 0x79, 0x55, 0x70, 0x39, 0x2f, 0x6b, 0x35, 0x44, 0x50, 0x41, 0x67, + 0x4d, 0x42, 0x41, 0x41, 0x47, 0x6a, 0x51, 0x6a, 0x42, 0x41, 0x4d, 0x41, + 0x34, 0x47, 0x41, 0x31, 0x55, 0x64, 0x44, 0x77, 0x45, 0x42, 0x2f, 0x77, + 0x51, 0x45, 0x41, 0x77, 0x49, 0x42, 0x42, 0x6a, 0x41, 0x50, 0x42, 0x67, + 0x4e, 0x56, 0x48, 0x52, 0x4d, 0x42, 0x41, 0x66, 0x38, 0x45, 0x0a, 0x42, + 0x54, 0x41, 0x44, 0x41, 0x51, 0x48, 0x2f, 0x4d, 0x42, 0x30, 0x47, 0x41, + 0x31, 0x55, 0x64, 0x44, 0x67, 0x51, 0x57, 0x42, 0x42, 0x52, 0x67, 0x65, + 0x32, 0x59, 0x61, 0x52, 0x51, 0x32, 0x58, 0x79, 0x6f, 0x6c, 0x51, 0x4c, + 0x33, 0x30, 0x45, 0x7a, 0x54, 0x53, 0x6f, 0x2f, 0x2f, 0x7a, 0x39, 0x53, + 0x7a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, + 0x77, 0x30, 0x42, 0x0a, 0x41, 0x51, 0x55, 0x46, 0x41, 0x41, 0x4f, 0x43, + 0x41, 0x51, 0x45, 0x41, 0x31, 0x6e, 0x50, 0x6e, 0x66, 0x45, 0x39, 0x32, + 0x30, 0x49, 0x32, 0x2f, 0x37, 0x4c, 0x71, 0x69, 0x76, 0x6a, 0x54, 0x46, + 0x4b, 0x44, 0x4b, 0x31, 0x66, 0x50, 0x78, 0x73, 0x6e, 0x43, 0x77, 0x72, + 0x76, 0x51, 0x6d, 0x65, 0x55, 0x37, 0x39, 0x72, 0x58, 0x71, 0x6f, 0x52, + 0x53, 0x4c, 0x62, 0x6c, 0x43, 0x4b, 0x4f, 0x7a, 0x0a, 0x79, 0x6a, 0x31, + 0x68, 0x54, 0x64, 0x4e, 0x47, 0x43, 0x62, 0x4d, 0x2b, 0x77, 0x36, 0x44, + 0x6a, 0x59, 0x31, 0x55, 0x62, 0x38, 0x72, 0x72, 0x76, 0x72, 0x54, 0x6e, + 0x68, 0x51, 0x37, 0x6b, 0x34, 0x6f, 0x2b, 0x59, 0x76, 0x69, 0x69, 0x59, + 0x37, 0x37, 0x36, 0x42, 0x51, 0x56, 0x76, 0x6e, 0x47, 0x43, 0x76, 0x30, + 0x34, 0x7a, 0x63, 0x51, 0x4c, 0x63, 0x46, 0x47, 0x55, 0x6c, 0x35, 0x67, + 0x45, 0x0a, 0x33, 0x38, 0x4e, 0x66, 0x6c, 0x4e, 0x55, 0x56, 0x79, 0x52, + 0x52, 0x42, 0x6e, 0x4d, 0x52, 0x64, 0x64, 0x57, 0x51, 0x56, 0x44, 0x66, + 0x39, 0x56, 0x4d, 0x4f, 0x79, 0x47, 0x6a, 0x2f, 0x38, 0x4e, 0x37, 0x79, + 0x79, 0x35, 0x59, 0x30, 0x62, 0x32, 0x71, 0x76, 0x7a, 0x66, 0x76, 0x47, + 0x6e, 0x39, 0x4c, 0x68, 0x4a, 0x49, 0x5a, 0x4a, 0x72, 0x67, 0x6c, 0x66, + 0x43, 0x6d, 0x37, 0x79, 0x6d, 0x50, 0x0a, 0x41, 0x62, 0x45, 0x56, 0x74, + 0x51, 0x77, 0x64, 0x70, 0x66, 0x35, 0x70, 0x4c, 0x47, 0x6b, 0x6b, 0x65, + 0x42, 0x36, 0x7a, 0x70, 0x78, 0x78, 0x78, 0x59, 0x75, 0x37, 0x4b, 0x79, + 0x4a, 0x65, 0x73, 0x46, 0x31, 0x32, 0x4b, 0x77, 0x76, 0x68, 0x48, 0x68, + 0x6d, 0x34, 0x71, 0x78, 0x46, 0x59, 0x78, 0x6c, 0x64, 0x42, 0x6e, 0x69, + 0x59, 0x55, 0x72, 0x2b, 0x57, 0x79, 0x6d, 0x58, 0x55, 0x61, 0x64, 0x0a, + 0x44, 0x4b, 0x71, 0x43, 0x35, 0x4a, 0x6c, 0x52, 0x33, 0x58, 0x43, 0x33, + 0x32, 0x31, 0x59, 0x39, 0x59, 0x65, 0x52, 0x71, 0x34, 0x56, 0x7a, 0x57, + 0x39, 0x76, 0x34, 0x39, 0x33, 0x6b, 0x48, 0x4d, 0x42, 0x36, 0x35, 0x6a, + 0x55, 0x72, 0x39, 0x54, 0x55, 0x2f, 0x51, 0x72, 0x36, 0x63, 0x66, 0x39, + 0x74, 0x76, 0x65, 0x43, 0x58, 0x34, 0x58, 0x53, 0x51, 0x52, 0x6a, 0x62, + 0x67, 0x62, 0x4d, 0x45, 0x0a, 0x48, 0x4d, 0x55, 0x66, 0x70, 0x49, 0x42, + 0x76, 0x46, 0x53, 0x44, 0x4a, 0x33, 0x67, 0x79, 0x49, 0x43, 0x68, 0x33, + 0x57, 0x5a, 0x6c, 0x58, 0x69, 0x2f, 0x45, 0x6a, 0x4a, 0x4b, 0x53, 0x5a, + 0x70, 0x34, 0x41, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, + 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, + 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, + 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x47, 0x6c, + 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x4f, 0x3d, 0x47, + 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x4f, 0x55, + 0x3d, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, + 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x52, 0x32, + 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, + 0x43, 0x4e, 0x3d, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, + 0x6e, 0x20, 0x4f, 0x3d, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, + 0x67, 0x6e, 0x20, 0x4f, 0x55, 0x3d, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, + 0x53, 0x69, 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, + 0x20, 0x2d, 0x20, 0x52, 0x32, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, + 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x2d, + 0x20, 0x52, 0x32, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, + 0x6c, 0x3a, 0x20, 0x34, 0x38, 0x33, 0x35, 0x37, 0x30, 0x33, 0x32, 0x37, + 0x38, 0x34, 0x35, 0x39, 0x36, 0x38, 0x32, 0x38, 0x38, 0x35, 0x36, 0x35, + 0x38, 0x31, 0x32, 0x35, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x39, 0x34, 0x3a, 0x31, 0x34, 0x3a, 0x37, 0x37, 0x3a, 0x37, 0x65, 0x3a, + 0x33, 0x65, 0x3a, 0x35, 0x65, 0x3a, 0x66, 0x64, 0x3a, 0x38, 0x66, 0x3a, + 0x33, 0x30, 0x3a, 0x62, 0x64, 0x3a, 0x34, 0x31, 0x3a, 0x62, 0x30, 0x3a, + 0x63, 0x66, 0x3a, 0x65, 0x37, 0x3a, 0x64, 0x30, 0x3a, 0x33, 0x30, 0x0a, + 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x37, 0x35, 0x3a, 0x65, + 0x30, 0x3a, 0x61, 0x62, 0x3a, 0x62, 0x36, 0x3a, 0x31, 0x33, 0x3a, 0x38, + 0x35, 0x3a, 0x31, 0x32, 0x3a, 0x32, 0x37, 0x3a, 0x31, 0x63, 0x3a, 0x30, + 0x34, 0x3a, 0x66, 0x38, 0x3a, 0x35, 0x66, 0x3a, 0x64, 0x64, 0x3a, 0x64, + 0x65, 0x3a, 0x33, 0x38, 0x3a, 0x65, 0x34, 0x3a, 0x62, 0x37, 0x3a, 0x32, + 0x34, 0x3a, 0x32, 0x65, 0x3a, 0x66, 0x65, 0x0a, 0x23, 0x20, 0x53, 0x48, + 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, + 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x63, 0x61, 0x3a, 0x34, 0x32, 0x3a, + 0x64, 0x64, 0x3a, 0x34, 0x31, 0x3a, 0x37, 0x34, 0x3a, 0x35, 0x66, 0x3a, + 0x64, 0x30, 0x3a, 0x62, 0x38, 0x3a, 0x31, 0x65, 0x3a, 0x62, 0x39, 0x3a, + 0x30, 0x32, 0x3a, 0x33, 0x36, 0x3a, 0x32, 0x63, 0x3a, 0x66, 0x39, 0x3a, + 0x64, 0x38, 0x3a, 0x62, 0x66, 0x3a, 0x37, 0x31, 0x3a, 0x39, 0x64, 0x3a, + 0x61, 0x31, 0x3a, 0x62, 0x64, 0x3a, 0x31, 0x62, 0x3a, 0x31, 0x65, 0x3a, + 0x66, 0x63, 0x3a, 0x39, 0x34, 0x3a, 0x36, 0x66, 0x3a, 0x35, 0x62, 0x3a, + 0x34, 0x63, 0x3a, 0x39, 0x39, 0x3a, 0x66, 0x34, 0x3a, 0x32, 0x63, 0x3a, + 0x31, 0x62, 0x3a, 0x39, 0x65, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, + 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, + 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, + 0x49, 0x44, 0x75, 0x6a, 0x43, 0x43, 0x41, 0x71, 0x4b, 0x67, 0x41, 0x77, + 0x49, 0x42, 0x41, 0x67, 0x49, 0x4c, 0x42, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x42, 0x44, 0x34, 0x59, 0x6d, 0x35, 0x67, 0x30, 0x77, 0x44, 0x51, + 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, + 0x45, 0x46, 0x42, 0x51, 0x41, 0x77, 0x54, 0x44, 0x45, 0x67, 0x4d, 0x42, + 0x34, 0x47, 0x0a, 0x41, 0x31, 0x55, 0x45, 0x43, 0x78, 0x4d, 0x58, 0x52, + 0x32, 0x78, 0x76, 0x59, 0x6d, 0x46, 0x73, 0x55, 0x32, 0x6c, 0x6e, 0x62, + 0x69, 0x42, 0x53, 0x62, 0x32, 0x39, 0x30, 0x49, 0x45, 0x4e, 0x42, 0x49, + 0x43, 0x30, 0x67, 0x55, 0x6a, 0x49, 0x78, 0x45, 0x7a, 0x41, 0x52, 0x42, + 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, 0x43, 0x6b, 0x64, 0x73, 0x62, + 0x32, 0x4a, 0x68, 0x62, 0x46, 0x4e, 0x70, 0x0a, 0x5a, 0x32, 0x34, 0x78, + 0x45, 0x7a, 0x41, 0x52, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x4d, 0x54, + 0x43, 0x6b, 0x64, 0x73, 0x62, 0x32, 0x4a, 0x68, 0x62, 0x46, 0x4e, 0x70, + 0x5a, 0x32, 0x34, 0x77, 0x48, 0x68, 0x63, 0x4e, 0x4d, 0x44, 0x59, 0x78, + 0x4d, 0x6a, 0x45, 0x31, 0x4d, 0x44, 0x67, 0x77, 0x4d, 0x44, 0x41, 0x77, + 0x57, 0x68, 0x63, 0x4e, 0x4d, 0x6a, 0x45, 0x78, 0x4d, 0x6a, 0x45, 0x31, + 0x0a, 0x4d, 0x44, 0x67, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x57, 0x6a, 0x42, + 0x4d, 0x4d, 0x53, 0x41, 0x77, 0x48, 0x67, 0x59, 0x44, 0x56, 0x51, 0x51, + 0x4c, 0x45, 0x78, 0x64, 0x48, 0x62, 0x47, 0x39, 0x69, 0x59, 0x57, 0x78, + 0x54, 0x61, 0x57, 0x64, 0x75, 0x49, 0x46, 0x4a, 0x76, 0x62, 0x33, 0x51, + 0x67, 0x51, 0x30, 0x45, 0x67, 0x4c, 0x53, 0x42, 0x53, 0x4d, 0x6a, 0x45, + 0x54, 0x4d, 0x42, 0x45, 0x47, 0x0a, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, + 0x4d, 0x4b, 0x52, 0x32, 0x78, 0x76, 0x59, 0x6d, 0x46, 0x73, 0x55, 0x32, + 0x6c, 0x6e, 0x62, 0x6a, 0x45, 0x54, 0x4d, 0x42, 0x45, 0x47, 0x41, 0x31, + 0x55, 0x45, 0x41, 0x78, 0x4d, 0x4b, 0x52, 0x32, 0x78, 0x76, 0x59, 0x6d, + 0x46, 0x73, 0x55, 0x32, 0x6c, 0x6e, 0x62, 0x6a, 0x43, 0x43, 0x41, 0x53, + 0x49, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x0a, 0x68, + 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x42, 0x42, 0x51, 0x41, 0x44, 0x67, + 0x67, 0x45, 0x50, 0x41, 0x44, 0x43, 0x43, 0x41, 0x51, 0x6f, 0x43, 0x67, + 0x67, 0x45, 0x42, 0x41, 0x4b, 0x62, 0x50, 0x4a, 0x41, 0x36, 0x2b, 0x4c, + 0x6d, 0x38, 0x6f, 0x6d, 0x55, 0x56, 0x43, 0x78, 0x4b, 0x73, 0x2b, 0x49, + 0x56, 0x53, 0x62, 0x43, 0x39, 0x4e, 0x2f, 0x68, 0x48, 0x44, 0x36, 0x45, + 0x72, 0x50, 0x4c, 0x0a, 0x76, 0x34, 0x64, 0x66, 0x78, 0x6e, 0x2b, 0x47, + 0x30, 0x37, 0x49, 0x77, 0x58, 0x4e, 0x62, 0x39, 0x72, 0x66, 0x46, 0x37, + 0x33, 0x4f, 0x58, 0x34, 0x59, 0x4a, 0x59, 0x4a, 0x6b, 0x68, 0x44, 0x31, + 0x30, 0x46, 0x50, 0x65, 0x2b, 0x33, 0x74, 0x2b, 0x63, 0x34, 0x69, 0x73, + 0x55, 0x6f, 0x68, 0x37, 0x53, 0x71, 0x62, 0x4b, 0x53, 0x61, 0x5a, 0x65, + 0x71, 0x4b, 0x65, 0x4d, 0x57, 0x68, 0x47, 0x38, 0x0a, 0x65, 0x6f, 0x4c, + 0x72, 0x76, 0x6f, 0x7a, 0x70, 0x73, 0x36, 0x79, 0x57, 0x4a, 0x51, 0x65, + 0x58, 0x53, 0x70, 0x6b, 0x71, 0x42, 0x79, 0x2b, 0x30, 0x48, 0x6e, 0x65, + 0x2f, 0x69, 0x67, 0x2b, 0x31, 0x41, 0x6e, 0x77, 0x62, 0x6c, 0x72, 0x6a, + 0x46, 0x75, 0x54, 0x6f, 0x73, 0x76, 0x4e, 0x59, 0x53, 0x75, 0x65, 0x74, + 0x5a, 0x66, 0x65, 0x4c, 0x51, 0x42, 0x6f, 0x5a, 0x66, 0x58, 0x6b, 0x6c, + 0x71, 0x0a, 0x74, 0x54, 0x6c, 0x65, 0x69, 0x44, 0x54, 0x73, 0x76, 0x48, + 0x67, 0x4d, 0x43, 0x4a, 0x69, 0x45, 0x62, 0x4b, 0x6a, 0x4e, 0x53, 0x37, + 0x53, 0x67, 0x66, 0x51, 0x78, 0x35, 0x54, 0x66, 0x43, 0x34, 0x4c, 0x63, + 0x73, 0x68, 0x79, 0x74, 0x56, 0x73, 0x57, 0x33, 0x33, 0x68, 0x6f, 0x43, + 0x6d, 0x45, 0x6f, 0x66, 0x6e, 0x54, 0x6c, 0x45, 0x6e, 0x4c, 0x4a, 0x47, + 0x4b, 0x52, 0x49, 0x4c, 0x7a, 0x64, 0x0a, 0x43, 0x39, 0x58, 0x5a, 0x7a, + 0x50, 0x6e, 0x71, 0x4a, 0x77, 0x6f, 0x72, 0x63, 0x35, 0x48, 0x47, 0x6e, + 0x52, 0x75, 0x73, 0x79, 0x4d, 0x76, 0x6f, 0x34, 0x4b, 0x44, 0x30, 0x4c, + 0x35, 0x43, 0x4c, 0x54, 0x66, 0x75, 0x77, 0x4e, 0x68, 0x76, 0x32, 0x47, + 0x58, 0x71, 0x46, 0x34, 0x47, 0x33, 0x79, 0x59, 0x52, 0x4f, 0x49, 0x58, + 0x4a, 0x2f, 0x67, 0x6b, 0x77, 0x70, 0x52, 0x6c, 0x34, 0x70, 0x61, 0x0a, + 0x7a, 0x71, 0x2b, 0x72, 0x31, 0x66, 0x65, 0x71, 0x43, 0x61, 0x70, 0x67, + 0x76, 0x64, 0x7a, 0x5a, 0x58, 0x39, 0x39, 0x79, 0x71, 0x57, 0x41, 0x54, + 0x58, 0x67, 0x41, 0x42, 0x79, 0x55, 0x72, 0x36, 0x50, 0x36, 0x54, 0x71, + 0x42, 0x77, 0x4d, 0x68, 0x41, 0x6f, 0x36, 0x43, 0x79, 0x67, 0x50, 0x43, + 0x6d, 0x34, 0x38, 0x43, 0x41, 0x77, 0x45, 0x41, 0x41, 0x61, 0x4f, 0x42, + 0x6e, 0x44, 0x43, 0x42, 0x0a, 0x6d, 0x54, 0x41, 0x4f, 0x42, 0x67, 0x4e, + 0x56, 0x48, 0x51, 0x38, 0x42, 0x41, 0x66, 0x38, 0x45, 0x42, 0x41, 0x4d, + 0x43, 0x41, 0x51, 0x59, 0x77, 0x44, 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, + 0x54, 0x41, 0x51, 0x48, 0x2f, 0x42, 0x41, 0x55, 0x77, 0x41, 0x77, 0x45, + 0x42, 0x2f, 0x7a, 0x41, 0x64, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x34, + 0x45, 0x46, 0x67, 0x51, 0x55, 0x6d, 0x2b, 0x49, 0x48, 0x0a, 0x56, 0x32, + 0x63, 0x63, 0x48, 0x73, 0x42, 0x71, 0x42, 0x74, 0x35, 0x5a, 0x74, 0x4a, + 0x6f, 0x74, 0x33, 0x39, 0x77, 0x5a, 0x68, 0x69, 0x34, 0x77, 0x4e, 0x67, + 0x59, 0x44, 0x56, 0x52, 0x30, 0x66, 0x42, 0x43, 0x38, 0x77, 0x4c, 0x54, + 0x41, 0x72, 0x6f, 0x43, 0x6d, 0x67, 0x4a, 0x34, 0x59, 0x6c, 0x61, 0x48, + 0x52, 0x30, 0x63, 0x44, 0x6f, 0x76, 0x4c, 0x32, 0x4e, 0x79, 0x62, 0x43, + 0x35, 0x6e, 0x0a, 0x62, 0x47, 0x39, 0x69, 0x59, 0x57, 0x78, 0x7a, 0x61, + 0x57, 0x64, 0x75, 0x4c, 0x6d, 0x35, 0x6c, 0x64, 0x43, 0x39, 0x79, 0x62, + 0x32, 0x39, 0x30, 0x4c, 0x58, 0x49, 0x79, 0x4c, 0x6d, 0x4e, 0x79, 0x62, + 0x44, 0x41, 0x66, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x53, 0x4d, 0x45, 0x47, + 0x44, 0x41, 0x57, 0x67, 0x42, 0x53, 0x62, 0x34, 0x67, 0x64, 0x58, 0x5a, + 0x78, 0x77, 0x65, 0x77, 0x47, 0x6f, 0x47, 0x0a, 0x33, 0x6c, 0x6d, 0x30, + 0x6d, 0x69, 0x33, 0x66, 0x33, 0x42, 0x6d, 0x47, 0x4c, 0x6a, 0x41, 0x4e, + 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, + 0x41, 0x51, 0x55, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x51, 0x45, 0x41, + 0x6d, 0x59, 0x46, 0x54, 0x68, 0x78, 0x78, 0x6f, 0x6c, 0x34, 0x61, 0x52, + 0x37, 0x4f, 0x42, 0x4b, 0x75, 0x45, 0x51, 0x4c, 0x71, 0x34, 0x47, 0x73, + 0x0a, 0x4a, 0x30, 0x2f, 0x57, 0x77, 0x62, 0x67, 0x63, 0x51, 0x33, 0x69, + 0x7a, 0x44, 0x4a, 0x72, 0x38, 0x36, 0x69, 0x77, 0x38, 0x62, 0x6d, 0x45, + 0x62, 0x54, 0x55, 0x73, 0x70, 0x39, 0x5a, 0x38, 0x46, 0x48, 0x53, 0x62, + 0x42, 0x75, 0x4f, 0x6d, 0x44, 0x41, 0x47, 0x4a, 0x46, 0x74, 0x71, 0x6b, + 0x49, 0x6b, 0x37, 0x6d, 0x70, 0x4d, 0x30, 0x73, 0x59, 0x6d, 0x73, 0x4c, + 0x34, 0x68, 0x34, 0x68, 0x4f, 0x0a, 0x32, 0x39, 0x31, 0x78, 0x4e, 0x42, + 0x72, 0x42, 0x56, 0x4e, 0x70, 0x47, 0x50, 0x2b, 0x44, 0x54, 0x4b, 0x71, + 0x74, 0x74, 0x56, 0x43, 0x4c, 0x31, 0x4f, 0x6d, 0x4c, 0x4e, 0x49, 0x47, + 0x2b, 0x36, 0x4b, 0x59, 0x6e, 0x58, 0x33, 0x5a, 0x48, 0x75, 0x30, 0x31, + 0x79, 0x69, 0x50, 0x71, 0x46, 0x62, 0x51, 0x66, 0x58, 0x66, 0x35, 0x57, + 0x52, 0x44, 0x4c, 0x65, 0x6e, 0x56, 0x4f, 0x61, 0x76, 0x53, 0x0a, 0x6f, + 0x74, 0x2b, 0x33, 0x69, 0x39, 0x44, 0x41, 0x67, 0x42, 0x6b, 0x63, 0x52, + 0x63, 0x41, 0x74, 0x6a, 0x4f, 0x6a, 0x34, 0x4c, 0x61, 0x52, 0x30, 0x56, + 0x6b, 0x6e, 0x46, 0x42, 0x62, 0x56, 0x50, 0x46, 0x64, 0x35, 0x75, 0x52, + 0x48, 0x67, 0x35, 0x68, 0x36, 0x68, 0x2b, 0x75, 0x2f, 0x4e, 0x35, 0x47, + 0x4a, 0x47, 0x37, 0x39, 0x47, 0x2b, 0x64, 0x77, 0x66, 0x43, 0x4d, 0x4e, + 0x59, 0x78, 0x64, 0x0a, 0x41, 0x66, 0x76, 0x44, 0x62, 0x62, 0x6e, 0x76, + 0x52, 0x47, 0x31, 0x35, 0x52, 0x6a, 0x46, 0x2b, 0x43, 0x76, 0x36, 0x70, + 0x67, 0x73, 0x48, 0x2f, 0x37, 0x36, 0x74, 0x75, 0x49, 0x4d, 0x52, 0x51, + 0x79, 0x56, 0x2b, 0x64, 0x54, 0x5a, 0x73, 0x58, 0x6a, 0x41, 0x7a, 0x6c, + 0x41, 0x63, 0x6d, 0x67, 0x51, 0x57, 0x70, 0x7a, 0x55, 0x2f, 0x71, 0x6c, + 0x55, 0x4c, 0x52, 0x75, 0x4a, 0x51, 0x2f, 0x37, 0x0a, 0x54, 0x42, 0x6a, + 0x30, 0x2f, 0x56, 0x4c, 0x5a, 0x6a, 0x6d, 0x6d, 0x78, 0x36, 0x42, 0x45, + 0x50, 0x33, 0x6f, 0x6a, 0x59, 0x2b, 0x78, 0x31, 0x4a, 0x39, 0x36, 0x72, + 0x65, 0x6c, 0x63, 0x38, 0x67, 0x65, 0x4d, 0x4a, 0x67, 0x45, 0x74, 0x73, + 0x6c, 0x51, 0x49, 0x78, 0x71, 0x2f, 0x48, 0x35, 0x43, 0x4f, 0x45, 0x42, + 0x6b, 0x45, 0x76, 0x65, 0x65, 0x67, 0x65, 0x47, 0x54, 0x4c, 0x67, 0x3d, + 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, + 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, + 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, + 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x63, 0x65, 0x72, + 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x20, 0x4f, 0x3d, 0x56, 0x61, 0x6c, + 0x69, 0x43, 0x65, 0x72, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, + 0x4f, 0x55, 0x3d, 0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, + 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x31, 0x20, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x0a, + 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, + 0x4e, 0x3d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, + 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x20, 0x4f, 0x3d, 0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, + 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x56, + 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x43, 0x6c, 0x61, 0x73, + 0x73, 0x20, 0x31, 0x20, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x20, 0x56, + 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x0a, 0x23, 0x20, 0x4c, 0x61, + 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, + 0x72, 0x74, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x31, 0x20, 0x56, + 0x41, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, + 0x20, 0x31, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, + 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x36, 0x35, + 0x3a, 0x35, 0x38, 0x3a, 0x61, 0x62, 0x3a, 0x31, 0x35, 0x3a, 0x61, 0x64, + 0x3a, 0x35, 0x37, 0x3a, 0x36, 0x63, 0x3a, 0x31, 0x65, 0x3a, 0x61, 0x38, + 0x3a, 0x61, 0x37, 0x3a, 0x62, 0x35, 0x3a, 0x36, 0x39, 0x3a, 0x61, 0x63, + 0x3a, 0x62, 0x66, 0x3a, 0x66, 0x66, 0x3a, 0x65, 0x62, 0x0a, 0x23, 0x20, + 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, + 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x65, 0x35, 0x3a, 0x64, 0x66, 0x3a, + 0x37, 0x34, 0x3a, 0x33, 0x63, 0x3a, 0x62, 0x36, 0x3a, 0x30, 0x31, 0x3a, + 0x63, 0x34, 0x3a, 0x39, 0x62, 0x3a, 0x39, 0x38, 0x3a, 0x34, 0x33, 0x3a, + 0x64, 0x63, 0x3a, 0x61, 0x62, 0x3a, 0x38, 0x63, 0x3a, 0x65, 0x38, 0x3a, + 0x36, 0x61, 0x3a, 0x38, 0x31, 0x3a, 0x31, 0x30, 0x3a, 0x39, 0x66, 0x3a, + 0x65, 0x34, 0x3a, 0x38, 0x65, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, + 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x3a, 0x20, 0x66, 0x34, 0x3a, 0x63, 0x31, 0x3a, 0x34, 0x39, + 0x3a, 0x35, 0x35, 0x3a, 0x31, 0x61, 0x3a, 0x33, 0x30, 0x3a, 0x31, 0x33, + 0x3a, 0x61, 0x33, 0x3a, 0x35, 0x62, 0x3a, 0x63, 0x37, 0x3a, 0x62, 0x66, + 0x3a, 0x66, 0x65, 0x3a, 0x31, 0x37, 0x3a, 0x61, 0x37, 0x3a, 0x66, 0x33, + 0x3a, 0x34, 0x34, 0x3a, 0x39, 0x62, 0x3a, 0x63, 0x31, 0x3a, 0x61, 0x62, + 0x3a, 0x35, 0x62, 0x3a, 0x35, 0x61, 0x3a, 0x30, 0x61, 0x3a, 0x65, 0x37, + 0x3a, 0x34, 0x62, 0x3a, 0x30, 0x36, 0x3a, 0x63, 0x32, 0x3a, 0x33, 0x62, + 0x3a, 0x39, 0x30, 0x3a, 0x30, 0x30, 0x3a, 0x34, 0x63, 0x3a, 0x30, 0x31, + 0x3a, 0x30, 0x34, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, + 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, + 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x43, + 0x35, 0x7a, 0x43, 0x43, 0x41, 0x6c, 0x41, 0x43, 0x41, 0x51, 0x45, 0x77, + 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, + 0x41, 0x51, 0x45, 0x46, 0x42, 0x51, 0x41, 0x77, 0x67, 0x62, 0x73, 0x78, + 0x4a, 0x44, 0x41, 0x69, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x63, 0x54, + 0x47, 0x31, 0x5a, 0x68, 0x62, 0x47, 0x6c, 0x44, 0x5a, 0x58, 0x4a, 0x30, + 0x0a, 0x49, 0x46, 0x5a, 0x68, 0x62, 0x47, 0x6c, 0x6b, 0x59, 0x58, 0x52, + 0x70, 0x62, 0x32, 0x34, 0x67, 0x54, 0x6d, 0x56, 0x30, 0x64, 0x32, 0x39, + 0x79, 0x61, 0x7a, 0x45, 0x58, 0x4d, 0x42, 0x55, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x43, 0x68, 0x4d, 0x4f, 0x56, 0x6d, 0x46, 0x73, 0x61, 0x55, 0x4e, + 0x6c, 0x63, 0x6e, 0x51, 0x73, 0x49, 0x45, 0x6c, 0x75, 0x59, 0x79, 0x34, + 0x78, 0x4e, 0x54, 0x41, 0x7a, 0x0a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, + 0x73, 0x54, 0x4c, 0x46, 0x5a, 0x68, 0x62, 0x47, 0x6c, 0x44, 0x5a, 0x58, + 0x4a, 0x30, 0x49, 0x45, 0x4e, 0x73, 0x59, 0x58, 0x4e, 0x7a, 0x49, 0x44, + 0x45, 0x67, 0x55, 0x47, 0x39, 0x73, 0x61, 0x57, 0x4e, 0x35, 0x49, 0x46, + 0x5a, 0x68, 0x62, 0x47, 0x6c, 0x6b, 0x59, 0x58, 0x52, 0x70, 0x62, 0x32, + 0x34, 0x67, 0x51, 0x58, 0x56, 0x30, 0x61, 0x47, 0x39, 0x79, 0x0a, 0x61, + 0x58, 0x52, 0x35, 0x4d, 0x53, 0x45, 0x77, 0x48, 0x77, 0x59, 0x44, 0x56, + 0x51, 0x51, 0x44, 0x45, 0x78, 0x68, 0x6f, 0x64, 0x48, 0x52, 0x77, 0x4f, + 0x69, 0x38, 0x76, 0x64, 0x33, 0x64, 0x33, 0x4c, 0x6e, 0x5a, 0x68, 0x62, + 0x47, 0x6c, 0x6a, 0x5a, 0x58, 0x4a, 0x30, 0x4c, 0x6d, 0x4e, 0x76, 0x62, + 0x53, 0x38, 0x78, 0x49, 0x44, 0x41, 0x65, 0x42, 0x67, 0x6b, 0x71, 0x68, + 0x6b, 0x69, 0x47, 0x0a, 0x39, 0x77, 0x30, 0x42, 0x43, 0x51, 0x45, 0x57, + 0x45, 0x57, 0x6c, 0x75, 0x5a, 0x6d, 0x39, 0x41, 0x64, 0x6d, 0x46, 0x73, + 0x61, 0x57, 0x4e, 0x6c, 0x63, 0x6e, 0x51, 0x75, 0x59, 0x32, 0x39, 0x74, + 0x4d, 0x42, 0x34, 0x58, 0x44, 0x54, 0x6b, 0x35, 0x4d, 0x44, 0x59, 0x79, + 0x4e, 0x54, 0x49, 0x79, 0x4d, 0x6a, 0x4d, 0x30, 0x4f, 0x46, 0x6f, 0x58, + 0x44, 0x54, 0x45, 0x35, 0x4d, 0x44, 0x59, 0x79, 0x0a, 0x4e, 0x54, 0x49, + 0x79, 0x4d, 0x6a, 0x4d, 0x30, 0x4f, 0x46, 0x6f, 0x77, 0x67, 0x62, 0x73, + 0x78, 0x4a, 0x44, 0x41, 0x69, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x63, + 0x54, 0x47, 0x31, 0x5a, 0x68, 0x62, 0x47, 0x6c, 0x44, 0x5a, 0x58, 0x4a, + 0x30, 0x49, 0x46, 0x5a, 0x68, 0x62, 0x47, 0x6c, 0x6b, 0x59, 0x58, 0x52, + 0x70, 0x62, 0x32, 0x34, 0x67, 0x54, 0x6d, 0x56, 0x30, 0x64, 0x32, 0x39, + 0x79, 0x0a, 0x61, 0x7a, 0x45, 0x58, 0x4d, 0x42, 0x55, 0x47, 0x41, 0x31, + 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4f, 0x56, 0x6d, 0x46, 0x73, 0x61, 0x55, + 0x4e, 0x6c, 0x63, 0x6e, 0x51, 0x73, 0x49, 0x45, 0x6c, 0x75, 0x59, 0x79, + 0x34, 0x78, 0x4e, 0x54, 0x41, 0x7a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, + 0x73, 0x54, 0x4c, 0x46, 0x5a, 0x68, 0x62, 0x47, 0x6c, 0x44, 0x5a, 0x58, + 0x4a, 0x30, 0x49, 0x45, 0x4e, 0x73, 0x0a, 0x59, 0x58, 0x4e, 0x7a, 0x49, + 0x44, 0x45, 0x67, 0x55, 0x47, 0x39, 0x73, 0x61, 0x57, 0x4e, 0x35, 0x49, + 0x46, 0x5a, 0x68, 0x62, 0x47, 0x6c, 0x6b, 0x59, 0x58, 0x52, 0x70, 0x62, + 0x32, 0x34, 0x67, 0x51, 0x58, 0x56, 0x30, 0x61, 0x47, 0x39, 0x79, 0x61, + 0x58, 0x52, 0x35, 0x4d, 0x53, 0x45, 0x77, 0x48, 0x77, 0x59, 0x44, 0x56, + 0x51, 0x51, 0x44, 0x45, 0x78, 0x68, 0x6f, 0x64, 0x48, 0x52, 0x77, 0x0a, + 0x4f, 0x69, 0x38, 0x76, 0x64, 0x33, 0x64, 0x33, 0x4c, 0x6e, 0x5a, 0x68, + 0x62, 0x47, 0x6c, 0x6a, 0x5a, 0x58, 0x4a, 0x30, 0x4c, 0x6d, 0x4e, 0x76, + 0x62, 0x53, 0x38, 0x78, 0x49, 0x44, 0x41, 0x65, 0x42, 0x67, 0x6b, 0x71, + 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x43, 0x51, 0x45, 0x57, + 0x45, 0x57, 0x6c, 0x75, 0x5a, 0x6d, 0x39, 0x41, 0x64, 0x6d, 0x46, 0x73, + 0x61, 0x57, 0x4e, 0x6c, 0x0a, 0x63, 0x6e, 0x51, 0x75, 0x59, 0x32, 0x39, + 0x74, 0x4d, 0x49, 0x47, 0x66, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, + 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x41, 0x51, 0x55, + 0x41, 0x41, 0x34, 0x47, 0x4e, 0x41, 0x44, 0x43, 0x42, 0x69, 0x51, 0x4b, + 0x42, 0x67, 0x51, 0x44, 0x59, 0x57, 0x59, 0x4a, 0x36, 0x69, 0x62, 0x69, + 0x57, 0x75, 0x71, 0x59, 0x76, 0x61, 0x47, 0x39, 0x59, 0x0a, 0x4c, 0x71, + 0x64, 0x55, 0x48, 0x41, 0x5a, 0x75, 0x39, 0x4f, 0x71, 0x4e, 0x53, 0x4c, + 0x77, 0x78, 0x6c, 0x42, 0x66, 0x77, 0x38, 0x30, 0x36, 0x38, 0x73, 0x72, + 0x67, 0x31, 0x6b, 0x6e, 0x61, 0x77, 0x30, 0x4b, 0x57, 0x6c, 0x41, 0x64, + 0x63, 0x41, 0x41, 0x78, 0x49, 0x69, 0x47, 0x51, 0x6a, 0x34, 0x2f, 0x78, + 0x45, 0x6a, 0x6d, 0x38, 0x34, 0x48, 0x39, 0x62, 0x39, 0x70, 0x47, 0x69, + 0x62, 0x2b, 0x0a, 0x54, 0x75, 0x6e, 0x52, 0x66, 0x35, 0x30, 0x73, 0x51, + 0x42, 0x31, 0x5a, 0x61, 0x47, 0x36, 0x6d, 0x2b, 0x46, 0x69, 0x77, 0x6e, + 0x52, 0x71, 0x50, 0x30, 0x7a, 0x2f, 0x78, 0x33, 0x42, 0x6b, 0x47, 0x67, + 0x61, 0x67, 0x4f, 0x34, 0x44, 0x72, 0x64, 0x79, 0x46, 0x4e, 0x46, 0x43, + 0x51, 0x62, 0x6d, 0x44, 0x33, 0x44, 0x44, 0x2b, 0x6b, 0x43, 0x6d, 0x44, + 0x75, 0x4a, 0x57, 0x42, 0x51, 0x38, 0x59, 0x0a, 0x54, 0x66, 0x77, 0x67, + 0x67, 0x74, 0x46, 0x7a, 0x56, 0x58, 0x53, 0x4e, 0x64, 0x6e, 0x4b, 0x67, + 0x48, 0x5a, 0x30, 0x64, 0x77, 0x4e, 0x30, 0x2f, 0x63, 0x51, 0x49, 0x44, + 0x41, 0x51, 0x41, 0x42, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, + 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x42, 0x51, 0x55, 0x41, + 0x41, 0x34, 0x47, 0x42, 0x41, 0x46, 0x42, 0x6f, 0x50, 0x55, 0x6e, 0x30, + 0x0a, 0x4c, 0x42, 0x77, 0x47, 0x6c, 0x4e, 0x2b, 0x56, 0x59, 0x48, 0x2b, + 0x57, 0x65, 0x78, 0x66, 0x2b, 0x54, 0x33, 0x47, 0x74, 0x5a, 0x4d, 0x6a, + 0x64, 0x64, 0x39, 0x4c, 0x76, 0x57, 0x56, 0x58, 0x6f, 0x50, 0x2b, 0x69, + 0x4f, 0x42, 0x53, 0x6f, 0x68, 0x38, 0x67, 0x66, 0x53, 0x74, 0x61, 0x64, + 0x53, 0x2f, 0x70, 0x79, 0x78, 0x74, 0x75, 0x4a, 0x62, 0x64, 0x78, 0x64, + 0x41, 0x36, 0x6e, 0x4c, 0x57, 0x0a, 0x49, 0x38, 0x73, 0x6f, 0x67, 0x54, + 0x4c, 0x44, 0x41, 0x48, 0x6b, 0x59, 0x37, 0x46, 0x6b, 0x58, 0x69, 0x63, + 0x6e, 0x47, 0x61, 0x68, 0x35, 0x78, 0x79, 0x66, 0x32, 0x33, 0x64, 0x4b, + 0x55, 0x6c, 0x52, 0x57, 0x6e, 0x46, 0x53, 0x4b, 0x73, 0x5a, 0x34, 0x55, + 0x57, 0x4b, 0x4a, 0x57, 0x73, 0x5a, 0x37, 0x75, 0x57, 0x37, 0x45, 0x76, + 0x56, 0x2f, 0x39, 0x36, 0x61, 0x4e, 0x55, 0x63, 0x50, 0x77, 0x0a, 0x6e, + 0x58, 0x53, 0x33, 0x71, 0x54, 0x36, 0x67, 0x70, 0x66, 0x2b, 0x32, 0x53, + 0x51, 0x4d, 0x54, 0x32, 0x69, 0x4c, 0x4d, 0x37, 0x58, 0x47, 0x43, 0x4b, + 0x35, 0x6e, 0x50, 0x4f, 0x72, 0x66, 0x31, 0x4c, 0x58, 0x4c, 0x49, 0x0a, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, + 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, + 0x20, 0x43, 0x4e, 0x3d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, + 0x77, 0x77, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x20, 0x4f, 0x3d, 0x56, 0x61, 0x6c, 0x69, 0x43, + 0x65, 0x72, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, + 0x3d, 0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x43, 0x6c, + 0x61, 0x73, 0x73, 0x20, 0x32, 0x20, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x0a, 0x23, 0x20, + 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, + 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, + 0x61, 0x6c, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x20, 0x4f, 0x3d, 0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x2c, + 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x56, 0x61, 0x6c, + 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, + 0x32, 0x20, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x20, 0x56, 0x61, 0x6c, + 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, + 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x32, 0x20, 0x56, 0x41, 0x22, + 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x31, + 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x61, 0x39, 0x3a, 0x32, + 0x33, 0x3a, 0x37, 0x35, 0x3a, 0x39, 0x62, 0x3a, 0x62, 0x61, 0x3a, 0x34, + 0x39, 0x3a, 0x33, 0x36, 0x3a, 0x36, 0x65, 0x3a, 0x33, 0x31, 0x3a, 0x63, + 0x32, 0x3a, 0x64, 0x62, 0x3a, 0x66, 0x32, 0x3a, 0x65, 0x37, 0x3a, 0x36, + 0x36, 0x3a, 0x62, 0x61, 0x3a, 0x38, 0x37, 0x0a, 0x23, 0x20, 0x53, 0x48, + 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x3a, 0x20, 0x33, 0x31, 0x3a, 0x37, 0x61, 0x3a, 0x32, 0x61, + 0x3a, 0x64, 0x30, 0x3a, 0x37, 0x66, 0x3a, 0x32, 0x62, 0x3a, 0x33, 0x33, + 0x3a, 0x35, 0x65, 0x3a, 0x66, 0x35, 0x3a, 0x61, 0x31, 0x3a, 0x63, 0x33, + 0x3a, 0x34, 0x65, 0x3a, 0x34, 0x62, 0x3a, 0x35, 0x37, 0x3a, 0x65, 0x38, + 0x3a, 0x62, 0x37, 0x3a, 0x64, 0x38, 0x3a, 0x66, 0x31, 0x3a, 0x66, 0x63, + 0x3a, 0x61, 0x36, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, + 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, + 0x3a, 0x20, 0x35, 0x38, 0x3a, 0x64, 0x30, 0x3a, 0x31, 0x37, 0x3a, 0x32, + 0x37, 0x3a, 0x39, 0x63, 0x3a, 0x64, 0x34, 0x3a, 0x64, 0x63, 0x3a, 0x36, + 0x33, 0x3a, 0x61, 0x62, 0x3a, 0x64, 0x64, 0x3a, 0x62, 0x31, 0x3a, 0x39, + 0x36, 0x3a, 0x61, 0x36, 0x3a, 0x63, 0x39, 0x3a, 0x39, 0x30, 0x3a, 0x36, + 0x63, 0x3a, 0x33, 0x30, 0x3a, 0x63, 0x34, 0x3a, 0x65, 0x30, 0x3a, 0x38, + 0x37, 0x3a, 0x38, 0x33, 0x3a, 0x65, 0x61, 0x3a, 0x65, 0x38, 0x3a, 0x63, + 0x31, 0x3a, 0x36, 0x30, 0x3a, 0x39, 0x39, 0x3a, 0x35, 0x34, 0x3a, 0x64, + 0x36, 0x3a, 0x39, 0x33, 0x3a, 0x35, 0x35, 0x3a, 0x35, 0x39, 0x3a, 0x36, + 0x62, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, + 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x43, 0x35, 0x7a, + 0x43, 0x43, 0x41, 0x6c, 0x41, 0x43, 0x41, 0x51, 0x45, 0x77, 0x44, 0x51, + 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, + 0x45, 0x46, 0x42, 0x51, 0x41, 0x77, 0x67, 0x62, 0x73, 0x78, 0x4a, 0x44, + 0x41, 0x69, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x63, 0x54, 0x47, 0x31, + 0x5a, 0x68, 0x62, 0x47, 0x6c, 0x44, 0x5a, 0x58, 0x4a, 0x30, 0x0a, 0x49, + 0x46, 0x5a, 0x68, 0x62, 0x47, 0x6c, 0x6b, 0x59, 0x58, 0x52, 0x70, 0x62, + 0x32, 0x34, 0x67, 0x54, 0x6d, 0x56, 0x30, 0x64, 0x32, 0x39, 0x79, 0x61, + 0x7a, 0x45, 0x58, 0x4d, 0x42, 0x55, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, + 0x68, 0x4d, 0x4f, 0x56, 0x6d, 0x46, 0x73, 0x61, 0x55, 0x4e, 0x6c, 0x63, + 0x6e, 0x51, 0x73, 0x49, 0x45, 0x6c, 0x75, 0x59, 0x79, 0x34, 0x78, 0x4e, + 0x54, 0x41, 0x7a, 0x0a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, + 0x4c, 0x46, 0x5a, 0x68, 0x62, 0x47, 0x6c, 0x44, 0x5a, 0x58, 0x4a, 0x30, + 0x49, 0x45, 0x4e, 0x73, 0x59, 0x58, 0x4e, 0x7a, 0x49, 0x44, 0x49, 0x67, + 0x55, 0x47, 0x39, 0x73, 0x61, 0x57, 0x4e, 0x35, 0x49, 0x46, 0x5a, 0x68, + 0x62, 0x47, 0x6c, 0x6b, 0x59, 0x58, 0x52, 0x70, 0x62, 0x32, 0x34, 0x67, + 0x51, 0x58, 0x56, 0x30, 0x61, 0x47, 0x39, 0x79, 0x0a, 0x61, 0x58, 0x52, + 0x35, 0x4d, 0x53, 0x45, 0x77, 0x48, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, + 0x44, 0x45, 0x78, 0x68, 0x6f, 0x64, 0x48, 0x52, 0x77, 0x4f, 0x69, 0x38, + 0x76, 0x64, 0x33, 0x64, 0x33, 0x4c, 0x6e, 0x5a, 0x68, 0x62, 0x47, 0x6c, + 0x6a, 0x5a, 0x58, 0x4a, 0x30, 0x4c, 0x6d, 0x4e, 0x76, 0x62, 0x53, 0x38, + 0x78, 0x49, 0x44, 0x41, 0x65, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, + 0x47, 0x0a, 0x39, 0x77, 0x30, 0x42, 0x43, 0x51, 0x45, 0x57, 0x45, 0x57, + 0x6c, 0x75, 0x5a, 0x6d, 0x39, 0x41, 0x64, 0x6d, 0x46, 0x73, 0x61, 0x57, + 0x4e, 0x6c, 0x63, 0x6e, 0x51, 0x75, 0x59, 0x32, 0x39, 0x74, 0x4d, 0x42, + 0x34, 0x58, 0x44, 0x54, 0x6b, 0x35, 0x4d, 0x44, 0x59, 0x79, 0x4e, 0x6a, + 0x41, 0x77, 0x4d, 0x54, 0x6b, 0x31, 0x4e, 0x46, 0x6f, 0x58, 0x44, 0x54, + 0x45, 0x35, 0x4d, 0x44, 0x59, 0x79, 0x0a, 0x4e, 0x6a, 0x41, 0x77, 0x4d, + 0x54, 0x6b, 0x31, 0x4e, 0x46, 0x6f, 0x77, 0x67, 0x62, 0x73, 0x78, 0x4a, + 0x44, 0x41, 0x69, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x63, 0x54, 0x47, + 0x31, 0x5a, 0x68, 0x62, 0x47, 0x6c, 0x44, 0x5a, 0x58, 0x4a, 0x30, 0x49, + 0x46, 0x5a, 0x68, 0x62, 0x47, 0x6c, 0x6b, 0x59, 0x58, 0x52, 0x70, 0x62, + 0x32, 0x34, 0x67, 0x54, 0x6d, 0x56, 0x30, 0x64, 0x32, 0x39, 0x79, 0x0a, + 0x61, 0x7a, 0x45, 0x58, 0x4d, 0x42, 0x55, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x43, 0x68, 0x4d, 0x4f, 0x56, 0x6d, 0x46, 0x73, 0x61, 0x55, 0x4e, 0x6c, + 0x63, 0x6e, 0x51, 0x73, 0x49, 0x45, 0x6c, 0x75, 0x59, 0x79, 0x34, 0x78, + 0x4e, 0x54, 0x41, 0x7a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, + 0x4c, 0x46, 0x5a, 0x68, 0x62, 0x47, 0x6c, 0x44, 0x5a, 0x58, 0x4a, 0x30, + 0x49, 0x45, 0x4e, 0x73, 0x0a, 0x59, 0x58, 0x4e, 0x7a, 0x49, 0x44, 0x49, + 0x67, 0x55, 0x47, 0x39, 0x73, 0x61, 0x57, 0x4e, 0x35, 0x49, 0x46, 0x5a, + 0x68, 0x62, 0x47, 0x6c, 0x6b, 0x59, 0x58, 0x52, 0x70, 0x62, 0x32, 0x34, + 0x67, 0x51, 0x58, 0x56, 0x30, 0x61, 0x47, 0x39, 0x79, 0x61, 0x58, 0x52, + 0x35, 0x4d, 0x53, 0x45, 0x77, 0x48, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, + 0x44, 0x45, 0x78, 0x68, 0x6f, 0x64, 0x48, 0x52, 0x77, 0x0a, 0x4f, 0x69, + 0x38, 0x76, 0x64, 0x33, 0x64, 0x33, 0x4c, 0x6e, 0x5a, 0x68, 0x62, 0x47, + 0x6c, 0x6a, 0x5a, 0x58, 0x4a, 0x30, 0x4c, 0x6d, 0x4e, 0x76, 0x62, 0x53, + 0x38, 0x78, 0x49, 0x44, 0x41, 0x65, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, + 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x43, 0x51, 0x45, 0x57, 0x45, 0x57, + 0x6c, 0x75, 0x5a, 0x6d, 0x39, 0x41, 0x64, 0x6d, 0x46, 0x73, 0x61, 0x57, + 0x4e, 0x6c, 0x0a, 0x63, 0x6e, 0x51, 0x75, 0x59, 0x32, 0x39, 0x74, 0x4d, + 0x49, 0x47, 0x66, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, + 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x41, 0x51, 0x55, 0x41, 0x41, + 0x34, 0x47, 0x4e, 0x41, 0x44, 0x43, 0x42, 0x69, 0x51, 0x4b, 0x42, 0x67, + 0x51, 0x44, 0x4f, 0x4f, 0x6e, 0x48, 0x4b, 0x35, 0x61, 0x76, 0x49, 0x57, + 0x5a, 0x4a, 0x56, 0x31, 0x36, 0x76, 0x59, 0x0a, 0x64, 0x41, 0x37, 0x35, + 0x37, 0x74, 0x6e, 0x32, 0x56, 0x55, 0x64, 0x5a, 0x5a, 0x55, 0x63, 0x4f, + 0x42, 0x56, 0x58, 0x63, 0x36, 0x35, 0x67, 0x32, 0x50, 0x46, 0x78, 0x54, + 0x58, 0x64, 0x4d, 0x77, 0x7a, 0x7a, 0x6a, 0x73, 0x76, 0x55, 0x47, 0x4a, + 0x37, 0x53, 0x56, 0x43, 0x43, 0x53, 0x52, 0x72, 0x43, 0x6c, 0x36, 0x7a, + 0x66, 0x4e, 0x31, 0x53, 0x4c, 0x55, 0x7a, 0x6d, 0x31, 0x4e, 0x5a, 0x39, + 0x0a, 0x57, 0x6c, 0x6d, 0x70, 0x5a, 0x64, 0x52, 0x4a, 0x45, 0x79, 0x30, + 0x6b, 0x54, 0x52, 0x78, 0x51, 0x62, 0x37, 0x58, 0x42, 0x68, 0x56, 0x51, + 0x37, 0x2f, 0x6e, 0x48, 0x6b, 0x30, 0x31, 0x78, 0x43, 0x2b, 0x59, 0x44, + 0x67, 0x6b, 0x52, 0x6f, 0x4b, 0x57, 0x7a, 0x6b, 0x32, 0x5a, 0x2f, 0x4d, + 0x2f, 0x56, 0x58, 0x77, 0x62, 0x50, 0x37, 0x52, 0x66, 0x5a, 0x48, 0x4d, + 0x30, 0x34, 0x37, 0x51, 0x53, 0x0a, 0x76, 0x34, 0x64, 0x6b, 0x2b, 0x4e, + 0x6f, 0x53, 0x2f, 0x7a, 0x63, 0x6e, 0x77, 0x62, 0x4e, 0x44, 0x75, 0x2b, + 0x39, 0x37, 0x62, 0x69, 0x35, 0x70, 0x39, 0x77, 0x49, 0x44, 0x41, 0x51, + 0x41, 0x42, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, + 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x42, 0x51, 0x55, 0x41, 0x41, 0x34, + 0x47, 0x42, 0x41, 0x44, 0x74, 0x2f, 0x55, 0x47, 0x39, 0x76, 0x0a, 0x55, + 0x4a, 0x53, 0x5a, 0x53, 0x57, 0x49, 0x34, 0x4f, 0x42, 0x39, 0x4c, 0x2b, + 0x4b, 0x58, 0x49, 0x50, 0x71, 0x65, 0x43, 0x67, 0x66, 0x59, 0x72, 0x78, + 0x2b, 0x6a, 0x46, 0x7a, 0x75, 0x67, 0x36, 0x45, 0x49, 0x4c, 0x4c, 0x47, + 0x41, 0x43, 0x4f, 0x54, 0x62, 0x32, 0x6f, 0x57, 0x48, 0x2b, 0x68, 0x65, + 0x51, 0x43, 0x31, 0x75, 0x2b, 0x6d, 0x4e, 0x72, 0x30, 0x48, 0x5a, 0x44, + 0x7a, 0x54, 0x75, 0x0a, 0x49, 0x59, 0x45, 0x5a, 0x6f, 0x44, 0x4a, 0x4a, + 0x4b, 0x50, 0x54, 0x45, 0x6a, 0x6c, 0x62, 0x56, 0x55, 0x6a, 0x50, 0x39, + 0x55, 0x4e, 0x56, 0x2b, 0x6d, 0x57, 0x77, 0x44, 0x35, 0x4d, 0x6c, 0x4d, + 0x2f, 0x4d, 0x74, 0x73, 0x71, 0x32, 0x61, 0x7a, 0x53, 0x69, 0x47, 0x4d, + 0x35, 0x62, 0x55, 0x4d, 0x4d, 0x6a, 0x34, 0x51, 0x73, 0x73, 0x78, 0x73, + 0x6f, 0x64, 0x79, 0x61, 0x6d, 0x45, 0x77, 0x43, 0x0a, 0x57, 0x2f, 0x50, + 0x4f, 0x75, 0x5a, 0x36, 0x6c, 0x63, 0x67, 0x35, 0x4b, 0x74, 0x7a, 0x38, + 0x38, 0x35, 0x68, 0x5a, 0x6f, 0x2b, 0x4c, 0x37, 0x74, 0x64, 0x45, 0x79, + 0x38, 0x57, 0x39, 0x56, 0x69, 0x48, 0x30, 0x50, 0x64, 0x0a, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, + 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, + 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, + 0x4e, 0x3d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, + 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x20, 0x4f, 0x3d, 0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, + 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x56, + 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x43, 0x6c, 0x61, 0x73, + 0x73, 0x20, 0x33, 0x20, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x20, 0x56, + 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x0a, 0x23, 0x20, 0x53, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x68, 0x74, + 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x61, 0x6c, + 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x20, 0x4f, + 0x3d, 0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x2c, 0x20, 0x49, + 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x56, 0x61, 0x6c, 0x69, 0x43, + 0x65, 0x72, 0x74, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x74, 0x79, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x52, 0x53, 0x41, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x31, + 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, + 0x31, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, + 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x61, 0x32, 0x3a, + 0x36, 0x66, 0x3a, 0x35, 0x33, 0x3a, 0x62, 0x37, 0x3a, 0x65, 0x65, 0x3a, + 0x34, 0x30, 0x3a, 0x64, 0x62, 0x3a, 0x34, 0x61, 0x3a, 0x36, 0x38, 0x3a, + 0x65, 0x37, 0x3a, 0x66, 0x61, 0x3a, 0x31, 0x38, 0x3a, 0x64, 0x39, 0x3a, + 0x31, 0x30, 0x3a, 0x34, 0x62, 0x3a, 0x37, 0x32, 0x0a, 0x23, 0x20, 0x53, + 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, + 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x36, 0x39, 0x3a, 0x62, 0x64, 0x3a, 0x38, + 0x63, 0x3a, 0x66, 0x34, 0x3a, 0x39, 0x63, 0x3a, 0x64, 0x33, 0x3a, 0x30, + 0x30, 0x3a, 0x66, 0x62, 0x3a, 0x35, 0x39, 0x3a, 0x32, 0x65, 0x3a, 0x31, + 0x37, 0x3a, 0x39, 0x33, 0x3a, 0x63, 0x61, 0x3a, 0x35, 0x35, 0x3a, 0x36, + 0x61, 0x3a, 0x66, 0x33, 0x3a, 0x65, 0x63, 0x3a, 0x61, 0x61, 0x3a, 0x33, + 0x35, 0x3a, 0x66, 0x62, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, + 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, + 0x74, 0x3a, 0x20, 0x62, 0x63, 0x3a, 0x32, 0x33, 0x3a, 0x66, 0x39, 0x3a, + 0x38, 0x61, 0x3a, 0x33, 0x31, 0x3a, 0x33, 0x63, 0x3a, 0x62, 0x39, 0x3a, + 0x32, 0x64, 0x3a, 0x65, 0x33, 0x3a, 0x62, 0x62, 0x3a, 0x66, 0x63, 0x3a, + 0x33, 0x61, 0x3a, 0x35, 0x61, 0x3a, 0x39, 0x66, 0x3a, 0x34, 0x34, 0x3a, + 0x36, 0x31, 0x3a, 0x61, 0x63, 0x3a, 0x33, 0x39, 0x3a, 0x34, 0x39, 0x3a, + 0x34, 0x63, 0x3a, 0x34, 0x61, 0x3a, 0x65, 0x31, 0x3a, 0x35, 0x61, 0x3a, + 0x39, 0x65, 0x3a, 0x39, 0x64, 0x3a, 0x66, 0x31, 0x3a, 0x33, 0x31, 0x3a, + 0x65, 0x39, 0x3a, 0x39, 0x62, 0x3a, 0x37, 0x33, 0x3a, 0x30, 0x31, 0x3a, + 0x39, 0x61, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, + 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, + 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x43, 0x35, + 0x7a, 0x43, 0x43, 0x41, 0x6c, 0x41, 0x43, 0x41, 0x51, 0x45, 0x77, 0x44, + 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, + 0x51, 0x45, 0x46, 0x42, 0x51, 0x41, 0x77, 0x67, 0x62, 0x73, 0x78, 0x4a, + 0x44, 0x41, 0x69, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x63, 0x54, 0x47, + 0x31, 0x5a, 0x68, 0x62, 0x47, 0x6c, 0x44, 0x5a, 0x58, 0x4a, 0x30, 0x0a, + 0x49, 0x46, 0x5a, 0x68, 0x62, 0x47, 0x6c, 0x6b, 0x59, 0x58, 0x52, 0x70, + 0x62, 0x32, 0x34, 0x67, 0x54, 0x6d, 0x56, 0x30, 0x64, 0x32, 0x39, 0x79, + 0x61, 0x7a, 0x45, 0x58, 0x4d, 0x42, 0x55, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x43, 0x68, 0x4d, 0x4f, 0x56, 0x6d, 0x46, 0x73, 0x61, 0x55, 0x4e, 0x6c, + 0x63, 0x6e, 0x51, 0x73, 0x49, 0x45, 0x6c, 0x75, 0x59, 0x79, 0x34, 0x78, + 0x4e, 0x54, 0x41, 0x7a, 0x0a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, + 0x54, 0x4c, 0x46, 0x5a, 0x68, 0x62, 0x47, 0x6c, 0x44, 0x5a, 0x58, 0x4a, + 0x30, 0x49, 0x45, 0x4e, 0x73, 0x59, 0x58, 0x4e, 0x7a, 0x49, 0x44, 0x4d, + 0x67, 0x55, 0x47, 0x39, 0x73, 0x61, 0x57, 0x4e, 0x35, 0x49, 0x46, 0x5a, + 0x68, 0x62, 0x47, 0x6c, 0x6b, 0x59, 0x58, 0x52, 0x70, 0x62, 0x32, 0x34, + 0x67, 0x51, 0x58, 0x56, 0x30, 0x61, 0x47, 0x39, 0x79, 0x0a, 0x61, 0x58, + 0x52, 0x35, 0x4d, 0x53, 0x45, 0x77, 0x48, 0x77, 0x59, 0x44, 0x56, 0x51, + 0x51, 0x44, 0x45, 0x78, 0x68, 0x6f, 0x64, 0x48, 0x52, 0x77, 0x4f, 0x69, + 0x38, 0x76, 0x64, 0x33, 0x64, 0x33, 0x4c, 0x6e, 0x5a, 0x68, 0x62, 0x47, + 0x6c, 0x6a, 0x5a, 0x58, 0x4a, 0x30, 0x4c, 0x6d, 0x4e, 0x76, 0x62, 0x53, + 0x38, 0x78, 0x49, 0x44, 0x41, 0x65, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, + 0x69, 0x47, 0x0a, 0x39, 0x77, 0x30, 0x42, 0x43, 0x51, 0x45, 0x57, 0x45, + 0x57, 0x6c, 0x75, 0x5a, 0x6d, 0x39, 0x41, 0x64, 0x6d, 0x46, 0x73, 0x61, + 0x57, 0x4e, 0x6c, 0x63, 0x6e, 0x51, 0x75, 0x59, 0x32, 0x39, 0x74, 0x4d, + 0x42, 0x34, 0x58, 0x44, 0x54, 0x6b, 0x35, 0x4d, 0x44, 0x59, 0x79, 0x4e, + 0x6a, 0x41, 0x77, 0x4d, 0x6a, 0x49, 0x7a, 0x4d, 0x31, 0x6f, 0x58, 0x44, + 0x54, 0x45, 0x35, 0x4d, 0x44, 0x59, 0x79, 0x0a, 0x4e, 0x6a, 0x41, 0x77, + 0x4d, 0x6a, 0x49, 0x7a, 0x4d, 0x31, 0x6f, 0x77, 0x67, 0x62, 0x73, 0x78, + 0x4a, 0x44, 0x41, 0x69, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x63, 0x54, + 0x47, 0x31, 0x5a, 0x68, 0x62, 0x47, 0x6c, 0x44, 0x5a, 0x58, 0x4a, 0x30, + 0x49, 0x46, 0x5a, 0x68, 0x62, 0x47, 0x6c, 0x6b, 0x59, 0x58, 0x52, 0x70, + 0x62, 0x32, 0x34, 0x67, 0x54, 0x6d, 0x56, 0x30, 0x64, 0x32, 0x39, 0x79, + 0x0a, 0x61, 0x7a, 0x45, 0x58, 0x4d, 0x42, 0x55, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x43, 0x68, 0x4d, 0x4f, 0x56, 0x6d, 0x46, 0x73, 0x61, 0x55, 0x4e, + 0x6c, 0x63, 0x6e, 0x51, 0x73, 0x49, 0x45, 0x6c, 0x75, 0x59, 0x79, 0x34, + 0x78, 0x4e, 0x54, 0x41, 0x7a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, + 0x54, 0x4c, 0x46, 0x5a, 0x68, 0x62, 0x47, 0x6c, 0x44, 0x5a, 0x58, 0x4a, + 0x30, 0x49, 0x45, 0x4e, 0x73, 0x0a, 0x59, 0x58, 0x4e, 0x7a, 0x49, 0x44, + 0x4d, 0x67, 0x55, 0x47, 0x39, 0x73, 0x61, 0x57, 0x4e, 0x35, 0x49, 0x46, + 0x5a, 0x68, 0x62, 0x47, 0x6c, 0x6b, 0x59, 0x58, 0x52, 0x70, 0x62, 0x32, + 0x34, 0x67, 0x51, 0x58, 0x56, 0x30, 0x61, 0x47, 0x39, 0x79, 0x61, 0x58, + 0x52, 0x35, 0x4d, 0x53, 0x45, 0x77, 0x48, 0x77, 0x59, 0x44, 0x56, 0x51, + 0x51, 0x44, 0x45, 0x78, 0x68, 0x6f, 0x64, 0x48, 0x52, 0x77, 0x0a, 0x4f, + 0x69, 0x38, 0x76, 0x64, 0x33, 0x64, 0x33, 0x4c, 0x6e, 0x5a, 0x68, 0x62, + 0x47, 0x6c, 0x6a, 0x5a, 0x58, 0x4a, 0x30, 0x4c, 0x6d, 0x4e, 0x76, 0x62, + 0x53, 0x38, 0x78, 0x49, 0x44, 0x41, 0x65, 0x42, 0x67, 0x6b, 0x71, 0x68, + 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x43, 0x51, 0x45, 0x57, 0x45, + 0x57, 0x6c, 0x75, 0x5a, 0x6d, 0x39, 0x41, 0x64, 0x6d, 0x46, 0x73, 0x61, + 0x57, 0x4e, 0x6c, 0x0a, 0x63, 0x6e, 0x51, 0x75, 0x59, 0x32, 0x39, 0x74, + 0x4d, 0x49, 0x47, 0x66, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, + 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x41, 0x51, 0x55, 0x41, + 0x41, 0x34, 0x47, 0x4e, 0x41, 0x44, 0x43, 0x42, 0x69, 0x51, 0x4b, 0x42, + 0x67, 0x51, 0x44, 0x6a, 0x6d, 0x46, 0x47, 0x57, 0x48, 0x4f, 0x6a, 0x56, + 0x73, 0x51, 0x61, 0x42, 0x61, 0x6c, 0x66, 0x44, 0x0a, 0x63, 0x6e, 0x57, + 0x54, 0x71, 0x38, 0x2b, 0x65, 0x70, 0x76, 0x7a, 0x7a, 0x46, 0x6c, 0x4c, + 0x57, 0x4c, 0x55, 0x32, 0x66, 0x4e, 0x55, 0x53, 0x6f, 0x4c, 0x67, 0x52, + 0x4e, 0x42, 0x30, 0x6d, 0x4b, 0x4f, 0x43, 0x6e, 0x31, 0x64, 0x7a, 0x66, + 0x6e, 0x74, 0x36, 0x74, 0x64, 0x33, 0x7a, 0x5a, 0x78, 0x46, 0x4a, 0x6d, + 0x50, 0x33, 0x4d, 0x4b, 0x53, 0x38, 0x65, 0x64, 0x67, 0x6b, 0x70, 0x66, + 0x73, 0x0a, 0x32, 0x45, 0x6a, 0x63, 0x76, 0x38, 0x45, 0x43, 0x49, 0x4d, + 0x59, 0x6b, 0x70, 0x43, 0x68, 0x4d, 0x4d, 0x46, 0x70, 0x32, 0x62, 0x62, + 0x46, 0x63, 0x38, 0x39, 0x33, 0x65, 0x6e, 0x68, 0x42, 0x78, 0x6f, 0x59, + 0x6a, 0x48, 0x57, 0x35, 0x74, 0x42, 0x62, 0x63, 0x71, 0x77, 0x75, 0x49, + 0x34, 0x56, 0x37, 0x71, 0x30, 0x7a, 0x4b, 0x38, 0x39, 0x48, 0x42, 0x46, + 0x78, 0x31, 0x63, 0x51, 0x71, 0x59, 0x0a, 0x4a, 0x4a, 0x67, 0x70, 0x70, + 0x30, 0x6c, 0x5a, 0x70, 0x64, 0x33, 0x34, 0x74, 0x30, 0x4e, 0x69, 0x59, + 0x66, 0x50, 0x54, 0x34, 0x74, 0x42, 0x56, 0x50, 0x77, 0x49, 0x44, 0x41, + 0x51, 0x41, 0x42, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, + 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x42, 0x51, 0x55, 0x41, 0x41, + 0x34, 0x47, 0x42, 0x41, 0x46, 0x61, 0x37, 0x41, 0x6c, 0x69, 0x45, 0x0a, + 0x5a, 0x77, 0x67, 0x73, 0x33, 0x78, 0x2f, 0x62, 0x65, 0x30, 0x6b, 0x7a, + 0x39, 0x64, 0x4e, 0x6e, 0x6e, 0x66, 0x53, 0x30, 0x43, 0x68, 0x43, 0x7a, + 0x79, 0x63, 0x55, 0x73, 0x34, 0x70, 0x4a, 0x71, 0x63, 0x58, 0x67, 0x6e, + 0x38, 0x6e, 0x43, 0x44, 0x51, 0x74, 0x4d, 0x2b, 0x7a, 0x36, 0x6c, 0x55, + 0x39, 0x50, 0x48, 0x59, 0x6b, 0x68, 0x61, 0x4d, 0x30, 0x51, 0x54, 0x4c, + 0x53, 0x36, 0x76, 0x4a, 0x0a, 0x6e, 0x30, 0x57, 0x75, 0x50, 0x49, 0x71, + 0x70, 0x73, 0x48, 0x45, 0x7a, 0x58, 0x63, 0x6a, 0x46, 0x56, 0x39, 0x2b, + 0x76, 0x71, 0x44, 0x57, 0x7a, 0x66, 0x34, 0x6d, 0x48, 0x36, 0x65, 0x67, + 0x6c, 0x6b, 0x72, 0x68, 0x2f, 0x68, 0x58, 0x71, 0x75, 0x31, 0x72, 0x77, + 0x65, 0x4e, 0x31, 0x67, 0x71, 0x5a, 0x38, 0x6d, 0x52, 0x7a, 0x79, 0x71, + 0x42, 0x50, 0x75, 0x33, 0x47, 0x4f, 0x64, 0x2f, 0x41, 0x0a, 0x50, 0x68, + 0x6d, 0x63, 0x47, 0x63, 0x77, 0x54, 0x54, 0x59, 0x4a, 0x42, 0x74, 0x59, + 0x7a, 0x65, 0x34, 0x44, 0x31, 0x67, 0x43, 0x43, 0x41, 0x50, 0x52, 0x58, + 0x35, 0x72, 0x6f, 0x6e, 0x2b, 0x6a, 0x6a, 0x42, 0x58, 0x75, 0x0a, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, + 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, + 0x43, 0x4e, 0x3d, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, + 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, + 0x20, 0x47, 0x33, 0x20, 0x4f, 0x3d, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, + 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, + 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, + 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x28, + 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39, 0x20, 0x56, 0x65, 0x72, 0x69, + 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, + 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, + 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, + 0x43, 0x4e, 0x3d, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, + 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, + 0x20, 0x47, 0x33, 0x20, 0x4f, 0x3d, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, + 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, + 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, + 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x28, + 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39, 0x20, 0x56, 0x65, 0x72, 0x69, + 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, + 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, + 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x56, + 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, + 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, + 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x22, + 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x32, + 0x30, 0x36, 0x36, 0x38, 0x34, 0x36, 0x39, 0x36, 0x32, 0x37, 0x39, 0x34, + 0x37, 0x32, 0x33, 0x31, 0x30, 0x32, 0x35, 0x34, 0x32, 0x37, 0x37, 0x38, + 0x37, 0x30, 0x31, 0x38, 0x30, 0x39, 0x36, 0x36, 0x37, 0x32, 0x33, 0x34, + 0x31, 0x35, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, + 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x63, 0x64, + 0x3a, 0x36, 0x38, 0x3a, 0x62, 0x36, 0x3a, 0x61, 0x37, 0x3a, 0x63, 0x37, + 0x3a, 0x63, 0x34, 0x3a, 0x63, 0x65, 0x3a, 0x37, 0x35, 0x3a, 0x65, 0x30, + 0x3a, 0x31, 0x64, 0x3a, 0x34, 0x66, 0x3a, 0x35, 0x37, 0x3a, 0x34, 0x34, + 0x3a, 0x36, 0x31, 0x3a, 0x39, 0x32, 0x3a, 0x30, 0x39, 0x0a, 0x23, 0x20, + 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, + 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x31, 0x33, 0x3a, 0x32, 0x64, 0x3a, + 0x30, 0x64, 0x3a, 0x34, 0x35, 0x3a, 0x35, 0x33, 0x3a, 0x34, 0x62, 0x3a, + 0x36, 0x39, 0x3a, 0x39, 0x37, 0x3a, 0x63, 0x64, 0x3a, 0x62, 0x32, 0x3a, + 0x64, 0x35, 0x3a, 0x63, 0x33, 0x3a, 0x33, 0x39, 0x3a, 0x65, 0x32, 0x3a, + 0x35, 0x35, 0x3a, 0x37, 0x36, 0x3a, 0x36, 0x30, 0x3a, 0x39, 0x62, 0x3a, + 0x35, 0x63, 0x3a, 0x63, 0x36, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, + 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x3a, 0x20, 0x65, 0x62, 0x3a, 0x30, 0x34, 0x3a, 0x63, 0x66, + 0x3a, 0x35, 0x65, 0x3a, 0x62, 0x31, 0x3a, 0x66, 0x33, 0x3a, 0x39, 0x61, + 0x3a, 0x66, 0x61, 0x3a, 0x37, 0x36, 0x3a, 0x32, 0x66, 0x3a, 0x32, 0x62, + 0x3a, 0x62, 0x31, 0x3a, 0x32, 0x30, 0x3a, 0x66, 0x32, 0x3a, 0x39, 0x36, + 0x3a, 0x63, 0x62, 0x3a, 0x61, 0x35, 0x3a, 0x32, 0x30, 0x3a, 0x63, 0x31, + 0x3a, 0x62, 0x39, 0x3a, 0x37, 0x64, 0x3a, 0x62, 0x31, 0x3a, 0x35, 0x38, + 0x3a, 0x39, 0x35, 0x3a, 0x36, 0x35, 0x3a, 0x62, 0x38, 0x3a, 0x31, 0x63, + 0x3a, 0x62, 0x39, 0x3a, 0x61, 0x31, 0x3a, 0x37, 0x62, 0x3a, 0x37, 0x32, + 0x3a, 0x34, 0x34, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, + 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, + 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x45, + 0x47, 0x6a, 0x43, 0x43, 0x41, 0x77, 0x49, 0x43, 0x45, 0x51, 0x43, 0x62, + 0x66, 0x67, 0x5a, 0x4a, 0x6f, 0x7a, 0x35, 0x69, 0x75, 0x64, 0x58, 0x75, + 0x6b, 0x45, 0x68, 0x78, 0x4b, 0x65, 0x39, 0x58, 0x4d, 0x41, 0x30, 0x47, + 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, + 0x42, 0x51, 0x55, 0x41, 0x4d, 0x49, 0x48, 0x4b, 0x4d, 0x51, 0x73, 0x77, + 0x0a, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, + 0x56, 0x55, 0x7a, 0x45, 0x58, 0x4d, 0x42, 0x55, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x43, 0x68, 0x4d, 0x4f, 0x56, 0x6d, 0x56, 0x79, 0x61, 0x56, 0x4e, + 0x70, 0x5a, 0x32, 0x34, 0x73, 0x49, 0x45, 0x6c, 0x75, 0x59, 0x79, 0x34, + 0x78, 0x48, 0x7a, 0x41, 0x64, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, + 0x54, 0x46, 0x6c, 0x5a, 0x6c, 0x0a, 0x63, 0x6d, 0x6c, 0x54, 0x61, 0x57, + 0x64, 0x75, 0x49, 0x46, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x49, 0x45, + 0x35, 0x6c, 0x64, 0x48, 0x64, 0x76, 0x63, 0x6d, 0x73, 0x78, 0x4f, 0x6a, + 0x41, 0x34, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, 0x4d, 0x53, + 0x68, 0x6a, 0x4b, 0x53, 0x41, 0x78, 0x4f, 0x54, 0x6b, 0x35, 0x49, 0x46, + 0x5a, 0x6c, 0x63, 0x6d, 0x6c, 0x54, 0x61, 0x57, 0x64, 0x75, 0x0a, 0x4c, + 0x43, 0x42, 0x4a, 0x62, 0x6d, 0x4d, 0x75, 0x49, 0x43, 0x30, 0x67, 0x52, + 0x6d, 0x39, 0x79, 0x49, 0x47, 0x46, 0x31, 0x64, 0x47, 0x68, 0x76, 0x63, + 0x6d, 0x6c, 0x36, 0x5a, 0x57, 0x51, 0x67, 0x64, 0x58, 0x4e, 0x6c, 0x49, + 0x47, 0x39, 0x75, 0x62, 0x48, 0x6b, 0x78, 0x52, 0x54, 0x42, 0x44, 0x42, + 0x67, 0x4e, 0x56, 0x42, 0x41, 0x4d, 0x54, 0x50, 0x46, 0x5a, 0x6c, 0x63, + 0x6d, 0x6c, 0x54, 0x0a, 0x61, 0x57, 0x64, 0x75, 0x49, 0x45, 0x4e, 0x73, + 0x59, 0x58, 0x4e, 0x7a, 0x49, 0x44, 0x4d, 0x67, 0x55, 0x48, 0x56, 0x69, + 0x62, 0x47, 0x6c, 0x6a, 0x49, 0x46, 0x42, 0x79, 0x61, 0x57, 0x31, 0x68, + 0x63, 0x6e, 0x6b, 0x67, 0x51, 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, 0x6d, + 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, 0x6c, 0x76, 0x62, 0x69, 0x42, 0x42, + 0x64, 0x58, 0x52, 0x6f, 0x62, 0x33, 0x4a, 0x70, 0x0a, 0x64, 0x48, 0x6b, + 0x67, 0x4c, 0x53, 0x42, 0x48, 0x4d, 0x7a, 0x41, 0x65, 0x46, 0x77, 0x30, + 0x35, 0x4f, 0x54, 0x45, 0x77, 0x4d, 0x44, 0x45, 0x77, 0x4d, 0x44, 0x41, + 0x77, 0x4d, 0x44, 0x42, 0x61, 0x46, 0x77, 0x30, 0x7a, 0x4e, 0x6a, 0x41, + 0x33, 0x4d, 0x54, 0x59, 0x79, 0x4d, 0x7a, 0x55, 0x35, 0x4e, 0x54, 0x6c, + 0x61, 0x4d, 0x49, 0x48, 0x4b, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, + 0x44, 0x0a, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, 0x55, 0x7a, + 0x45, 0x58, 0x4d, 0x42, 0x55, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, + 0x4d, 0x4f, 0x56, 0x6d, 0x56, 0x79, 0x61, 0x56, 0x4e, 0x70, 0x5a, 0x32, + 0x34, 0x73, 0x49, 0x45, 0x6c, 0x75, 0x59, 0x79, 0x34, 0x78, 0x48, 0x7a, + 0x41, 0x64, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, 0x46, 0x6c, + 0x5a, 0x6c, 0x63, 0x6d, 0x6c, 0x54, 0x0a, 0x61, 0x57, 0x64, 0x75, 0x49, + 0x46, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x49, 0x45, 0x35, 0x6c, 0x64, + 0x48, 0x64, 0x76, 0x63, 0x6d, 0x73, 0x78, 0x4f, 0x6a, 0x41, 0x34, 0x42, + 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, 0x4d, 0x53, 0x68, 0x6a, 0x4b, + 0x53, 0x41, 0x78, 0x4f, 0x54, 0x6b, 0x35, 0x49, 0x46, 0x5a, 0x6c, 0x63, + 0x6d, 0x6c, 0x54, 0x61, 0x57, 0x64, 0x75, 0x4c, 0x43, 0x42, 0x4a, 0x0a, + 0x62, 0x6d, 0x4d, 0x75, 0x49, 0x43, 0x30, 0x67, 0x52, 0x6d, 0x39, 0x79, + 0x49, 0x47, 0x46, 0x31, 0x64, 0x47, 0x68, 0x76, 0x63, 0x6d, 0x6c, 0x36, + 0x5a, 0x57, 0x51, 0x67, 0x64, 0x58, 0x4e, 0x6c, 0x49, 0x47, 0x39, 0x75, + 0x62, 0x48, 0x6b, 0x78, 0x52, 0x54, 0x42, 0x44, 0x42, 0x67, 0x4e, 0x56, + 0x42, 0x41, 0x4d, 0x54, 0x50, 0x46, 0x5a, 0x6c, 0x63, 0x6d, 0x6c, 0x54, + 0x61, 0x57, 0x64, 0x75, 0x0a, 0x49, 0x45, 0x4e, 0x73, 0x59, 0x58, 0x4e, + 0x7a, 0x49, 0x44, 0x4d, 0x67, 0x55, 0x48, 0x56, 0x69, 0x62, 0x47, 0x6c, + 0x6a, 0x49, 0x46, 0x42, 0x79, 0x61, 0x57, 0x31, 0x68, 0x63, 0x6e, 0x6b, + 0x67, 0x51, 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, + 0x68, 0x64, 0x47, 0x6c, 0x76, 0x62, 0x69, 0x42, 0x42, 0x64, 0x58, 0x52, + 0x6f, 0x62, 0x33, 0x4a, 0x70, 0x64, 0x48, 0x6b, 0x67, 0x0a, 0x4c, 0x53, + 0x42, 0x48, 0x4d, 0x7a, 0x43, 0x43, 0x41, 0x53, 0x49, 0x77, 0x44, 0x51, + 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, + 0x45, 0x42, 0x42, 0x51, 0x41, 0x44, 0x67, 0x67, 0x45, 0x50, 0x41, 0x44, + 0x43, 0x43, 0x41, 0x51, 0x6f, 0x43, 0x67, 0x67, 0x45, 0x42, 0x41, 0x4d, + 0x75, 0x36, 0x6e, 0x46, 0x4c, 0x38, 0x65, 0x42, 0x38, 0x61, 0x48, 0x6d, + 0x38, 0x62, 0x0a, 0x4e, 0x33, 0x4f, 0x39, 0x2b, 0x4d, 0x6c, 0x72, 0x6c, + 0x42, 0x49, 0x77, 0x54, 0x2f, 0x41, 0x32, 0x52, 0x2f, 0x58, 0x51, 0x6b, + 0x51, 0x72, 0x31, 0x46, 0x38, 0x69, 0x6c, 0x59, 0x63, 0x45, 0x57, 0x51, + 0x45, 0x33, 0x37, 0x69, 0x6d, 0x47, 0x51, 0x35, 0x58, 0x59, 0x67, 0x77, + 0x52, 0x45, 0x47, 0x66, 0x61, 0x73, 0x73, 0x62, 0x71, 0x62, 0x31, 0x45, + 0x55, 0x47, 0x4f, 0x2b, 0x69, 0x32, 0x74, 0x0a, 0x4b, 0x6d, 0x46, 0x5a, + 0x70, 0x47, 0x63, 0x6d, 0x54, 0x4e, 0x44, 0x6f, 0x76, 0x46, 0x4a, 0x62, + 0x63, 0x43, 0x41, 0x45, 0x57, 0x4e, 0x46, 0x36, 0x79, 0x61, 0x52, 0x70, + 0x76, 0x49, 0x4d, 0x58, 0x5a, 0x4b, 0x30, 0x46, 0x69, 0x37, 0x7a, 0x51, + 0x57, 0x4d, 0x36, 0x4e, 0x6a, 0x50, 0x58, 0x72, 0x38, 0x45, 0x4a, 0x4a, + 0x43, 0x35, 0x32, 0x58, 0x4a, 0x32, 0x63, 0x79, 0x62, 0x75, 0x47, 0x75, + 0x0a, 0x6b, 0x78, 0x55, 0x63, 0x63, 0x4c, 0x77, 0x67, 0x54, 0x53, 0x38, + 0x59, 0x33, 0x70, 0x4b, 0x49, 0x36, 0x47, 0x79, 0x46, 0x56, 0x78, 0x45, + 0x61, 0x36, 0x58, 0x37, 0x6a, 0x4a, 0x68, 0x46, 0x55, 0x6f, 0x6b, 0x57, + 0x57, 0x56, 0x59, 0x50, 0x4b, 0x4d, 0x49, 0x6e, 0x6f, 0x33, 0x4e, 0x69, + 0x6a, 0x37, 0x53, 0x71, 0x41, 0x50, 0x33, 0x39, 0x35, 0x5a, 0x56, 0x63, + 0x2b, 0x46, 0x53, 0x42, 0x6d, 0x0a, 0x43, 0x43, 0x2b, 0x56, 0x6b, 0x37, + 0x2b, 0x71, 0x52, 0x79, 0x2b, 0x6f, 0x52, 0x70, 0x66, 0x77, 0x45, 0x75, + 0x4c, 0x2b, 0x77, 0x67, 0x6f, 0x72, 0x55, 0x65, 0x5a, 0x32, 0x35, 0x72, + 0x64, 0x47, 0x74, 0x2b, 0x49, 0x4e, 0x70, 0x73, 0x79, 0x6f, 0x77, 0x30, + 0x78, 0x5a, 0x56, 0x59, 0x6e, 0x6d, 0x36, 0x46, 0x4e, 0x63, 0x48, 0x4f, + 0x71, 0x64, 0x38, 0x47, 0x49, 0x57, 0x43, 0x36, 0x66, 0x4a, 0x0a, 0x58, + 0x77, 0x7a, 0x77, 0x33, 0x73, 0x4a, 0x32, 0x7a, 0x71, 0x2f, 0x33, 0x61, + 0x76, 0x4c, 0x36, 0x51, 0x61, 0x61, 0x69, 0x4d, 0x78, 0x54, 0x4a, 0x35, + 0x58, 0x70, 0x6a, 0x30, 0x35, 0x35, 0x69, 0x4e, 0x39, 0x57, 0x46, 0x5a, + 0x5a, 0x34, 0x4f, 0x35, 0x6c, 0x4d, 0x6b, 0x64, 0x42, 0x74, 0x65, 0x48, + 0x52, 0x4a, 0x54, 0x57, 0x38, 0x63, 0x73, 0x35, 0x34, 0x4e, 0x4a, 0x4f, + 0x78, 0x57, 0x75, 0x0a, 0x69, 0x6d, 0x69, 0x35, 0x56, 0x35, 0x63, 0x43, + 0x41, 0x77, 0x45, 0x41, 0x41, 0x54, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, + 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, + 0x41, 0x41, 0x4f, 0x43, 0x41, 0x51, 0x45, 0x41, 0x45, 0x52, 0x53, 0x57, + 0x77, 0x61, 0x75, 0x53, 0x43, 0x50, 0x63, 0x2f, 0x4c, 0x38, 0x6d, 0x79, + 0x2f, 0x75, 0x52, 0x61, 0x6e, 0x32, 0x54, 0x65, 0x0a, 0x32, 0x79, 0x46, + 0x50, 0x68, 0x70, 0x6b, 0x30, 0x64, 0x6a, 0x5a, 0x58, 0x33, 0x64, 0x41, + 0x56, 0x4c, 0x38, 0x57, 0x74, 0x66, 0x78, 0x55, 0x66, 0x4e, 0x32, 0x4a, + 0x7a, 0x50, 0x74, 0x54, 0x6e, 0x58, 0x38, 0x34, 0x58, 0x41, 0x39, 0x73, + 0x31, 0x2b, 0x69, 0x76, 0x62, 0x72, 0x6d, 0x41, 0x4a, 0x58, 0x78, 0x35, + 0x66, 0x6a, 0x32, 0x36, 0x37, 0x43, 0x7a, 0x33, 0x71, 0x57, 0x68, 0x4d, + 0x65, 0x0a, 0x44, 0x47, 0x42, 0x76, 0x74, 0x63, 0x43, 0x31, 0x49, 0x79, + 0x49, 0x75, 0x42, 0x77, 0x76, 0x4c, 0x71, 0x58, 0x54, 0x4c, 0x52, 0x37, + 0x73, 0x64, 0x77, 0x64, 0x65, 0x6c, 0x61, 0x38, 0x77, 0x76, 0x30, 0x6b, + 0x4c, 0x39, 0x53, 0x64, 0x32, 0x6e, 0x69, 0x63, 0x39, 0x54, 0x75, 0x74, + 0x6f, 0x41, 0x57, 0x69, 0x69, 0x2f, 0x67, 0x74, 0x2f, 0x34, 0x75, 0x68, + 0x4d, 0x64, 0x55, 0x49, 0x61, 0x43, 0x0a, 0x2f, 0x59, 0x34, 0x77, 0x6a, + 0x79, 0x6c, 0x47, 0x73, 0x42, 0x34, 0x39, 0x4e, 0x64, 0x6f, 0x34, 0x59, + 0x68, 0x59, 0x59, 0x53, 0x71, 0x33, 0x6d, 0x74, 0x6c, 0x46, 0x73, 0x33, + 0x71, 0x39, 0x69, 0x36, 0x77, 0x48, 0x51, 0x48, 0x69, 0x54, 0x2b, 0x65, + 0x6f, 0x38, 0x53, 0x47, 0x68, 0x4a, 0x6f, 0x75, 0x50, 0x74, 0x6d, 0x6d, + 0x52, 0x51, 0x55, 0x52, 0x56, 0x79, 0x75, 0x35, 0x36, 0x35, 0x70, 0x0a, + 0x46, 0x34, 0x45, 0x72, 0x57, 0x6a, 0x66, 0x4a, 0x58, 0x69, 0x72, 0x30, + 0x78, 0x75, 0x4b, 0x68, 0x58, 0x46, 0x53, 0x62, 0x70, 0x6c, 0x51, 0x41, + 0x7a, 0x2f, 0x44, 0x78, 0x77, 0x63, 0x65, 0x59, 0x4d, 0x42, 0x6f, 0x37, + 0x4e, 0x68, 0x62, 0x62, 0x6f, 0x32, 0x37, 0x71, 0x2f, 0x61, 0x32, 0x79, + 0x77, 0x74, 0x72, 0x76, 0x41, 0x6b, 0x63, 0x54, 0x69, 0x73, 0x44, 0x78, + 0x73, 0x7a, 0x47, 0x74, 0x0a, 0x54, 0x78, 0x7a, 0x68, 0x54, 0x35, 0x79, + 0x76, 0x44, 0x77, 0x79, 0x64, 0x39, 0x33, 0x67, 0x4e, 0x32, 0x50, 0x51, + 0x31, 0x56, 0x6f, 0x44, 0x61, 0x74, 0x32, 0x30, 0x58, 0x6a, 0x35, 0x30, + 0x65, 0x67, 0x57, 0x54, 0x68, 0x2f, 0x73, 0x56, 0x46, 0x75, 0x71, 0x31, + 0x72, 0x75, 0x51, 0x70, 0x36, 0x54, 0x6b, 0x39, 0x4c, 0x68, 0x4f, 0x35, + 0x4c, 0x38, 0x58, 0x33, 0x64, 0x45, 0x51, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, + 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, + 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, + 0x4e, 0x3d, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, + 0x6c, 0x61, 0x73, 0x73, 0x20, 0x34, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, + 0x47, 0x33, 0x20, 0x4f, 0x3d, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, + 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x56, + 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x28, 0x63, + 0x29, 0x20, 0x31, 0x39, 0x39, 0x39, 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, + 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, + 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x0a, + 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, + 0x4e, 0x3d, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, + 0x6c, 0x61, 0x73, 0x73, 0x20, 0x34, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, + 0x47, 0x33, 0x20, 0x4f, 0x3d, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, + 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x56, + 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x28, 0x63, + 0x29, 0x20, 0x31, 0x39, 0x39, 0x39, 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, + 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, + 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x0a, + 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x56, 0x65, + 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, + 0x20, 0x34, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, + 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x22, 0x0a, + 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x33, 0x31, + 0x34, 0x35, 0x33, 0x31, 0x39, 0x37, 0x32, 0x37, 0x31, 0x31, 0x39, 0x30, + 0x39, 0x34, 0x31, 0x33, 0x37, 0x34, 0x33, 0x30, 0x37, 0x35, 0x30, 0x39, + 0x36, 0x30, 0x33, 0x39, 0x33, 0x37, 0x38, 0x39, 0x33, 0x35, 0x35, 0x31, + 0x31, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, + 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x64, 0x62, 0x3a, + 0x63, 0x38, 0x3a, 0x66, 0x32, 0x3a, 0x32, 0x37, 0x3a, 0x32, 0x65, 0x3a, + 0x62, 0x31, 0x3a, 0x65, 0x61, 0x3a, 0x36, 0x61, 0x3a, 0x32, 0x39, 0x3a, + 0x32, 0x33, 0x3a, 0x35, 0x64, 0x3a, 0x66, 0x65, 0x3a, 0x35, 0x36, 0x3a, + 0x33, 0x65, 0x3a, 0x33, 0x33, 0x3a, 0x64, 0x66, 0x0a, 0x23, 0x20, 0x53, + 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, + 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x63, 0x38, 0x3a, 0x65, 0x63, 0x3a, 0x38, + 0x63, 0x3a, 0x38, 0x37, 0x3a, 0x39, 0x32, 0x3a, 0x36, 0x39, 0x3a, 0x63, + 0x62, 0x3a, 0x34, 0x62, 0x3a, 0x61, 0x62, 0x3a, 0x33, 0x39, 0x3a, 0x65, + 0x39, 0x3a, 0x38, 0x64, 0x3a, 0x37, 0x65, 0x3a, 0x35, 0x37, 0x3a, 0x36, + 0x37, 0x3a, 0x66, 0x33, 0x3a, 0x31, 0x34, 0x3a, 0x39, 0x35, 0x3a, 0x37, + 0x33, 0x3a, 0x39, 0x64, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, + 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, + 0x74, 0x3a, 0x20, 0x65, 0x33, 0x3a, 0x38, 0x39, 0x3a, 0x33, 0x36, 0x3a, + 0x30, 0x64, 0x3a, 0x30, 0x66, 0x3a, 0x64, 0x62, 0x3a, 0x61, 0x65, 0x3a, + 0x62, 0x33, 0x3a, 0x64, 0x32, 0x3a, 0x35, 0x30, 0x3a, 0x35, 0x38, 0x3a, + 0x34, 0x62, 0x3a, 0x34, 0x37, 0x3a, 0x33, 0x30, 0x3a, 0x33, 0x31, 0x3a, + 0x34, 0x65, 0x3a, 0x32, 0x32, 0x3a, 0x32, 0x66, 0x3a, 0x33, 0x39, 0x3a, + 0x63, 0x31, 0x3a, 0x35, 0x36, 0x3a, 0x61, 0x30, 0x3a, 0x32, 0x30, 0x3a, + 0x31, 0x34, 0x3a, 0x34, 0x65, 0x3a, 0x38, 0x64, 0x3a, 0x39, 0x36, 0x3a, + 0x30, 0x35, 0x3a, 0x36, 0x31, 0x3a, 0x37, 0x39, 0x3a, 0x31, 0x35, 0x3a, + 0x30, 0x36, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, + 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, + 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x45, 0x47, + 0x6a, 0x43, 0x43, 0x41, 0x77, 0x49, 0x43, 0x45, 0x51, 0x44, 0x73, 0x6f, + 0x4b, 0x65, 0x4c, 0x62, 0x6e, 0x56, 0x71, 0x41, 0x63, 0x2f, 0x45, 0x66, + 0x4d, 0x77, 0x76, 0x6c, 0x46, 0x37, 0x58, 0x4d, 0x41, 0x30, 0x47, 0x43, + 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x42, + 0x51, 0x55, 0x41, 0x4d, 0x49, 0x48, 0x4b, 0x4d, 0x51, 0x73, 0x77, 0x0a, + 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, + 0x55, 0x7a, 0x45, 0x58, 0x4d, 0x42, 0x55, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x43, 0x68, 0x4d, 0x4f, 0x56, 0x6d, 0x56, 0x79, 0x61, 0x56, 0x4e, 0x70, + 0x5a, 0x32, 0x34, 0x73, 0x49, 0x45, 0x6c, 0x75, 0x59, 0x79, 0x34, 0x78, + 0x48, 0x7a, 0x41, 0x64, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, + 0x46, 0x6c, 0x5a, 0x6c, 0x0a, 0x63, 0x6d, 0x6c, 0x54, 0x61, 0x57, 0x64, + 0x75, 0x49, 0x46, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x49, 0x45, 0x35, + 0x6c, 0x64, 0x48, 0x64, 0x76, 0x63, 0x6d, 0x73, 0x78, 0x4f, 0x6a, 0x41, + 0x34, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, 0x4d, 0x53, 0x68, + 0x6a, 0x4b, 0x53, 0x41, 0x78, 0x4f, 0x54, 0x6b, 0x35, 0x49, 0x46, 0x5a, + 0x6c, 0x63, 0x6d, 0x6c, 0x54, 0x61, 0x57, 0x64, 0x75, 0x0a, 0x4c, 0x43, + 0x42, 0x4a, 0x62, 0x6d, 0x4d, 0x75, 0x49, 0x43, 0x30, 0x67, 0x52, 0x6d, + 0x39, 0x79, 0x49, 0x47, 0x46, 0x31, 0x64, 0x47, 0x68, 0x76, 0x63, 0x6d, + 0x6c, 0x36, 0x5a, 0x57, 0x51, 0x67, 0x64, 0x58, 0x4e, 0x6c, 0x49, 0x47, + 0x39, 0x75, 0x62, 0x48, 0x6b, 0x78, 0x52, 0x54, 0x42, 0x44, 0x42, 0x67, + 0x4e, 0x56, 0x42, 0x41, 0x4d, 0x54, 0x50, 0x46, 0x5a, 0x6c, 0x63, 0x6d, + 0x6c, 0x54, 0x0a, 0x61, 0x57, 0x64, 0x75, 0x49, 0x45, 0x4e, 0x73, 0x59, + 0x58, 0x4e, 0x7a, 0x49, 0x44, 0x51, 0x67, 0x55, 0x48, 0x56, 0x69, 0x62, + 0x47, 0x6c, 0x6a, 0x49, 0x46, 0x42, 0x79, 0x61, 0x57, 0x31, 0x68, 0x63, + 0x6e, 0x6b, 0x67, 0x51, 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, + 0x57, 0x4e, 0x68, 0x64, 0x47, 0x6c, 0x76, 0x62, 0x69, 0x42, 0x42, 0x64, + 0x58, 0x52, 0x6f, 0x62, 0x33, 0x4a, 0x70, 0x0a, 0x64, 0x48, 0x6b, 0x67, + 0x4c, 0x53, 0x42, 0x48, 0x4d, 0x7a, 0x41, 0x65, 0x46, 0x77, 0x30, 0x35, + 0x4f, 0x54, 0x45, 0x77, 0x4d, 0x44, 0x45, 0x77, 0x4d, 0x44, 0x41, 0x77, + 0x4d, 0x44, 0x42, 0x61, 0x46, 0x77, 0x30, 0x7a, 0x4e, 0x6a, 0x41, 0x33, + 0x4d, 0x54, 0x59, 0x79, 0x4d, 0x7a, 0x55, 0x35, 0x4e, 0x54, 0x6c, 0x61, + 0x4d, 0x49, 0x48, 0x4b, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, + 0x0a, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, + 0x58, 0x4d, 0x42, 0x55, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, + 0x4f, 0x56, 0x6d, 0x56, 0x79, 0x61, 0x56, 0x4e, 0x70, 0x5a, 0x32, 0x34, + 0x73, 0x49, 0x45, 0x6c, 0x75, 0x59, 0x79, 0x34, 0x78, 0x48, 0x7a, 0x41, + 0x64, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, 0x46, 0x6c, 0x5a, + 0x6c, 0x63, 0x6d, 0x6c, 0x54, 0x0a, 0x61, 0x57, 0x64, 0x75, 0x49, 0x46, + 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x49, 0x45, 0x35, 0x6c, 0x64, 0x48, + 0x64, 0x76, 0x63, 0x6d, 0x73, 0x78, 0x4f, 0x6a, 0x41, 0x34, 0x42, 0x67, + 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, 0x4d, 0x53, 0x68, 0x6a, 0x4b, 0x53, + 0x41, 0x78, 0x4f, 0x54, 0x6b, 0x35, 0x49, 0x46, 0x5a, 0x6c, 0x63, 0x6d, + 0x6c, 0x54, 0x61, 0x57, 0x64, 0x75, 0x4c, 0x43, 0x42, 0x4a, 0x0a, 0x62, + 0x6d, 0x4d, 0x75, 0x49, 0x43, 0x30, 0x67, 0x52, 0x6d, 0x39, 0x79, 0x49, + 0x47, 0x46, 0x31, 0x64, 0x47, 0x68, 0x76, 0x63, 0x6d, 0x6c, 0x36, 0x5a, + 0x57, 0x51, 0x67, 0x64, 0x58, 0x4e, 0x6c, 0x49, 0x47, 0x39, 0x75, 0x62, + 0x48, 0x6b, 0x78, 0x52, 0x54, 0x42, 0x44, 0x42, 0x67, 0x4e, 0x56, 0x42, + 0x41, 0x4d, 0x54, 0x50, 0x46, 0x5a, 0x6c, 0x63, 0x6d, 0x6c, 0x54, 0x61, + 0x57, 0x64, 0x75, 0x0a, 0x49, 0x45, 0x4e, 0x73, 0x59, 0x58, 0x4e, 0x7a, + 0x49, 0x44, 0x51, 0x67, 0x55, 0x48, 0x56, 0x69, 0x62, 0x47, 0x6c, 0x6a, + 0x49, 0x46, 0x42, 0x79, 0x61, 0x57, 0x31, 0x68, 0x63, 0x6e, 0x6b, 0x67, + 0x51, 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, + 0x64, 0x47, 0x6c, 0x76, 0x62, 0x69, 0x42, 0x42, 0x64, 0x58, 0x52, 0x6f, + 0x62, 0x33, 0x4a, 0x70, 0x64, 0x48, 0x6b, 0x67, 0x0a, 0x4c, 0x53, 0x42, + 0x48, 0x4d, 0x7a, 0x43, 0x43, 0x41, 0x53, 0x49, 0x77, 0x44, 0x51, 0x59, + 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, + 0x42, 0x42, 0x51, 0x41, 0x44, 0x67, 0x67, 0x45, 0x50, 0x41, 0x44, 0x43, + 0x43, 0x41, 0x51, 0x6f, 0x43, 0x67, 0x67, 0x45, 0x42, 0x41, 0x4b, 0x33, + 0x4c, 0x70, 0x52, 0x46, 0x70, 0x78, 0x6c, 0x6d, 0x72, 0x38, 0x59, 0x2b, + 0x31, 0x0a, 0x47, 0x51, 0x39, 0x57, 0x7a, 0x73, 0x79, 0x31, 0x48, 0x79, + 0x44, 0x6b, 0x6e, 0x69, 0x59, 0x6c, 0x53, 0x2b, 0x42, 0x7a, 0x5a, 0x59, + 0x6c, 0x5a, 0x33, 0x74, 0x43, 0x44, 0x35, 0x50, 0x55, 0x50, 0x74, 0x62, + 0x75, 0x74, 0x38, 0x58, 0x7a, 0x6f, 0x49, 0x66, 0x7a, 0x6b, 0x36, 0x41, + 0x7a, 0x75, 0x66, 0x45, 0x55, 0x69, 0x47, 0x58, 0x61, 0x53, 0x74, 0x42, + 0x4f, 0x33, 0x49, 0x46, 0x73, 0x4a, 0x0a, 0x2b, 0x6d, 0x47, 0x75, 0x71, + 0x50, 0x4b, 0x6c, 0x6a, 0x59, 0x58, 0x43, 0x4b, 0x74, 0x62, 0x65, 0x5a, + 0x6a, 0x62, 0x53, 0x6d, 0x77, 0x4c, 0x30, 0x71, 0x4a, 0x4a, 0x67, 0x66, + 0x4a, 0x78, 0x70, 0x74, 0x49, 0x38, 0x6b, 0x48, 0x74, 0x43, 0x47, 0x55, + 0x76, 0x59, 0x79, 0x6e, 0x45, 0x46, 0x59, 0x48, 0x69, 0x4b, 0x39, 0x7a, + 0x55, 0x56, 0x69, 0x6c, 0x51, 0x68, 0x75, 0x30, 0x47, 0x62, 0x64, 0x0a, + 0x55, 0x36, 0x4c, 0x4d, 0x38, 0x42, 0x44, 0x63, 0x56, 0x48, 0x4f, 0x4c, + 0x42, 0x4b, 0x46, 0x47, 0x4d, 0x7a, 0x4e, 0x63, 0x46, 0x30, 0x43, 0x35, + 0x6e, 0x6b, 0x33, 0x54, 0x38, 0x37, 0x35, 0x56, 0x67, 0x2b, 0x69, 0x78, + 0x69, 0x59, 0x35, 0x61, 0x66, 0x4a, 0x71, 0x57, 0x49, 0x70, 0x41, 0x37, + 0x69, 0x43, 0x58, 0x79, 0x30, 0x6c, 0x4f, 0x49, 0x41, 0x67, 0x77, 0x4c, + 0x65, 0x50, 0x4c, 0x6d, 0x0a, 0x4e, 0x78, 0x64, 0x4c, 0x4d, 0x45, 0x59, + 0x48, 0x35, 0x49, 0x42, 0x74, 0x70, 0x74, 0x69, 0x57, 0x4c, 0x75, 0x67, + 0x73, 0x2b, 0x42, 0x47, 0x7a, 0x4f, 0x41, 0x31, 0x6d, 0x70, 0x70, 0x76, + 0x71, 0x79, 0x53, 0x4e, 0x62, 0x32, 0x34, 0x37, 0x69, 0x38, 0x78, 0x4f, + 0x4f, 0x47, 0x6c, 0x6b, 0x74, 0x71, 0x67, 0x4c, 0x77, 0x37, 0x4b, 0x53, + 0x48, 0x5a, 0x74, 0x7a, 0x42, 0x50, 0x2f, 0x58, 0x59, 0x0a, 0x75, 0x66, + 0x54, 0x73, 0x67, 0x73, 0x62, 0x53, 0x50, 0x5a, 0x55, 0x64, 0x35, 0x63, + 0x42, 0x50, 0x68, 0x4d, 0x6e, 0x5a, 0x6f, 0x30, 0x51, 0x6f, 0x42, 0x6d, + 0x72, 0x58, 0x52, 0x61, 0x7a, 0x77, 0x61, 0x32, 0x72, 0x76, 0x54, 0x6c, + 0x2f, 0x34, 0x45, 0x59, 0x49, 0x65, 0x4f, 0x47, 0x4d, 0x30, 0x5a, 0x6c, + 0x44, 0x55, 0x50, 0x70, 0x4e, 0x7a, 0x2b, 0x6a, 0x44, 0x44, 0x5a, 0x71, + 0x33, 0x2f, 0x0a, 0x6b, 0x79, 0x32, 0x58, 0x37, 0x77, 0x4d, 0x43, 0x41, + 0x77, 0x45, 0x41, 0x41, 0x54, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, + 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, + 0x41, 0x4f, 0x43, 0x41, 0x51, 0x45, 0x41, 0x6a, 0x2f, 0x6f, 0x6c, 0x61, + 0x30, 0x39, 0x62, 0x35, 0x4b, 0x52, 0x4f, 0x4a, 0x31, 0x57, 0x72, 0x49, + 0x68, 0x56, 0x5a, 0x50, 0x4d, 0x71, 0x31, 0x0a, 0x43, 0x74, 0x52, 0x4b, + 0x32, 0x36, 0x76, 0x64, 0x6f, 0x56, 0x39, 0x54, 0x78, 0x61, 0x42, 0x58, + 0x4f, 0x63, 0x4c, 0x4f, 0x52, 0x79, 0x75, 0x2b, 0x4f, 0x73, 0x68, 0x57, + 0x76, 0x38, 0x4c, 0x5a, 0x4a, 0x78, 0x41, 0x36, 0x73, 0x51, 0x55, 0x38, + 0x77, 0x48, 0x63, 0x78, 0x75, 0x7a, 0x72, 0x54, 0x42, 0x58, 0x74, 0x74, + 0x6d, 0x68, 0x77, 0x77, 0x6a, 0x49, 0x44, 0x4c, 0x6b, 0x35, 0x4d, 0x71, + 0x0a, 0x67, 0x36, 0x73, 0x46, 0x55, 0x59, 0x49, 0x43, 0x41, 0x42, 0x46, + 0x6e, 0x61, 0x2f, 0x4f, 0x49, 0x59, 0x55, 0x64, 0x66, 0x41, 0x35, 0x50, + 0x56, 0x57, 0x77, 0x33, 0x67, 0x38, 0x64, 0x53, 0x68, 0x4d, 0x6a, 0x57, + 0x46, 0x73, 0x6a, 0x72, 0x62, 0x73, 0x49, 0x4b, 0x72, 0x30, 0x63, 0x73, + 0x4b, 0x76, 0x45, 0x2b, 0x4d, 0x57, 0x38, 0x56, 0x4c, 0x41, 0x44, 0x73, + 0x66, 0x4b, 0x6f, 0x4b, 0x6d, 0x0a, 0x66, 0x6a, 0x61, 0x46, 0x33, 0x48, + 0x34, 0x38, 0x5a, 0x77, 0x43, 0x31, 0x35, 0x44, 0x74, 0x53, 0x34, 0x4b, + 0x6a, 0x72, 0x58, 0x52, 0x58, 0x35, 0x78, 0x6d, 0x33, 0x77, 0x72, 0x52, + 0x30, 0x4f, 0x68, 0x62, 0x65, 0x70, 0x6d, 0x6e, 0x4d, 0x55, 0x57, 0x6c, + 0x75, 0x50, 0x51, 0x53, 0x6a, 0x41, 0x31, 0x65, 0x67, 0x74, 0x54, 0x61, + 0x52, 0x65, 0x7a, 0x61, 0x72, 0x5a, 0x37, 0x63, 0x37, 0x63, 0x0a, 0x32, + 0x4e, 0x55, 0x38, 0x51, 0x68, 0x30, 0x58, 0x77, 0x52, 0x4a, 0x64, 0x52, + 0x54, 0x6a, 0x44, 0x4f, 0x50, 0x50, 0x38, 0x68, 0x53, 0x36, 0x44, 0x52, + 0x6b, 0x69, 0x79, 0x31, 0x79, 0x42, 0x66, 0x6b, 0x6a, 0x61, 0x50, 0x35, + 0x33, 0x6b, 0x50, 0x6d, 0x46, 0x36, 0x5a, 0x36, 0x50, 0x44, 0x51, 0x70, + 0x4c, 0x76, 0x31, 0x55, 0x37, 0x30, 0x71, 0x7a, 0x6c, 0x6d, 0x77, 0x72, + 0x32, 0x35, 0x2f, 0x0a, 0x62, 0x4c, 0x76, 0x53, 0x48, 0x67, 0x43, 0x77, + 0x49, 0x65, 0x33, 0x34, 0x51, 0x57, 0x4b, 0x43, 0x75, 0x64, 0x69, 0x79, + 0x78, 0x4c, 0x74, 0x47, 0x55, 0x50, 0x4d, 0x78, 0x78, 0x59, 0x38, 0x42, + 0x71, 0x48, 0x54, 0x72, 0x39, 0x58, 0x67, 0x6e, 0x32, 0x75, 0x66, 0x33, + 0x5a, 0x6b, 0x50, 0x7a, 0x6e, 0x6f, 0x4d, 0x2b, 0x49, 0x4b, 0x72, 0x44, + 0x4e, 0x57, 0x43, 0x52, 0x7a, 0x67, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, + 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, + 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, + 0x3d, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, + 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x74, 0x79, 0x20, 0x4f, 0x3d, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, + 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x4f, 0x55, 0x3d, 0x77, 0x77, 0x77, 0x2e, + 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, + 0x43, 0x50, 0x53, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x20, + 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x2e, 0x20, 0x28, 0x6c, 0x69, 0x6d, + 0x69, 0x74, 0x73, 0x20, 0x6c, 0x69, 0x61, 0x62, 0x2e, 0x29, 0x2f, 0x28, + 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39, 0x20, 0x45, 0x6e, 0x74, 0x72, + 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x4c, 0x69, 0x6d, 0x69, + 0x74, 0x65, 0x64, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, + 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, + 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x4f, 0x3d, 0x45, 0x6e, + 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x4f, 0x55, + 0x3d, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, + 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x20, 0x69, 0x6e, 0x63, + 0x6f, 0x72, 0x70, 0x2e, 0x20, 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x2e, + 0x20, 0x28, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x20, 0x6c, 0x69, 0x61, + 0x62, 0x2e, 0x29, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39, + 0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, + 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x0a, 0x23, 0x20, 0x4c, + 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x45, 0x6e, 0x74, 0x72, 0x75, + 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, + 0x65, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x22, + 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x39, + 0x32, 0x37, 0x36, 0x35, 0x30, 0x33, 0x37, 0x31, 0x0a, 0x23, 0x20, 0x4d, + 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x3a, 0x20, 0x64, 0x66, 0x3a, 0x66, 0x32, 0x3a, 0x38, 0x30, + 0x3a, 0x37, 0x33, 0x3a, 0x63, 0x63, 0x3a, 0x66, 0x31, 0x3a, 0x65, 0x36, + 0x3a, 0x36, 0x31, 0x3a, 0x37, 0x33, 0x3a, 0x66, 0x63, 0x3a, 0x66, 0x35, + 0x3a, 0x34, 0x32, 0x3a, 0x65, 0x39, 0x3a, 0x63, 0x35, 0x3a, 0x37, 0x63, + 0x3a, 0x65, 0x65, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x39, 0x39, 0x3a, 0x61, 0x36, 0x3a, 0x39, 0x62, 0x3a, 0x65, 0x36, 0x3a, + 0x31, 0x61, 0x3a, 0x66, 0x65, 0x3a, 0x38, 0x38, 0x3a, 0x36, 0x62, 0x3a, + 0x34, 0x64, 0x3a, 0x32, 0x62, 0x3a, 0x38, 0x32, 0x3a, 0x30, 0x30, 0x3a, + 0x37, 0x63, 0x3a, 0x62, 0x38, 0x3a, 0x35, 0x34, 0x3a, 0x66, 0x63, 0x3a, + 0x33, 0x31, 0x3a, 0x37, 0x65, 0x3a, 0x31, 0x35, 0x3a, 0x33, 0x39, 0x0a, + 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, + 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x36, 0x32, + 0x3a, 0x66, 0x32, 0x3a, 0x34, 0x30, 0x3a, 0x32, 0x37, 0x3a, 0x38, 0x63, + 0x3a, 0x35, 0x36, 0x3a, 0x34, 0x63, 0x3a, 0x34, 0x64, 0x3a, 0x64, 0x38, + 0x3a, 0x62, 0x66, 0x3a, 0x37, 0x64, 0x3a, 0x39, 0x64, 0x3a, 0x34, 0x66, + 0x3a, 0x36, 0x66, 0x3a, 0x33, 0x36, 0x3a, 0x36, 0x65, 0x3a, 0x61, 0x38, + 0x3a, 0x39, 0x34, 0x3a, 0x64, 0x32, 0x3a, 0x32, 0x66, 0x3a, 0x35, 0x66, + 0x3a, 0x33, 0x34, 0x3a, 0x64, 0x39, 0x3a, 0x38, 0x39, 0x3a, 0x61, 0x39, + 0x3a, 0x38, 0x33, 0x3a, 0x61, 0x63, 0x3a, 0x65, 0x63, 0x3a, 0x32, 0x66, + 0x3a, 0x66, 0x66, 0x3a, 0x65, 0x64, 0x3a, 0x35, 0x30, 0x0a, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, + 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x45, 0x32, 0x44, 0x43, 0x43, 0x42, 0x45, + 0x47, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x45, 0x4e, 0x30, + 0x72, 0x53, 0x51, 0x7a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, + 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, + 0x43, 0x42, 0x77, 0x7a, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, + 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x0a, 0x56, 0x56, 0x4d, 0x78, 0x46, + 0x44, 0x41, 0x53, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, 0x43, + 0x30, 0x56, 0x75, 0x64, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x51, 0x75, 0x62, + 0x6d, 0x56, 0x30, 0x4d, 0x54, 0x73, 0x77, 0x4f, 0x51, 0x59, 0x44, 0x56, + 0x51, 0x51, 0x4c, 0x45, 0x7a, 0x4a, 0x33, 0x64, 0x33, 0x63, 0x75, 0x5a, + 0x57, 0x35, 0x30, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x35, 0x75, 0x0a, + 0x5a, 0x58, 0x51, 0x76, 0x51, 0x31, 0x42, 0x54, 0x49, 0x47, 0x6c, 0x75, + 0x59, 0x32, 0x39, 0x79, 0x63, 0x43, 0x34, 0x67, 0x59, 0x6e, 0x6b, 0x67, + 0x63, 0x6d, 0x56, 0x6d, 0x4c, 0x69, 0x41, 0x6f, 0x62, 0x47, 0x6c, 0x74, + 0x61, 0x58, 0x52, 0x7a, 0x49, 0x47, 0x78, 0x70, 0x59, 0x57, 0x49, 0x75, + 0x4b, 0x54, 0x45, 0x6c, 0x4d, 0x43, 0x4d, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x43, 0x78, 0x4d, 0x63, 0x0a, 0x4b, 0x47, 0x4d, 0x70, 0x49, 0x44, 0x45, + 0x35, 0x4f, 0x54, 0x6b, 0x67, 0x52, 0x57, 0x35, 0x30, 0x63, 0x6e, 0x56, + 0x7a, 0x64, 0x43, 0x35, 0x75, 0x5a, 0x58, 0x51, 0x67, 0x54, 0x47, 0x6c, + 0x74, 0x61, 0x58, 0x52, 0x6c, 0x5a, 0x44, 0x45, 0x36, 0x4d, 0x44, 0x67, + 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x78, 0x52, 0x57, 0x35, + 0x30, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x35, 0x75, 0x0a, 0x5a, 0x58, + 0x51, 0x67, 0x55, 0x32, 0x56, 0x6a, 0x64, 0x58, 0x4a, 0x6c, 0x49, 0x46, + 0x4e, 0x6c, 0x63, 0x6e, 0x5a, 0x6c, 0x63, 0x69, 0x42, 0x44, 0x5a, 0x58, + 0x4a, 0x30, 0x61, 0x57, 0x5a, 0x70, 0x59, 0x32, 0x46, 0x30, 0x61, 0x57, + 0x39, 0x75, 0x49, 0x45, 0x46, 0x31, 0x64, 0x47, 0x68, 0x76, 0x63, 0x6d, + 0x6c, 0x30, 0x65, 0x54, 0x41, 0x65, 0x46, 0x77, 0x30, 0x35, 0x4f, 0x54, + 0x41, 0x31, 0x0a, 0x4d, 0x6a, 0x55, 0x78, 0x4e, 0x6a, 0x41, 0x35, 0x4e, + 0x44, 0x42, 0x61, 0x46, 0x77, 0x30, 0x78, 0x4f, 0x54, 0x41, 0x31, 0x4d, + 0x6a, 0x55, 0x78, 0x4e, 0x6a, 0x4d, 0x35, 0x4e, 0x44, 0x42, 0x61, 0x4d, + 0x49, 0x48, 0x44, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, + 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, 0x55, 0x4d, + 0x42, 0x49, 0x47, 0x41, 0x31, 0x55, 0x45, 0x0a, 0x43, 0x68, 0x4d, 0x4c, + 0x52, 0x57, 0x35, 0x30, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x35, 0x75, + 0x5a, 0x58, 0x51, 0x78, 0x4f, 0x7a, 0x41, 0x35, 0x42, 0x67, 0x4e, 0x56, + 0x42, 0x41, 0x73, 0x54, 0x4d, 0x6e, 0x64, 0x33, 0x64, 0x79, 0x35, 0x6c, + 0x62, 0x6e, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x4c, 0x6d, 0x35, 0x6c, + 0x64, 0x43, 0x39, 0x44, 0x55, 0x46, 0x4d, 0x67, 0x61, 0x57, 0x35, 0x6a, + 0x0a, 0x62, 0x33, 0x4a, 0x77, 0x4c, 0x69, 0x42, 0x69, 0x65, 0x53, 0x42, + 0x79, 0x5a, 0x57, 0x59, 0x75, 0x49, 0x43, 0x68, 0x73, 0x61, 0x57, 0x31, + 0x70, 0x64, 0x48, 0x4d, 0x67, 0x62, 0x47, 0x6c, 0x68, 0x59, 0x69, 0x34, + 0x70, 0x4d, 0x53, 0x55, 0x77, 0x49, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, + 0x4c, 0x45, 0x78, 0x77, 0x6f, 0x59, 0x79, 0x6b, 0x67, 0x4d, 0x54, 0x6b, + 0x35, 0x4f, 0x53, 0x42, 0x46, 0x0a, 0x62, 0x6e, 0x52, 0x79, 0x64, 0x58, + 0x4e, 0x30, 0x4c, 0x6d, 0x35, 0x6c, 0x64, 0x43, 0x42, 0x4d, 0x61, 0x57, + 0x31, 0x70, 0x64, 0x47, 0x56, 0x6b, 0x4d, 0x54, 0x6f, 0x77, 0x4f, 0x41, + 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, 0x7a, 0x46, 0x46, 0x62, 0x6e, + 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x4c, 0x6d, 0x35, 0x6c, 0x64, 0x43, + 0x42, 0x54, 0x5a, 0x57, 0x4e, 0x31, 0x63, 0x6d, 0x55, 0x67, 0x0a, 0x55, + 0x32, 0x56, 0x79, 0x64, 0x6d, 0x56, 0x79, 0x49, 0x45, 0x4e, 0x6c, 0x63, + 0x6e, 0x52, 0x70, 0x5a, 0x6d, 0x6c, 0x6a, 0x59, 0x58, 0x52, 0x70, 0x62, + 0x32, 0x34, 0x67, 0x51, 0x58, 0x56, 0x30, 0x61, 0x47, 0x39, 0x79, 0x61, + 0x58, 0x52, 0x35, 0x4d, 0x49, 0x47, 0x64, 0x4d, 0x41, 0x30, 0x47, 0x43, + 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x41, + 0x51, 0x55, 0x41, 0x0a, 0x41, 0x34, 0x47, 0x4c, 0x41, 0x44, 0x43, 0x42, + 0x68, 0x77, 0x4b, 0x42, 0x67, 0x51, 0x44, 0x4e, 0x4b, 0x49, 0x4d, 0x30, + 0x56, 0x42, 0x75, 0x4a, 0x38, 0x77, 0x2b, 0x76, 0x4e, 0x35, 0x45, 0x78, + 0x2f, 0x36, 0x38, 0x78, 0x59, 0x4d, 0x6d, 0x6f, 0x36, 0x4c, 0x49, 0x51, + 0x61, 0x4f, 0x32, 0x66, 0x35, 0x35, 0x4d, 0x32, 0x38, 0x51, 0x70, 0x6b, + 0x75, 0x30, 0x66, 0x31, 0x42, 0x42, 0x63, 0x2f, 0x0a, 0x49, 0x30, 0x64, + 0x4e, 0x78, 0x53, 0x63, 0x5a, 0x67, 0x53, 0x59, 0x4d, 0x56, 0x48, 0x49, + 0x4e, 0x69, 0x43, 0x33, 0x5a, 0x48, 0x35, 0x6f, 0x53, 0x6e, 0x37, 0x79, + 0x7a, 0x63, 0x64, 0x4f, 0x41, 0x47, 0x54, 0x39, 0x48, 0x5a, 0x6e, 0x75, + 0x4d, 0x4e, 0x53, 0x6a, 0x53, 0x75, 0x51, 0x72, 0x66, 0x4a, 0x4e, 0x71, + 0x63, 0x31, 0x6c, 0x42, 0x35, 0x67, 0x58, 0x70, 0x61, 0x30, 0x7a, 0x66, + 0x33, 0x0a, 0x77, 0x6b, 0x72, 0x59, 0x4b, 0x5a, 0x49, 0x6d, 0x5a, 0x4e, + 0x48, 0x6b, 0x6d, 0x47, 0x77, 0x36, 0x41, 0x49, 0x72, 0x31, 0x4e, 0x4a, + 0x74, 0x6c, 0x2b, 0x4f, 0x33, 0x6a, 0x45, 0x50, 0x2f, 0x39, 0x75, 0x45, + 0x6c, 0x59, 0x33, 0x4b, 0x44, 0x65, 0x67, 0x6a, 0x6c, 0x72, 0x67, 0x62, + 0x45, 0x57, 0x47, 0x57, 0x47, 0x35, 0x56, 0x4c, 0x62, 0x6d, 0x51, 0x77, + 0x49, 0x42, 0x41, 0x36, 0x4f, 0x43, 0x0a, 0x41, 0x64, 0x63, 0x77, 0x67, + 0x67, 0x48, 0x54, 0x4d, 0x42, 0x45, 0x47, 0x43, 0x57, 0x43, 0x47, 0x53, + 0x41, 0x47, 0x47, 0x2b, 0x45, 0x49, 0x42, 0x41, 0x51, 0x51, 0x45, 0x41, + 0x77, 0x49, 0x41, 0x42, 0x7a, 0x43, 0x43, 0x41, 0x52, 0x6b, 0x47, 0x41, + 0x31, 0x55, 0x64, 0x48, 0x77, 0x53, 0x43, 0x41, 0x52, 0x41, 0x77, 0x67, + 0x67, 0x45, 0x4d, 0x4d, 0x49, 0x48, 0x65, 0x6f, 0x49, 0x48, 0x62, 0x0a, + 0x6f, 0x49, 0x48, 0x59, 0x70, 0x49, 0x48, 0x56, 0x4d, 0x49, 0x48, 0x53, + 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, + 0x45, 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, 0x55, 0x4d, 0x42, 0x49, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4c, 0x52, 0x57, 0x35, 0x30, + 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x35, 0x75, 0x5a, 0x58, 0x51, 0x78, + 0x4f, 0x7a, 0x41, 0x35, 0x0a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, + 0x54, 0x4d, 0x6e, 0x64, 0x33, 0x64, 0x79, 0x35, 0x6c, 0x62, 0x6e, 0x52, + 0x79, 0x64, 0x58, 0x4e, 0x30, 0x4c, 0x6d, 0x35, 0x6c, 0x64, 0x43, 0x39, + 0x44, 0x55, 0x46, 0x4d, 0x67, 0x61, 0x57, 0x35, 0x6a, 0x62, 0x33, 0x4a, + 0x77, 0x4c, 0x69, 0x42, 0x69, 0x65, 0x53, 0x42, 0x79, 0x5a, 0x57, 0x59, + 0x75, 0x49, 0x43, 0x68, 0x73, 0x61, 0x57, 0x31, 0x70, 0x0a, 0x64, 0x48, + 0x4d, 0x67, 0x62, 0x47, 0x6c, 0x68, 0x59, 0x69, 0x34, 0x70, 0x4d, 0x53, + 0x55, 0x77, 0x49, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, 0x78, + 0x77, 0x6f, 0x59, 0x79, 0x6b, 0x67, 0x4d, 0x54, 0x6b, 0x35, 0x4f, 0x53, + 0x42, 0x46, 0x62, 0x6e, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x4c, 0x6d, + 0x35, 0x6c, 0x64, 0x43, 0x42, 0x4d, 0x61, 0x57, 0x31, 0x70, 0x64, 0x47, + 0x56, 0x6b, 0x0a, 0x4d, 0x54, 0x6f, 0x77, 0x4f, 0x41, 0x59, 0x44, 0x56, + 0x51, 0x51, 0x44, 0x45, 0x7a, 0x46, 0x46, 0x62, 0x6e, 0x52, 0x79, 0x64, + 0x58, 0x4e, 0x30, 0x4c, 0x6d, 0x35, 0x6c, 0x64, 0x43, 0x42, 0x54, 0x5a, + 0x57, 0x4e, 0x31, 0x63, 0x6d, 0x55, 0x67, 0x55, 0x32, 0x56, 0x79, 0x64, + 0x6d, 0x56, 0x79, 0x49, 0x45, 0x4e, 0x6c, 0x63, 0x6e, 0x52, 0x70, 0x5a, + 0x6d, 0x6c, 0x6a, 0x59, 0x58, 0x52, 0x70, 0x0a, 0x62, 0x32, 0x34, 0x67, + 0x51, 0x58, 0x56, 0x30, 0x61, 0x47, 0x39, 0x79, 0x61, 0x58, 0x52, 0x35, + 0x4d, 0x51, 0x30, 0x77, 0x43, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, + 0x45, 0x77, 0x52, 0x44, 0x55, 0x6b, 0x77, 0x78, 0x4d, 0x43, 0x6d, 0x67, + 0x4a, 0x36, 0x41, 0x6c, 0x68, 0x69, 0x4e, 0x6f, 0x64, 0x48, 0x52, 0x77, + 0x4f, 0x69, 0x38, 0x76, 0x64, 0x33, 0x64, 0x33, 0x4c, 0x6d, 0x56, 0x75, + 0x0a, 0x64, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x51, 0x75, 0x62, 0x6d, 0x56, + 0x30, 0x4c, 0x30, 0x4e, 0x53, 0x54, 0x43, 0x39, 0x75, 0x5a, 0x58, 0x51, + 0x78, 0x4c, 0x6d, 0x4e, 0x79, 0x62, 0x44, 0x41, 0x72, 0x42, 0x67, 0x4e, + 0x56, 0x48, 0x52, 0x41, 0x45, 0x4a, 0x44, 0x41, 0x69, 0x67, 0x41, 0x38, + 0x78, 0x4f, 0x54, 0x6b, 0x35, 0x4d, 0x44, 0x55, 0x79, 0x4e, 0x54, 0x45, + 0x32, 0x4d, 0x44, 0x6b, 0x30, 0x0a, 0x4d, 0x46, 0x71, 0x42, 0x44, 0x7a, + 0x49, 0x77, 0x4d, 0x54, 0x6b, 0x77, 0x4e, 0x54, 0x49, 0x31, 0x4d, 0x54, + 0x59, 0x77, 0x4f, 0x54, 0x51, 0x77, 0x57, 0x6a, 0x41, 0x4c, 0x42, 0x67, + 0x4e, 0x56, 0x48, 0x51, 0x38, 0x45, 0x42, 0x41, 0x4d, 0x43, 0x41, 0x51, + 0x59, 0x77, 0x48, 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, 0x6a, 0x42, 0x42, + 0x67, 0x77, 0x46, 0x6f, 0x41, 0x55, 0x38, 0x42, 0x64, 0x69, 0x0a, 0x45, + 0x31, 0x55, 0x39, 0x73, 0x2f, 0x38, 0x4b, 0x41, 0x47, 0x76, 0x37, 0x55, + 0x49, 0x53, 0x58, 0x38, 0x2b, 0x31, 0x69, 0x30, 0x42, 0x6f, 0x77, 0x48, + 0x51, 0x59, 0x44, 0x56, 0x52, 0x30, 0x4f, 0x42, 0x42, 0x59, 0x45, 0x46, + 0x50, 0x41, 0x58, 0x59, 0x68, 0x4e, 0x56, 0x50, 0x62, 0x50, 0x2f, 0x43, + 0x67, 0x42, 0x72, 0x2b, 0x31, 0x43, 0x45, 0x6c, 0x2f, 0x50, 0x74, 0x59, + 0x74, 0x41, 0x61, 0x0a, 0x4d, 0x41, 0x77, 0x47, 0x41, 0x31, 0x55, 0x64, + 0x45, 0x77, 0x51, 0x46, 0x4d, 0x41, 0x4d, 0x42, 0x41, 0x66, 0x38, 0x77, + 0x47, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x5a, 0x39, + 0x42, 0x30, 0x45, 0x41, 0x42, 0x41, 0x77, 0x77, 0x43, 0x68, 0x73, 0x45, + 0x56, 0x6a, 0x51, 0x75, 0x4d, 0x41, 0x4d, 0x43, 0x42, 0x4a, 0x41, 0x77, + 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x0a, 0x68, 0x76, 0x63, + 0x4e, 0x41, 0x51, 0x45, 0x46, 0x42, 0x51, 0x41, 0x44, 0x67, 0x59, 0x45, + 0x41, 0x6b, 0x4e, 0x77, 0x77, 0x41, 0x76, 0x70, 0x6b, 0x64, 0x4d, 0x4b, + 0x6e, 0x43, 0x71, 0x56, 0x38, 0x49, 0x59, 0x30, 0x30, 0x46, 0x36, 0x6a, + 0x37, 0x52, 0x77, 0x37, 0x2f, 0x4a, 0x58, 0x79, 0x4e, 0x45, 0x77, 0x72, + 0x37, 0x35, 0x4a, 0x69, 0x31, 0x37, 0x34, 0x7a, 0x34, 0x78, 0x52, 0x41, + 0x4e, 0x0a, 0x39, 0x35, 0x4b, 0x2b, 0x38, 0x63, 0x50, 0x56, 0x31, 0x5a, + 0x56, 0x71, 0x42, 0x4c, 0x73, 0x73, 0x7a, 0x69, 0x59, 0x32, 0x5a, 0x63, + 0x67, 0x78, 0x78, 0x75, 0x66, 0x75, 0x50, 0x2b, 0x4e, 0x58, 0x64, 0x59, + 0x52, 0x36, 0x45, 0x65, 0x39, 0x47, 0x54, 0x78, 0x6a, 0x30, 0x30, 0x35, + 0x69, 0x37, 0x71, 0x49, 0x63, 0x79, 0x75, 0x6e, 0x4c, 0x32, 0x50, 0x4f, + 0x49, 0x39, 0x6e, 0x39, 0x63, 0x64, 0x0a, 0x32, 0x63, 0x4e, 0x67, 0x51, + 0x34, 0x78, 0x59, 0x44, 0x69, 0x4b, 0x57, 0x4c, 0x32, 0x4b, 0x6a, 0x4c, + 0x42, 0x2b, 0x36, 0x72, 0x51, 0x58, 0x76, 0x71, 0x7a, 0x4a, 0x34, 0x68, + 0x36, 0x42, 0x55, 0x63, 0x78, 0x6d, 0x31, 0x58, 0x41, 0x58, 0x35, 0x55, + 0x6a, 0x35, 0x74, 0x4c, 0x55, 0x55, 0x4c, 0x39, 0x77, 0x71, 0x54, 0x36, + 0x75, 0x30, 0x47, 0x2b, 0x62, 0x49, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, + 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, + 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, + 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, + 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, + 0x28, 0x32, 0x30, 0x34, 0x38, 0x29, 0x20, 0x4f, 0x3d, 0x45, 0x6e, 0x74, + 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x4f, 0x55, 0x3d, + 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, + 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x5f, 0x32, 0x30, 0x34, 0x38, + 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x20, 0x62, 0x79, 0x20, + 0x72, 0x65, 0x66, 0x2e, 0x20, 0x28, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, + 0x20, 0x6c, 0x69, 0x61, 0x62, 0x2e, 0x29, 0x2f, 0x28, 0x63, 0x29, 0x20, + 0x31, 0x39, 0x39, 0x39, 0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, + 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, + 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, + 0x43, 0x4e, 0x3d, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, + 0x65, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x74, 0x79, 0x20, 0x28, 0x32, 0x30, 0x34, 0x38, 0x29, 0x20, 0x4f, 0x3d, + 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, + 0x4f, 0x55, 0x3d, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, + 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x5f, 0x32, + 0x30, 0x34, 0x38, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x20, + 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x2e, 0x20, 0x28, 0x6c, 0x69, 0x6d, + 0x69, 0x74, 0x73, 0x20, 0x6c, 0x69, 0x61, 0x62, 0x2e, 0x29, 0x2f, 0x28, + 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39, 0x20, 0x45, 0x6e, 0x74, 0x72, + 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x4c, 0x69, 0x6d, 0x69, + 0x74, 0x65, 0x64, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, + 0x74, 0x20, 0x50, 0x72, 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x20, 0x32, 0x30, + 0x34, 0x38, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x22, 0x0a, 0x23, 0x20, 0x53, + 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x39, 0x34, 0x36, 0x30, 0x35, + 0x39, 0x36, 0x32, 0x32, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x62, 0x61, 0x3a, 0x32, 0x31, 0x3a, 0x65, 0x61, 0x3a, 0x32, 0x30, 0x3a, + 0x64, 0x36, 0x3a, 0x64, 0x64, 0x3a, 0x64, 0x62, 0x3a, 0x38, 0x66, 0x3a, + 0x63, 0x31, 0x3a, 0x35, 0x37, 0x3a, 0x38, 0x62, 0x3a, 0x34, 0x30, 0x3a, + 0x61, 0x64, 0x3a, 0x61, 0x31, 0x3a, 0x66, 0x63, 0x3a, 0x66, 0x63, 0x0a, + 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x38, 0x30, 0x3a, 0x31, + 0x64, 0x3a, 0x36, 0x32, 0x3a, 0x64, 0x30, 0x3a, 0x37, 0x62, 0x3a, 0x34, + 0x34, 0x3a, 0x39, 0x64, 0x3a, 0x35, 0x63, 0x3a, 0x35, 0x63, 0x3a, 0x30, + 0x33, 0x3a, 0x35, 0x63, 0x3a, 0x39, 0x38, 0x3a, 0x65, 0x61, 0x3a, 0x36, + 0x31, 0x3a, 0x66, 0x61, 0x3a, 0x34, 0x34, 0x3a, 0x33, 0x63, 0x3a, 0x32, + 0x61, 0x3a, 0x35, 0x38, 0x3a, 0x66, 0x65, 0x0a, 0x23, 0x20, 0x53, 0x48, + 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, + 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x64, 0x31, 0x3a, 0x63, 0x33, 0x3a, + 0x33, 0x39, 0x3a, 0x65, 0x61, 0x3a, 0x32, 0x37, 0x3a, 0x38, 0x34, 0x3a, + 0x65, 0x62, 0x3a, 0x38, 0x37, 0x3a, 0x30, 0x66, 0x3a, 0x39, 0x33, 0x3a, + 0x34, 0x66, 0x3a, 0x63, 0x35, 0x3a, 0x36, 0x33, 0x3a, 0x34, 0x65, 0x3a, + 0x34, 0x61, 0x3a, 0x61, 0x39, 0x3a, 0x61, 0x64, 0x3a, 0x35, 0x35, 0x3a, + 0x30, 0x35, 0x3a, 0x30, 0x31, 0x3a, 0x36, 0x34, 0x3a, 0x30, 0x31, 0x3a, + 0x66, 0x32, 0x3a, 0x36, 0x34, 0x3a, 0x36, 0x35, 0x3a, 0x64, 0x33, 0x3a, + 0x37, 0x61, 0x3a, 0x35, 0x37, 0x3a, 0x34, 0x36, 0x3a, 0x36, 0x33, 0x3a, + 0x33, 0x35, 0x3a, 0x39, 0x66, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, + 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, + 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, + 0x49, 0x45, 0x58, 0x44, 0x43, 0x43, 0x41, 0x30, 0x53, 0x67, 0x41, 0x77, + 0x49, 0x42, 0x41, 0x67, 0x49, 0x45, 0x4f, 0x47, 0x4f, 0x35, 0x5a, 0x6a, + 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, + 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x43, 0x42, 0x74, 0x44, + 0x45, 0x55, 0x4d, 0x42, 0x49, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, + 0x4d, 0x4c, 0x0a, 0x52, 0x57, 0x35, 0x30, 0x63, 0x6e, 0x56, 0x7a, 0x64, + 0x43, 0x35, 0x75, 0x5a, 0x58, 0x51, 0x78, 0x51, 0x44, 0x41, 0x2b, 0x42, + 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x55, 0x4e, 0x33, 0x64, 0x33, 0x64, + 0x79, 0x35, 0x6c, 0x62, 0x6e, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x4c, + 0x6d, 0x35, 0x6c, 0x64, 0x43, 0x39, 0x44, 0x55, 0x46, 0x4e, 0x66, 0x4d, + 0x6a, 0x41, 0x30, 0x4f, 0x43, 0x42, 0x70, 0x0a, 0x62, 0x6d, 0x4e, 0x76, + 0x63, 0x6e, 0x41, 0x75, 0x49, 0x47, 0x4a, 0x35, 0x49, 0x48, 0x4a, 0x6c, + 0x5a, 0x69, 0x34, 0x67, 0x4b, 0x47, 0x78, 0x70, 0x62, 0x57, 0x6c, 0x30, + 0x63, 0x79, 0x42, 0x73, 0x61, 0x57, 0x46, 0x69, 0x4c, 0x69, 0x6b, 0x78, + 0x4a, 0x54, 0x41, 0x6a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, + 0x48, 0x43, 0x68, 0x6a, 0x4b, 0x53, 0x41, 0x78, 0x4f, 0x54, 0x6b, 0x35, + 0x0a, 0x49, 0x45, 0x56, 0x75, 0x64, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x51, + 0x75, 0x62, 0x6d, 0x56, 0x30, 0x49, 0x45, 0x78, 0x70, 0x62, 0x57, 0x6c, + 0x30, 0x5a, 0x57, 0x51, 0x78, 0x4d, 0x7a, 0x41, 0x78, 0x42, 0x67, 0x4e, + 0x56, 0x42, 0x41, 0x4d, 0x54, 0x4b, 0x6b, 0x56, 0x75, 0x64, 0x48, 0x4a, + 0x31, 0x63, 0x33, 0x51, 0x75, 0x62, 0x6d, 0x56, 0x30, 0x49, 0x45, 0x4e, + 0x6c, 0x63, 0x6e, 0x52, 0x70, 0x0a, 0x5a, 0x6d, 0x6c, 0x6a, 0x59, 0x58, + 0x52, 0x70, 0x62, 0x32, 0x34, 0x67, 0x51, 0x58, 0x56, 0x30, 0x61, 0x47, + 0x39, 0x79, 0x61, 0x58, 0x52, 0x35, 0x49, 0x43, 0x67, 0x79, 0x4d, 0x44, + 0x51, 0x34, 0x4b, 0x54, 0x41, 0x65, 0x46, 0x77, 0x30, 0x35, 0x4f, 0x54, + 0x45, 0x79, 0x4d, 0x6a, 0x51, 0x78, 0x4e, 0x7a, 0x55, 0x77, 0x4e, 0x54, + 0x46, 0x61, 0x46, 0x77, 0x30, 0x78, 0x4f, 0x54, 0x45, 0x79, 0x0a, 0x4d, + 0x6a, 0x51, 0x78, 0x4f, 0x44, 0x49, 0x77, 0x4e, 0x54, 0x46, 0x61, 0x4d, + 0x49, 0x47, 0x30, 0x4d, 0x52, 0x51, 0x77, 0x45, 0x67, 0x59, 0x44, 0x56, + 0x51, 0x51, 0x4b, 0x45, 0x77, 0x74, 0x46, 0x62, 0x6e, 0x52, 0x79, 0x64, + 0x58, 0x4e, 0x30, 0x4c, 0x6d, 0x35, 0x6c, 0x64, 0x44, 0x46, 0x41, 0x4d, + 0x44, 0x34, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x78, 0x51, 0x33, 0x64, + 0x33, 0x64, 0x33, 0x0a, 0x4c, 0x6d, 0x56, 0x75, 0x64, 0x48, 0x4a, 0x31, + 0x63, 0x33, 0x51, 0x75, 0x62, 0x6d, 0x56, 0x30, 0x4c, 0x30, 0x4e, 0x51, + 0x55, 0x31, 0x38, 0x79, 0x4d, 0x44, 0x51, 0x34, 0x49, 0x47, 0x6c, 0x75, + 0x59, 0x32, 0x39, 0x79, 0x63, 0x43, 0x34, 0x67, 0x59, 0x6e, 0x6b, 0x67, + 0x63, 0x6d, 0x56, 0x6d, 0x4c, 0x69, 0x41, 0x6f, 0x62, 0x47, 0x6c, 0x74, + 0x61, 0x58, 0x52, 0x7a, 0x49, 0x47, 0x78, 0x70, 0x0a, 0x59, 0x57, 0x49, + 0x75, 0x4b, 0x54, 0x45, 0x6c, 0x4d, 0x43, 0x4d, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x43, 0x78, 0x4d, 0x63, 0x4b, 0x47, 0x4d, 0x70, 0x49, 0x44, 0x45, + 0x35, 0x4f, 0x54, 0x6b, 0x67, 0x52, 0x57, 0x35, 0x30, 0x63, 0x6e, 0x56, + 0x7a, 0x64, 0x43, 0x35, 0x75, 0x5a, 0x58, 0x51, 0x67, 0x54, 0x47, 0x6c, + 0x74, 0x61, 0x58, 0x52, 0x6c, 0x5a, 0x44, 0x45, 0x7a, 0x4d, 0x44, 0x45, + 0x47, 0x0a, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x71, 0x52, 0x57, + 0x35, 0x30, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x35, 0x75, 0x5a, 0x58, + 0x51, 0x67, 0x51, 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, + 0x4e, 0x68, 0x64, 0x47, 0x6c, 0x76, 0x62, 0x69, 0x42, 0x42, 0x64, 0x58, + 0x52, 0x6f, 0x62, 0x33, 0x4a, 0x70, 0x64, 0x48, 0x6b, 0x67, 0x4b, 0x44, + 0x49, 0x77, 0x4e, 0x44, 0x67, 0x70, 0x0a, 0x4d, 0x49, 0x49, 0x42, 0x49, + 0x6a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, + 0x77, 0x30, 0x42, 0x41, 0x51, 0x45, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, + 0x51, 0x38, 0x41, 0x4d, 0x49, 0x49, 0x42, 0x43, 0x67, 0x4b, 0x43, 0x41, + 0x51, 0x45, 0x41, 0x72, 0x55, 0x31, 0x4c, 0x71, 0x52, 0x4b, 0x47, 0x73, + 0x75, 0x71, 0x6a, 0x49, 0x41, 0x63, 0x56, 0x46, 0x6d, 0x51, 0x71, 0x0a, + 0x4b, 0x30, 0x76, 0x52, 0x76, 0x77, 0x74, 0x4b, 0x54, 0x59, 0x37, 0x74, + 0x67, 0x48, 0x61, 0x6c, 0x5a, 0x37, 0x64, 0x34, 0x51, 0x4d, 0x42, 0x7a, + 0x51, 0x73, 0x68, 0x6f, 0x77, 0x4e, 0x74, 0x54, 0x4b, 0x39, 0x31, 0x65, + 0x75, 0x48, 0x61, 0x59, 0x4e, 0x5a, 0x4f, 0x4c, 0x47, 0x70, 0x31, 0x38, + 0x45, 0x7a, 0x6f, 0x4f, 0x48, 0x31, 0x75, 0x33, 0x48, 0x73, 0x2f, 0x6c, + 0x4a, 0x42, 0x51, 0x65, 0x0a, 0x73, 0x59, 0x47, 0x70, 0x6a, 0x58, 0x32, + 0x34, 0x7a, 0x47, 0x74, 0x4c, 0x41, 0x2f, 0x45, 0x43, 0x44, 0x4e, 0x79, + 0x72, 0x70, 0x55, 0x41, 0x6b, 0x41, 0x48, 0x39, 0x30, 0x6c, 0x4b, 0x47, + 0x64, 0x43, 0x43, 0x6d, 0x7a, 0x69, 0x41, 0x76, 0x31, 0x68, 0x33, 0x65, + 0x64, 0x56, 0x63, 0x33, 0x6b, 0x77, 0x33, 0x37, 0x58, 0x61, 0x6d, 0x53, + 0x72, 0x68, 0x52, 0x53, 0x47, 0x6c, 0x56, 0x75, 0x58, 0x0a, 0x4d, 0x6c, + 0x42, 0x76, 0x50, 0x63, 0x69, 0x36, 0x5a, 0x67, 0x7a, 0x6a, 0x2f, 0x4c, + 0x32, 0x34, 0x53, 0x63, 0x46, 0x32, 0x69, 0x55, 0x6b, 0x5a, 0x2f, 0x63, + 0x43, 0x6f, 0x76, 0x59, 0x6d, 0x6a, 0x5a, 0x79, 0x2f, 0x47, 0x6e, 0x37, + 0x78, 0x78, 0x47, 0x57, 0x43, 0x34, 0x4c, 0x65, 0x6b, 0x73, 0x79, 0x5a, + 0x42, 0x32, 0x5a, 0x6e, 0x75, 0x55, 0x34, 0x71, 0x39, 0x34, 0x31, 0x6d, + 0x56, 0x54, 0x0a, 0x58, 0x54, 0x7a, 0x57, 0x6e, 0x4c, 0x4c, 0x50, 0x4b, + 0x51, 0x50, 0x35, 0x4c, 0x36, 0x52, 0x51, 0x73, 0x74, 0x52, 0x49, 0x7a, + 0x67, 0x55, 0x79, 0x56, 0x59, 0x72, 0x39, 0x73, 0x6d, 0x52, 0x4d, 0x44, + 0x75, 0x53, 0x59, 0x42, 0x33, 0x58, 0x62, 0x66, 0x39, 0x2b, 0x35, 0x43, + 0x46, 0x56, 0x67, 0x68, 0x54, 0x41, 0x70, 0x2b, 0x58, 0x74, 0x49, 0x70, + 0x47, 0x6d, 0x47, 0x34, 0x7a, 0x55, 0x2f, 0x0a, 0x48, 0x6f, 0x5a, 0x64, + 0x65, 0x6e, 0x6f, 0x56, 0x76, 0x65, 0x38, 0x41, 0x6a, 0x68, 0x55, 0x69, + 0x56, 0x42, 0x63, 0x41, 0x6b, 0x43, 0x61, 0x54, 0x76, 0x41, 0x35, 0x4a, + 0x61, 0x4a, 0x47, 0x2f, 0x2b, 0x45, 0x66, 0x54, 0x6e, 0x5a, 0x56, 0x43, + 0x77, 0x51, 0x35, 0x4e, 0x33, 0x32, 0x38, 0x6d, 0x7a, 0x38, 0x4d, 0x59, + 0x49, 0x57, 0x4a, 0x6d, 0x51, 0x33, 0x44, 0x57, 0x31, 0x63, 0x41, 0x48, + 0x0a, 0x34, 0x51, 0x49, 0x44, 0x41, 0x51, 0x41, 0x42, 0x6f, 0x33, 0x51, + 0x77, 0x63, 0x6a, 0x41, 0x52, 0x42, 0x67, 0x6c, 0x67, 0x68, 0x6b, 0x67, + 0x42, 0x68, 0x76, 0x68, 0x43, 0x41, 0x51, 0x45, 0x45, 0x42, 0x41, 0x4d, + 0x43, 0x41, 0x41, 0x63, 0x77, 0x48, 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, + 0x6a, 0x42, 0x42, 0x67, 0x77, 0x46, 0x6f, 0x41, 0x55, 0x56, 0x65, 0x53, + 0x42, 0x30, 0x52, 0x47, 0x41, 0x0a, 0x76, 0x74, 0x69, 0x4a, 0x75, 0x51, + 0x69, 0x6a, 0x4d, 0x66, 0x6d, 0x68, 0x4a, 0x41, 0x6b, 0x57, 0x75, 0x58, + 0x41, 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, 0x52, 0x30, 0x4f, 0x42, 0x42, + 0x59, 0x45, 0x46, 0x46, 0x58, 0x6b, 0x67, 0x64, 0x45, 0x52, 0x67, 0x4c, + 0x37, 0x59, 0x69, 0x62, 0x6b, 0x49, 0x6f, 0x7a, 0x48, 0x35, 0x6f, 0x53, + 0x51, 0x4a, 0x46, 0x72, 0x6c, 0x77, 0x4d, 0x42, 0x30, 0x47, 0x0a, 0x43, + 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x32, 0x66, 0x51, 0x64, 0x42, 0x41, + 0x41, 0x51, 0x51, 0x4d, 0x41, 0x34, 0x62, 0x43, 0x46, 0x59, 0x31, 0x4c, + 0x6a, 0x41, 0x36, 0x4e, 0x43, 0x34, 0x77, 0x41, 0x77, 0x49, 0x45, 0x6b, + 0x44, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, + 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, + 0x51, 0x45, 0x41, 0x0a, 0x57, 0x55, 0x65, 0x73, 0x49, 0x59, 0x53, 0x4b, + 0x46, 0x38, 0x6d, 0x63, 0x69, 0x56, 0x4d, 0x65, 0x75, 0x6f, 0x43, 0x46, + 0x47, 0x73, 0x59, 0x38, 0x54, 0x6a, 0x36, 0x78, 0x6e, 0x4c, 0x5a, 0x38, + 0x78, 0x70, 0x4a, 0x64, 0x47, 0x47, 0x51, 0x43, 0x34, 0x39, 0x4d, 0x47, + 0x43, 0x42, 0x46, 0x68, 0x66, 0x47, 0x50, 0x6a, 0x4b, 0x35, 0x30, 0x78, + 0x41, 0x33, 0x42, 0x32, 0x30, 0x71, 0x4d, 0x6f, 0x0a, 0x6f, 0x50, 0x53, + 0x37, 0x6d, 0x6d, 0x4e, 0x7a, 0x37, 0x57, 0x33, 0x6c, 0x4b, 0x74, 0x76, + 0x74, 0x46, 0x4b, 0x6b, 0x72, 0x78, 0x6a, 0x59, 0x52, 0x30, 0x43, 0x76, + 0x72, 0x42, 0x34, 0x75, 0x6c, 0x32, 0x70, 0x35, 0x63, 0x47, 0x5a, 0x31, + 0x57, 0x45, 0x76, 0x56, 0x55, 0x4b, 0x63, 0x67, 0x46, 0x37, 0x62, 0x49, + 0x53, 0x4b, 0x6f, 0x33, 0x30, 0x41, 0x78, 0x76, 0x2f, 0x35, 0x35, 0x49, + 0x51, 0x0a, 0x68, 0x37, 0x41, 0x36, 0x74, 0x63, 0x4f, 0x64, 0x42, 0x54, + 0x63, 0x53, 0x6f, 0x38, 0x66, 0x30, 0x46, 0x62, 0x6e, 0x56, 0x70, 0x44, + 0x6b, 0x57, 0x6d, 0x31, 0x4d, 0x36, 0x49, 0x35, 0x48, 0x78, 0x71, 0x49, + 0x4b, 0x69, 0x61, 0x6f, 0x68, 0x6f, 0x77, 0x58, 0x6b, 0x43, 0x49, 0x72, + 0x79, 0x71, 0x70, 0x74, 0x61, 0x75, 0x33, 0x37, 0x41, 0x55, 0x58, 0x37, + 0x69, 0x48, 0x30, 0x4e, 0x31, 0x38, 0x0a, 0x66, 0x33, 0x76, 0x2f, 0x72, + 0x78, 0x7a, 0x50, 0x35, 0x74, 0x73, 0x48, 0x72, 0x56, 0x37, 0x62, 0x68, + 0x5a, 0x33, 0x51, 0x4b, 0x77, 0x30, 0x7a, 0x32, 0x77, 0x54, 0x52, 0x35, + 0x6b, 0x6c, 0x41, 0x45, 0x79, 0x74, 0x32, 0x2b, 0x7a, 0x37, 0x70, 0x6e, + 0x49, 0x6b, 0x50, 0x46, 0x63, 0x34, 0x59, 0x73, 0x49, 0x56, 0x34, 0x49, + 0x55, 0x39, 0x72, 0x54, 0x77, 0x37, 0x36, 0x4e, 0x6d, 0x66, 0x4e, 0x0a, + 0x42, 0x2f, 0x4c, 0x2f, 0x43, 0x4e, 0x44, 0x69, 0x33, 0x74, 0x6d, 0x2f, + 0x4b, 0x71, 0x2b, 0x34, 0x68, 0x34, 0x59, 0x68, 0x50, 0x41, 0x54, 0x4b, + 0x74, 0x35, 0x52, 0x6f, 0x66, 0x38, 0x38, 0x38, 0x36, 0x5a, 0x6a, 0x58, + 0x4f, 0x50, 0x2f, 0x73, 0x77, 0x4e, 0x6c, 0x51, 0x38, 0x43, 0x35, 0x4c, + 0x57, 0x4b, 0x35, 0x47, 0x62, 0x39, 0x41, 0x75, 0x77, 0x32, 0x44, 0x61, + 0x63, 0x6c, 0x56, 0x79, 0x0a, 0x76, 0x55, 0x78, 0x46, 0x6e, 0x6d, 0x47, + 0x36, 0x76, 0x34, 0x53, 0x42, 0x6b, 0x67, 0x50, 0x52, 0x30, 0x6d, 0x6c, + 0x38, 0x78, 0x51, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, + 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, + 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, + 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x42, 0x61, + 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, + 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, + 0x4f, 0x3d, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x20, + 0x4f, 0x55, 0x3d, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, + 0x20, 0x43, 0x4e, 0x3d, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, + 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, + 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x4f, 0x3d, 0x42, 0x61, 0x6c, 0x74, + 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x4f, 0x55, 0x3d, 0x43, 0x79, 0x62, + 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x0a, 0x23, 0x20, 0x4c, 0x61, + 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, + 0x6f, 0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, + 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x22, 0x0a, 0x23, 0x20, 0x53, + 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x33, 0x33, 0x35, 0x35, 0x34, + 0x36, 0x31, 0x37, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, + 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x61, + 0x63, 0x3a, 0x62, 0x36, 0x3a, 0x39, 0x34, 0x3a, 0x61, 0x35, 0x3a, 0x39, + 0x63, 0x3a, 0x31, 0x37, 0x3a, 0x65, 0x30, 0x3a, 0x64, 0x37, 0x3a, 0x39, + 0x31, 0x3a, 0x35, 0x32, 0x3a, 0x39, 0x62, 0x3a, 0x62, 0x31, 0x3a, 0x39, + 0x37, 0x3a, 0x30, 0x36, 0x3a, 0x61, 0x36, 0x3a, 0x65, 0x34, 0x0a, 0x23, + 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x64, 0x34, 0x3a, 0x64, 0x65, + 0x3a, 0x32, 0x30, 0x3a, 0x64, 0x30, 0x3a, 0x35, 0x65, 0x3a, 0x36, 0x36, + 0x3a, 0x66, 0x63, 0x3a, 0x35, 0x33, 0x3a, 0x66, 0x65, 0x3a, 0x31, 0x61, + 0x3a, 0x35, 0x30, 0x3a, 0x38, 0x38, 0x3a, 0x32, 0x63, 0x3a, 0x37, 0x38, + 0x3a, 0x64, 0x62, 0x3a, 0x32, 0x38, 0x3a, 0x35, 0x32, 0x3a, 0x63, 0x61, + 0x3a, 0x65, 0x34, 0x3a, 0x37, 0x34, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, + 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, + 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x31, 0x36, 0x3a, 0x61, 0x66, 0x3a, 0x35, + 0x37, 0x3a, 0x61, 0x39, 0x3a, 0x66, 0x36, 0x3a, 0x37, 0x36, 0x3a, 0x62, + 0x30, 0x3a, 0x61, 0x62, 0x3a, 0x31, 0x32, 0x3a, 0x36, 0x30, 0x3a, 0x39, + 0x35, 0x3a, 0x61, 0x61, 0x3a, 0x35, 0x65, 0x3a, 0x62, 0x61, 0x3a, 0x64, + 0x65, 0x3a, 0x66, 0x32, 0x3a, 0x32, 0x61, 0x3a, 0x62, 0x33, 0x3a, 0x31, + 0x31, 0x3a, 0x31, 0x39, 0x3a, 0x64, 0x36, 0x3a, 0x34, 0x34, 0x3a, 0x61, + 0x63, 0x3a, 0x39, 0x35, 0x3a, 0x63, 0x64, 0x3a, 0x34, 0x62, 0x3a, 0x39, + 0x33, 0x3a, 0x64, 0x62, 0x3a, 0x66, 0x33, 0x3a, 0x66, 0x32, 0x3a, 0x36, + 0x61, 0x3a, 0x65, 0x62, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, + 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, + 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, + 0x44, 0x64, 0x7a, 0x43, 0x43, 0x41, 0x6c, 0x2b, 0x67, 0x41, 0x77, 0x49, + 0x42, 0x41, 0x67, 0x49, 0x45, 0x41, 0x67, 0x41, 0x41, 0x75, 0x54, 0x41, + 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, + 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x42, 0x61, 0x4d, 0x51, 0x73, + 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, + 0x4a, 0x0a, 0x52, 0x54, 0x45, 0x53, 0x4d, 0x42, 0x41, 0x47, 0x41, 0x31, + 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4a, 0x51, 0x6d, 0x46, 0x73, 0x64, 0x47, + 0x6c, 0x74, 0x62, 0x33, 0x4a, 0x6c, 0x4d, 0x52, 0x4d, 0x77, 0x45, 0x51, + 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, 0x77, 0x70, 0x44, 0x65, 0x57, + 0x4a, 0x6c, 0x63, 0x6c, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x4d, 0x53, + 0x49, 0x77, 0x49, 0x41, 0x59, 0x44, 0x0a, 0x56, 0x51, 0x51, 0x44, 0x45, + 0x78, 0x6c, 0x43, 0x59, 0x57, 0x78, 0x30, 0x61, 0x57, 0x31, 0x76, 0x63, + 0x6d, 0x55, 0x67, 0x51, 0x33, 0x6c, 0x69, 0x5a, 0x58, 0x4a, 0x55, 0x63, + 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x53, 0x62, 0x32, 0x39, 0x30, 0x4d, + 0x42, 0x34, 0x58, 0x44, 0x54, 0x41, 0x77, 0x4d, 0x44, 0x55, 0x78, 0x4d, + 0x6a, 0x45, 0x34, 0x4e, 0x44, 0x59, 0x77, 0x4d, 0x46, 0x6f, 0x58, 0x0a, + 0x44, 0x54, 0x49, 0x31, 0x4d, 0x44, 0x55, 0x78, 0x4d, 0x6a, 0x49, 0x7a, + 0x4e, 0x54, 0x6b, 0x77, 0x4d, 0x46, 0x6f, 0x77, 0x57, 0x6a, 0x45, 0x4c, + 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, + 0x53, 0x55, 0x55, 0x78, 0x45, 0x6a, 0x41, 0x51, 0x42, 0x67, 0x4e, 0x56, + 0x42, 0x41, 0x6f, 0x54, 0x43, 0x55, 0x4a, 0x68, 0x62, 0x48, 0x52, 0x70, + 0x62, 0x57, 0x39, 0x79, 0x0a, 0x5a, 0x54, 0x45, 0x54, 0x4d, 0x42, 0x45, + 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x78, 0x4d, 0x4b, 0x51, 0x33, 0x6c, + 0x69, 0x5a, 0x58, 0x4a, 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x44, 0x45, + 0x69, 0x4d, 0x43, 0x41, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, + 0x5a, 0x51, 0x6d, 0x46, 0x73, 0x64, 0x47, 0x6c, 0x74, 0x62, 0x33, 0x4a, + 0x6c, 0x49, 0x45, 0x4e, 0x35, 0x59, 0x6d, 0x56, 0x79, 0x0a, 0x56, 0x48, + 0x4a, 0x31, 0x63, 0x33, 0x51, 0x67, 0x55, 0x6d, 0x39, 0x76, 0x64, 0x44, + 0x43, 0x43, 0x41, 0x53, 0x49, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, + 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x42, 0x42, 0x51, + 0x41, 0x44, 0x67, 0x67, 0x45, 0x50, 0x41, 0x44, 0x43, 0x43, 0x41, 0x51, + 0x6f, 0x43, 0x67, 0x67, 0x45, 0x42, 0x41, 0x4b, 0x4d, 0x45, 0x75, 0x79, + 0x4b, 0x72, 0x0a, 0x6d, 0x44, 0x31, 0x58, 0x36, 0x43, 0x5a, 0x79, 0x6d, + 0x72, 0x56, 0x35, 0x31, 0x43, 0x6e, 0x69, 0x34, 0x65, 0x69, 0x56, 0x67, + 0x4c, 0x47, 0x77, 0x34, 0x31, 0x75, 0x4f, 0x4b, 0x79, 0x6d, 0x61, 0x5a, + 0x4e, 0x2b, 0x68, 0x58, 0x65, 0x32, 0x77, 0x43, 0x51, 0x56, 0x74, 0x32, + 0x79, 0x67, 0x75, 0x7a, 0x6d, 0x4b, 0x69, 0x59, 0x76, 0x36, 0x30, 0x69, + 0x4e, 0x6f, 0x53, 0x36, 0x7a, 0x6a, 0x72, 0x0a, 0x49, 0x5a, 0x33, 0x41, + 0x51, 0x53, 0x73, 0x42, 0x55, 0x6e, 0x75, 0x49, 0x64, 0x39, 0x4d, 0x63, + 0x6a, 0x38, 0x65, 0x36, 0x75, 0x59, 0x69, 0x31, 0x61, 0x67, 0x6e, 0x6e, + 0x63, 0x2b, 0x67, 0x52, 0x51, 0x4b, 0x66, 0x52, 0x7a, 0x4d, 0x70, 0x69, + 0x6a, 0x53, 0x33, 0x6c, 0x6a, 0x77, 0x75, 0x6d, 0x55, 0x4e, 0x4b, 0x6f, + 0x55, 0x4d, 0x4d, 0x6f, 0x36, 0x76, 0x57, 0x72, 0x4a, 0x59, 0x65, 0x4b, + 0x0a, 0x6d, 0x70, 0x59, 0x63, 0x71, 0x57, 0x65, 0x34, 0x50, 0x77, 0x7a, + 0x56, 0x39, 0x2f, 0x6c, 0x53, 0x45, 0x79, 0x2f, 0x43, 0x47, 0x39, 0x56, + 0x77, 0x63, 0x50, 0x43, 0x50, 0x77, 0x42, 0x4c, 0x4b, 0x42, 0x73, 0x75, + 0x61, 0x34, 0x64, 0x6e, 0x4b, 0x4d, 0x33, 0x70, 0x33, 0x31, 0x76, 0x6a, + 0x73, 0x75, 0x66, 0x46, 0x6f, 0x52, 0x45, 0x4a, 0x49, 0x45, 0x39, 0x4c, + 0x41, 0x77, 0x71, 0x53, 0x75, 0x0a, 0x58, 0x6d, 0x44, 0x2b, 0x74, 0x71, + 0x59, 0x46, 0x2f, 0x4c, 0x54, 0x64, 0x42, 0x31, 0x6b, 0x43, 0x31, 0x46, + 0x6b, 0x59, 0x6d, 0x47, 0x50, 0x31, 0x70, 0x57, 0x50, 0x67, 0x6b, 0x41, + 0x78, 0x39, 0x58, 0x62, 0x49, 0x47, 0x65, 0x76, 0x4f, 0x46, 0x36, 0x75, + 0x76, 0x55, 0x41, 0x36, 0x35, 0x65, 0x68, 0x44, 0x35, 0x66, 0x2f, 0x78, + 0x58, 0x74, 0x61, 0x62, 0x7a, 0x35, 0x4f, 0x54, 0x5a, 0x79, 0x0a, 0x64, + 0x63, 0x39, 0x33, 0x55, 0x6b, 0x33, 0x7a, 0x79, 0x5a, 0x41, 0x73, 0x75, + 0x54, 0x33, 0x6c, 0x79, 0x53, 0x4e, 0x54, 0x50, 0x78, 0x38, 0x6b, 0x6d, + 0x43, 0x46, 0x63, 0x42, 0x35, 0x6b, 0x70, 0x76, 0x63, 0x59, 0x36, 0x37, + 0x4f, 0x64, 0x75, 0x68, 0x6a, 0x70, 0x72, 0x6c, 0x33, 0x52, 0x6a, 0x4d, + 0x37, 0x31, 0x6f, 0x47, 0x44, 0x48, 0x77, 0x65, 0x49, 0x31, 0x32, 0x76, + 0x2f, 0x79, 0x65, 0x0a, 0x6a, 0x6c, 0x30, 0x71, 0x68, 0x71, 0x64, 0x4e, + 0x6b, 0x4e, 0x77, 0x6e, 0x47, 0x6a, 0x6b, 0x43, 0x41, 0x77, 0x45, 0x41, + 0x41, 0x61, 0x4e, 0x46, 0x4d, 0x45, 0x4d, 0x77, 0x48, 0x51, 0x59, 0x44, + 0x56, 0x52, 0x30, 0x4f, 0x42, 0x42, 0x59, 0x45, 0x46, 0x4f, 0x57, 0x64, + 0x57, 0x54, 0x43, 0x43, 0x52, 0x31, 0x6a, 0x4d, 0x72, 0x50, 0x6f, 0x49, + 0x56, 0x44, 0x61, 0x47, 0x65, 0x7a, 0x71, 0x31, 0x0a, 0x42, 0x45, 0x33, + 0x77, 0x4d, 0x42, 0x49, 0x47, 0x41, 0x31, 0x55, 0x64, 0x45, 0x77, 0x45, + 0x42, 0x2f, 0x77, 0x51, 0x49, 0x4d, 0x41, 0x59, 0x42, 0x41, 0x66, 0x38, + 0x43, 0x41, 0x51, 0x4d, 0x77, 0x44, 0x67, 0x59, 0x44, 0x56, 0x52, 0x30, + 0x50, 0x41, 0x51, 0x48, 0x2f, 0x42, 0x41, 0x51, 0x44, 0x41, 0x67, 0x45, + 0x47, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, + 0x33, 0x0a, 0x44, 0x51, 0x45, 0x42, 0x42, 0x51, 0x55, 0x41, 0x41, 0x34, + 0x49, 0x42, 0x41, 0x51, 0x43, 0x46, 0x44, 0x46, 0x32, 0x4f, 0x35, 0x47, + 0x39, 0x52, 0x61, 0x45, 0x49, 0x46, 0x6f, 0x4e, 0x32, 0x37, 0x54, 0x79, + 0x63, 0x6c, 0x68, 0x41, 0x4f, 0x39, 0x39, 0x32, 0x54, 0x39, 0x4c, 0x64, + 0x63, 0x77, 0x34, 0x36, 0x51, 0x51, 0x46, 0x2b, 0x76, 0x61, 0x4b, 0x53, + 0x6d, 0x32, 0x65, 0x54, 0x39, 0x32, 0x0a, 0x39, 0x68, 0x6b, 0x54, 0x49, + 0x37, 0x67, 0x51, 0x43, 0x76, 0x6c, 0x59, 0x70, 0x4e, 0x52, 0x68, 0x63, + 0x4c, 0x30, 0x45, 0x59, 0x57, 0x6f, 0x53, 0x69, 0x68, 0x66, 0x56, 0x43, + 0x72, 0x33, 0x46, 0x76, 0x44, 0x42, 0x38, 0x31, 0x75, 0x6b, 0x4d, 0x4a, + 0x59, 0x32, 0x47, 0x51, 0x45, 0x2f, 0x73, 0x7a, 0x4b, 0x4e, 0x2b, 0x4f, + 0x4d, 0x59, 0x33, 0x45, 0x55, 0x2f, 0x74, 0x33, 0x57, 0x67, 0x78, 0x0a, + 0x6a, 0x6b, 0x7a, 0x53, 0x73, 0x77, 0x46, 0x30, 0x37, 0x72, 0x35, 0x31, + 0x58, 0x67, 0x64, 0x49, 0x47, 0x6e, 0x39, 0x77, 0x2f, 0x78, 0x5a, 0x63, + 0x68, 0x4d, 0x42, 0x35, 0x68, 0x62, 0x67, 0x46, 0x2f, 0x58, 0x2b, 0x2b, + 0x5a, 0x52, 0x47, 0x6a, 0x44, 0x38, 0x41, 0x43, 0x74, 0x50, 0x68, 0x53, + 0x4e, 0x7a, 0x6b, 0x45, 0x31, 0x61, 0x6b, 0x78, 0x65, 0x68, 0x69, 0x2f, + 0x6f, 0x43, 0x72, 0x30, 0x0a, 0x45, 0x70, 0x6e, 0x33, 0x6f, 0x30, 0x57, + 0x43, 0x34, 0x7a, 0x78, 0x65, 0x39, 0x5a, 0x32, 0x65, 0x74, 0x63, 0x69, + 0x65, 0x66, 0x43, 0x37, 0x49, 0x70, 0x4a, 0x35, 0x4f, 0x43, 0x42, 0x52, + 0x4c, 0x62, 0x66, 0x31, 0x77, 0x62, 0x57, 0x73, 0x61, 0x59, 0x37, 0x31, + 0x6b, 0x35, 0x68, 0x2b, 0x33, 0x7a, 0x76, 0x44, 0x79, 0x6e, 0x79, 0x36, + 0x37, 0x47, 0x37, 0x66, 0x79, 0x55, 0x49, 0x68, 0x7a, 0x0a, 0x6b, 0x73, + 0x4c, 0x69, 0x34, 0x78, 0x61, 0x4e, 0x6d, 0x6a, 0x49, 0x43, 0x71, 0x34, + 0x34, 0x59, 0x33, 0x65, 0x6b, 0x51, 0x45, 0x65, 0x35, 0x2b, 0x4e, 0x61, + 0x75, 0x51, 0x72, 0x7a, 0x34, 0x77, 0x6c, 0x48, 0x72, 0x51, 0x4d, 0x7a, + 0x32, 0x6e, 0x5a, 0x51, 0x2f, 0x31, 0x2f, 0x49, 0x36, 0x65, 0x59, 0x73, + 0x39, 0x48, 0x52, 0x43, 0x77, 0x42, 0x58, 0x62, 0x73, 0x64, 0x74, 0x54, + 0x4c, 0x53, 0x0a, 0x52, 0x39, 0x49, 0x34, 0x4c, 0x74, 0x44, 0x2b, 0x67, + 0x64, 0x77, 0x79, 0x61, 0x68, 0x36, 0x31, 0x37, 0x6a, 0x7a, 0x56, 0x2f, + 0x4f, 0x65, 0x42, 0x48, 0x52, 0x6e, 0x44, 0x4a, 0x45, 0x4c, 0x71, 0x59, + 0x7a, 0x6d, 0x70, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, + 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, + 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x45, 0x71, 0x75, 0x69, + 0x66, 0x61, 0x78, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x47, + 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x65, 0x42, 0x75, 0x73, 0x69, 0x6e, + 0x65, 0x73, 0x73, 0x20, 0x43, 0x41, 0x2d, 0x31, 0x20, 0x4f, 0x3d, 0x45, + 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, + 0x65, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x45, 0x71, 0x75, + 0x69, 0x66, 0x61, 0x78, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, + 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x65, 0x42, 0x75, 0x73, 0x69, + 0x6e, 0x65, 0x73, 0x73, 0x20, 0x43, 0x41, 0x2d, 0x31, 0x20, 0x4f, 0x3d, + 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, 0x20, 0x53, 0x65, 0x63, 0x75, + 0x72, 0x65, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x4c, 0x61, + 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, + 0x78, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x47, 0x6c, 0x6f, + 0x62, 0x61, 0x6c, 0x20, 0x65, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, + 0x73, 0x20, 0x43, 0x41, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, + 0x61, 0x6c, 0x3a, 0x20, 0x31, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, + 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, + 0x20, 0x38, 0x66, 0x3a, 0x35, 0x64, 0x3a, 0x37, 0x37, 0x3a, 0x30, 0x36, + 0x3a, 0x32, 0x37, 0x3a, 0x63, 0x34, 0x3a, 0x39, 0x38, 0x3a, 0x33, 0x63, + 0x3a, 0x35, 0x62, 0x3a, 0x39, 0x33, 0x3a, 0x37, 0x38, 0x3a, 0x65, 0x37, + 0x3a, 0x64, 0x37, 0x3a, 0x37, 0x64, 0x3a, 0x39, 0x62, 0x3a, 0x63, 0x63, + 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, + 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x37, 0x65, 0x3a, + 0x37, 0x38, 0x3a, 0x34, 0x61, 0x3a, 0x31, 0x30, 0x3a, 0x31, 0x63, 0x3a, + 0x38, 0x32, 0x3a, 0x36, 0x35, 0x3a, 0x63, 0x63, 0x3a, 0x32, 0x64, 0x3a, + 0x65, 0x31, 0x3a, 0x66, 0x31, 0x3a, 0x36, 0x64, 0x3a, 0x34, 0x37, 0x3a, + 0x62, 0x34, 0x3a, 0x34, 0x30, 0x3a, 0x63, 0x61, 0x3a, 0x64, 0x39, 0x3a, + 0x30, 0x61, 0x3a, 0x31, 0x39, 0x3a, 0x34, 0x35, 0x0a, 0x23, 0x20, 0x53, + 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x35, 0x66, 0x3a, 0x30, 0x62, + 0x3a, 0x36, 0x32, 0x3a, 0x65, 0x61, 0x3a, 0x62, 0x35, 0x3a, 0x65, 0x33, + 0x3a, 0x35, 0x33, 0x3a, 0x65, 0x61, 0x3a, 0x36, 0x35, 0x3a, 0x32, 0x31, + 0x3a, 0x36, 0x35, 0x3a, 0x31, 0x36, 0x3a, 0x35, 0x38, 0x3a, 0x66, 0x62, + 0x3a, 0x62, 0x36, 0x3a, 0x35, 0x33, 0x3a, 0x35, 0x39, 0x3a, 0x66, 0x34, + 0x3a, 0x34, 0x33, 0x3a, 0x32, 0x38, 0x3a, 0x30, 0x61, 0x3a, 0x34, 0x61, + 0x3a, 0x66, 0x62, 0x3a, 0x64, 0x31, 0x3a, 0x30, 0x34, 0x3a, 0x64, 0x37, + 0x3a, 0x37, 0x64, 0x3a, 0x31, 0x30, 0x3a, 0x66, 0x39, 0x3a, 0x66, 0x30, + 0x3a, 0x34, 0x63, 0x3a, 0x30, 0x37, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, + 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, + 0x49, 0x49, 0x43, 0x6b, 0x44, 0x43, 0x43, 0x41, 0x66, 0x6d, 0x67, 0x41, + 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x42, 0x41, 0x54, 0x41, 0x4e, 0x42, + 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, + 0x51, 0x51, 0x46, 0x41, 0x44, 0x42, 0x61, 0x4d, 0x51, 0x73, 0x77, 0x43, + 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, 0x55, + 0x7a, 0x45, 0x63, 0x0a, 0x4d, 0x42, 0x6f, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x43, 0x68, 0x4d, 0x54, 0x52, 0x58, 0x46, 0x31, 0x61, 0x57, 0x5a, 0x68, + 0x65, 0x43, 0x42, 0x54, 0x5a, 0x57, 0x4e, 0x31, 0x63, 0x6d, 0x55, 0x67, + 0x53, 0x57, 0x35, 0x6a, 0x4c, 0x6a, 0x45, 0x74, 0x4d, 0x43, 0x73, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x6b, 0x52, 0x58, 0x46, 0x31, + 0x61, 0x57, 0x5a, 0x68, 0x65, 0x43, 0x42, 0x54, 0x0a, 0x5a, 0x57, 0x4e, + 0x31, 0x63, 0x6d, 0x55, 0x67, 0x52, 0x32, 0x78, 0x76, 0x59, 0x6d, 0x46, + 0x73, 0x49, 0x47, 0x56, 0x43, 0x64, 0x58, 0x4e, 0x70, 0x62, 0x6d, 0x56, + 0x7a, 0x63, 0x79, 0x42, 0x44, 0x51, 0x53, 0x30, 0x78, 0x4d, 0x42, 0x34, + 0x58, 0x44, 0x54, 0x6b, 0x35, 0x4d, 0x44, 0x59, 0x79, 0x4d, 0x54, 0x41, + 0x30, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x46, 0x6f, 0x58, 0x44, 0x54, 0x49, + 0x77, 0x0a, 0x4d, 0x44, 0x59, 0x79, 0x4d, 0x54, 0x41, 0x30, 0x4d, 0x44, + 0x41, 0x77, 0x4d, 0x46, 0x6f, 0x77, 0x57, 0x6a, 0x45, 0x4c, 0x4d, 0x41, + 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x56, 0x56, + 0x4d, 0x78, 0x48, 0x44, 0x41, 0x61, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, + 0x6f, 0x54, 0x45, 0x30, 0x56, 0x78, 0x64, 0x57, 0x6c, 0x6d, 0x59, 0x58, + 0x67, 0x67, 0x55, 0x32, 0x56, 0x6a, 0x0a, 0x64, 0x58, 0x4a, 0x6c, 0x49, + 0x45, 0x6c, 0x75, 0x59, 0x79, 0x34, 0x78, 0x4c, 0x54, 0x41, 0x72, 0x42, + 0x67, 0x4e, 0x56, 0x42, 0x41, 0x4d, 0x54, 0x4a, 0x45, 0x56, 0x78, 0x64, + 0x57, 0x6c, 0x6d, 0x59, 0x58, 0x67, 0x67, 0x55, 0x32, 0x56, 0x6a, 0x64, + 0x58, 0x4a, 0x6c, 0x49, 0x45, 0x64, 0x73, 0x62, 0x32, 0x4a, 0x68, 0x62, + 0x43, 0x42, 0x6c, 0x51, 0x6e, 0x56, 0x7a, 0x61, 0x57, 0x35, 0x6c, 0x0a, + 0x63, 0x33, 0x4d, 0x67, 0x51, 0x30, 0x45, 0x74, 0x4d, 0x54, 0x43, 0x42, + 0x6e, 0x7a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, + 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x45, 0x46, 0x41, 0x41, 0x4f, 0x42, + 0x6a, 0x51, 0x41, 0x77, 0x67, 0x59, 0x6b, 0x43, 0x67, 0x59, 0x45, 0x41, + 0x75, 0x75, 0x63, 0x58, 0x6b, 0x41, 0x4a, 0x6c, 0x73, 0x54, 0x52, 0x56, + 0x50, 0x45, 0x6e, 0x43, 0x0a, 0x55, 0x64, 0x58, 0x66, 0x70, 0x39, 0x45, + 0x33, 0x6a, 0x39, 0x48, 0x6e, 0x67, 0x58, 0x4e, 0x42, 0x55, 0x6d, 0x43, + 0x62, 0x6e, 0x61, 0x45, 0x58, 0x4a, 0x6e, 0x69, 0x74, 0x78, 0x37, 0x48, + 0x6f, 0x4a, 0x70, 0x51, 0x79, 0x74, 0x64, 0x34, 0x7a, 0x6a, 0x54, 0x6f, + 0x76, 0x32, 0x2f, 0x4b, 0x61, 0x65, 0x6c, 0x70, 0x7a, 0x6d, 0x4b, 0x4e, + 0x63, 0x36, 0x66, 0x75, 0x4b, 0x63, 0x78, 0x74, 0x63, 0x0a, 0x35, 0x38, + 0x4f, 0x2f, 0x67, 0x47, 0x7a, 0x4e, 0x71, 0x66, 0x54, 0x57, 0x4b, 0x38, + 0x44, 0x33, 0x2b, 0x5a, 0x6d, 0x71, 0x59, 0x36, 0x4b, 0x78, 0x52, 0x77, + 0x49, 0x50, 0x31, 0x4f, 0x52, 0x52, 0x4f, 0x68, 0x49, 0x38, 0x62, 0x49, + 0x70, 0x61, 0x56, 0x49, 0x52, 0x77, 0x32, 0x38, 0x48, 0x46, 0x6b, 0x4d, + 0x39, 0x79, 0x52, 0x63, 0x75, 0x6f, 0x57, 0x63, 0x44, 0x4e, 0x4d, 0x35, + 0x30, 0x2f, 0x0a, 0x6f, 0x35, 0x62, 0x72, 0x68, 0x54, 0x4d, 0x68, 0x48, + 0x44, 0x34, 0x65, 0x50, 0x6d, 0x42, 0x75, 0x64, 0x70, 0x78, 0x6e, 0x68, + 0x63, 0x58, 0x49, 0x77, 0x32, 0x45, 0x43, 0x41, 0x77, 0x45, 0x41, 0x41, + 0x61, 0x4e, 0x6d, 0x4d, 0x47, 0x51, 0x77, 0x45, 0x51, 0x59, 0x4a, 0x59, + 0x49, 0x5a, 0x49, 0x41, 0x59, 0x62, 0x34, 0x51, 0x67, 0x45, 0x42, 0x42, + 0x41, 0x51, 0x44, 0x41, 0x67, 0x41, 0x48, 0x0a, 0x4d, 0x41, 0x38, 0x47, + 0x41, 0x31, 0x55, 0x64, 0x45, 0x77, 0x45, 0x42, 0x2f, 0x77, 0x51, 0x46, + 0x4d, 0x41, 0x4d, 0x42, 0x41, 0x66, 0x38, 0x77, 0x48, 0x77, 0x59, 0x44, + 0x56, 0x52, 0x30, 0x6a, 0x42, 0x42, 0x67, 0x77, 0x46, 0x6f, 0x41, 0x55, + 0x76, 0x71, 0x69, 0x67, 0x64, 0x48, 0x4a, 0x51, 0x61, 0x30, 0x53, 0x33, + 0x79, 0x53, 0x50, 0x59, 0x2b, 0x36, 0x6a, 0x2f, 0x73, 0x31, 0x64, 0x72, + 0x0a, 0x61, 0x47, 0x77, 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, 0x52, 0x30, + 0x4f, 0x42, 0x42, 0x59, 0x45, 0x46, 0x4c, 0x36, 0x6f, 0x6f, 0x48, 0x52, + 0x79, 0x55, 0x47, 0x74, 0x45, 0x74, 0x38, 0x6b, 0x6a, 0x32, 0x50, 0x75, + 0x6f, 0x2f, 0x37, 0x4e, 0x58, 0x61, 0x32, 0x68, 0x73, 0x4d, 0x41, 0x30, + 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, + 0x42, 0x42, 0x41, 0x55, 0x41, 0x0a, 0x41, 0x34, 0x47, 0x42, 0x41, 0x44, + 0x44, 0x69, 0x41, 0x56, 0x47, 0x71, 0x78, 0x2b, 0x70, 0x66, 0x32, 0x72, + 0x6e, 0x51, 0x5a, 0x51, 0x38, 0x77, 0x31, 0x6a, 0x37, 0x61, 0x44, 0x52, + 0x52, 0x4a, 0x62, 0x70, 0x47, 0x54, 0x4a, 0x78, 0x51, 0x78, 0x37, 0x38, + 0x54, 0x33, 0x4c, 0x55, 0x58, 0x34, 0x37, 0x4d, 0x65, 0x2f, 0x6f, 0x6b, + 0x45, 0x4e, 0x49, 0x37, 0x53, 0x53, 0x2b, 0x52, 0x6b, 0x41, 0x0a, 0x5a, + 0x37, 0x30, 0x42, 0x72, 0x38, 0x33, 0x67, 0x63, 0x66, 0x78, 0x61, 0x7a, + 0x32, 0x54, 0x45, 0x34, 0x4a, 0x61, 0x59, 0x30, 0x4b, 0x4e, 0x41, 0x34, + 0x67, 0x47, 0x4b, 0x37, 0x79, 0x63, 0x48, 0x38, 0x57, 0x55, 0x42, 0x69, + 0x6b, 0x51, 0x74, 0x42, 0x6d, 0x56, 0x31, 0x55, 0x73, 0x43, 0x47, 0x45, + 0x43, 0x41, 0x68, 0x58, 0x32, 0x78, 0x72, 0x44, 0x32, 0x79, 0x75, 0x43, + 0x52, 0x79, 0x76, 0x0a, 0x38, 0x71, 0x49, 0x59, 0x4e, 0x4d, 0x52, 0x31, + 0x70, 0x48, 0x4d, 0x63, 0x38, 0x59, 0x33, 0x63, 0x37, 0x36, 0x33, 0x35, + 0x73, 0x33, 0x61, 0x30, 0x6b, 0x72, 0x2f, 0x63, 0x6c, 0x52, 0x41, 0x65, + 0x76, 0x73, 0x76, 0x49, 0x4f, 0x31, 0x71, 0x45, 0x59, 0x42, 0x6c, 0x57, + 0x6c, 0x4b, 0x6c, 0x56, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, + 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, + 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, + 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x45, 0x71, 0x75, + 0x69, 0x66, 0x61, 0x78, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, + 0x65, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x20, 0x43, 0x41, + 0x2d, 0x31, 0x20, 0x4f, 0x3d, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, + 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x49, 0x6e, 0x63, 0x2e, + 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, + 0x43, 0x4e, 0x3d, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, 0x20, 0x53, + 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x65, 0x42, 0x75, 0x73, 0x69, 0x6e, + 0x65, 0x73, 0x73, 0x20, 0x43, 0x41, 0x2d, 0x31, 0x20, 0x4f, 0x3d, 0x45, + 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, + 0x65, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, + 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, + 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x65, 0x42, 0x75, 0x73, + 0x69, 0x6e, 0x65, 0x73, 0x73, 0x20, 0x43, 0x41, 0x20, 0x31, 0x22, 0x0a, + 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x34, 0x0a, + 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x36, 0x34, 0x3a, 0x39, 0x63, + 0x3a, 0x65, 0x66, 0x3a, 0x32, 0x65, 0x3a, 0x34, 0x34, 0x3a, 0x66, 0x63, + 0x3a, 0x63, 0x36, 0x3a, 0x38, 0x66, 0x3a, 0x35, 0x32, 0x3a, 0x30, 0x37, + 0x3a, 0x64, 0x30, 0x3a, 0x35, 0x31, 0x3a, 0x37, 0x33, 0x3a, 0x38, 0x66, + 0x3a, 0x63, 0x62, 0x3a, 0x33, 0x64, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, + 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, + 0x74, 0x3a, 0x20, 0x64, 0x61, 0x3a, 0x34, 0x30, 0x3a, 0x31, 0x38, 0x3a, + 0x38, 0x62, 0x3a, 0x39, 0x31, 0x3a, 0x38, 0x39, 0x3a, 0x61, 0x33, 0x3a, + 0x65, 0x64, 0x3a, 0x65, 0x65, 0x3a, 0x61, 0x65, 0x3a, 0x64, 0x61, 0x3a, + 0x39, 0x37, 0x3a, 0x66, 0x65, 0x3a, 0x32, 0x66, 0x3a, 0x39, 0x64, 0x3a, + 0x66, 0x35, 0x3a, 0x62, 0x37, 0x3a, 0x64, 0x31, 0x3a, 0x38, 0x61, 0x3a, + 0x34, 0x31, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, + 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, + 0x20, 0x63, 0x66, 0x3a, 0x35, 0x36, 0x3a, 0x66, 0x66, 0x3a, 0x34, 0x36, + 0x3a, 0x61, 0x34, 0x3a, 0x61, 0x31, 0x3a, 0x38, 0x36, 0x3a, 0x31, 0x30, + 0x3a, 0x39, 0x64, 0x3a, 0x64, 0x39, 0x3a, 0x36, 0x35, 0x3a, 0x38, 0x34, + 0x3a, 0x62, 0x35, 0x3a, 0x65, 0x65, 0x3a, 0x62, 0x35, 0x3a, 0x38, 0x61, + 0x3a, 0x35, 0x31, 0x3a, 0x30, 0x63, 0x3a, 0x34, 0x32, 0x3a, 0x37, 0x35, + 0x3a, 0x62, 0x30, 0x3a, 0x65, 0x35, 0x3a, 0x66, 0x39, 0x3a, 0x34, 0x66, + 0x3a, 0x34, 0x30, 0x3a, 0x62, 0x62, 0x3a, 0x61, 0x65, 0x3a, 0x38, 0x36, + 0x3a, 0x35, 0x65, 0x3a, 0x31, 0x39, 0x3a, 0x66, 0x36, 0x3a, 0x37, 0x33, + 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, + 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x43, 0x67, 0x6a, 0x43, + 0x43, 0x41, 0x65, 0x75, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, + 0x42, 0x42, 0x44, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, + 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x51, 0x46, 0x41, 0x44, 0x42, + 0x54, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, + 0x47, 0x45, 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, 0x63, 0x0a, 0x4d, 0x42, + 0x6f, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x54, 0x52, 0x58, + 0x46, 0x31, 0x61, 0x57, 0x5a, 0x68, 0x65, 0x43, 0x42, 0x54, 0x5a, 0x57, + 0x4e, 0x31, 0x63, 0x6d, 0x55, 0x67, 0x53, 0x57, 0x35, 0x6a, 0x4c, 0x6a, + 0x45, 0x6d, 0x4d, 0x43, 0x51, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, + 0x4d, 0x64, 0x52, 0x58, 0x46, 0x31, 0x61, 0x57, 0x5a, 0x68, 0x65, 0x43, + 0x42, 0x54, 0x0a, 0x5a, 0x57, 0x4e, 0x31, 0x63, 0x6d, 0x55, 0x67, 0x5a, + 0x55, 0x4a, 0x31, 0x63, 0x32, 0x6c, 0x75, 0x5a, 0x58, 0x4e, 0x7a, 0x49, + 0x45, 0x4e, 0x42, 0x4c, 0x54, 0x45, 0x77, 0x48, 0x68, 0x63, 0x4e, 0x4f, + 0x54, 0x6b, 0x77, 0x4e, 0x6a, 0x49, 0x78, 0x4d, 0x44, 0x51, 0x77, 0x4d, + 0x44, 0x41, 0x77, 0x57, 0x68, 0x63, 0x4e, 0x4d, 0x6a, 0x41, 0x77, 0x4e, + 0x6a, 0x49, 0x78, 0x4d, 0x44, 0x51, 0x77, 0x0a, 0x4d, 0x44, 0x41, 0x77, + 0x57, 0x6a, 0x42, 0x54, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, + 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, 0x63, + 0x4d, 0x42, 0x6f, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x54, + 0x52, 0x58, 0x46, 0x31, 0x61, 0x57, 0x5a, 0x68, 0x65, 0x43, 0x42, 0x54, + 0x5a, 0x57, 0x4e, 0x31, 0x63, 0x6d, 0x55, 0x67, 0x53, 0x57, 0x35, 0x6a, + 0x0a, 0x4c, 0x6a, 0x45, 0x6d, 0x4d, 0x43, 0x51, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x41, 0x78, 0x4d, 0x64, 0x52, 0x58, 0x46, 0x31, 0x61, 0x57, 0x5a, + 0x68, 0x65, 0x43, 0x42, 0x54, 0x5a, 0x57, 0x4e, 0x31, 0x63, 0x6d, 0x55, + 0x67, 0x5a, 0x55, 0x4a, 0x31, 0x63, 0x32, 0x6c, 0x75, 0x5a, 0x58, 0x4e, + 0x7a, 0x49, 0x45, 0x4e, 0x42, 0x4c, 0x54, 0x45, 0x77, 0x67, 0x5a, 0x38, + 0x77, 0x44, 0x51, 0x59, 0x4a, 0x0a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, + 0x63, 0x4e, 0x41, 0x51, 0x45, 0x42, 0x42, 0x51, 0x41, 0x44, 0x67, 0x59, + 0x30, 0x41, 0x4d, 0x49, 0x47, 0x4a, 0x41, 0x6f, 0x47, 0x42, 0x41, 0x4d, + 0x34, 0x76, 0x47, 0x62, 0x77, 0x58, 0x74, 0x33, 0x66, 0x65, 0x6b, 0x36, + 0x6c, 0x66, 0x57, 0x67, 0x30, 0x58, 0x54, 0x7a, 0x51, 0x61, 0x44, 0x4a, + 0x6a, 0x30, 0x49, 0x74, 0x6c, 0x5a, 0x31, 0x4d, 0x52, 0x6f, 0x0a, 0x52, + 0x76, 0x43, 0x30, 0x4e, 0x63, 0x57, 0x46, 0x41, 0x79, 0x44, 0x47, 0x72, + 0x30, 0x57, 0x6c, 0x49, 0x56, 0x46, 0x46, 0x51, 0x65, 0x73, 0x57, 0x57, + 0x44, 0x59, 0x79, 0x62, 0x2b, 0x4a, 0x51, 0x59, 0x6d, 0x54, 0x35, 0x2f, + 0x56, 0x47, 0x63, 0x71, 0x69, 0x54, 0x5a, 0x39, 0x4a, 0x32, 0x44, 0x4b, + 0x6f, 0x63, 0x4b, 0x49, 0x64, 0x4d, 0x53, 0x4f, 0x44, 0x52, 0x73, 0x6a, + 0x51, 0x42, 0x75, 0x0a, 0x57, 0x71, 0x44, 0x5a, 0x51, 0x75, 0x34, 0x61, + 0x49, 0x5a, 0x58, 0x35, 0x55, 0x6b, 0x78, 0x56, 0x57, 0x73, 0x55, 0x50, + 0x4f, 0x45, 0x39, 0x47, 0x2b, 0x6d, 0x33, 0x34, 0x4c, 0x6a, 0x58, 0x57, + 0x48, 0x58, 0x7a, 0x72, 0x34, 0x76, 0x43, 0x77, 0x64, 0x59, 0x44, 0x49, + 0x71, 0x52, 0x4f, 0x73, 0x76, 0x6f, 0x6a, 0x76, 0x4f, 0x6d, 0x36, 0x72, + 0x58, 0x79, 0x6f, 0x34, 0x59, 0x67, 0x4b, 0x77, 0x0a, 0x45, 0x6e, 0x76, + 0x2b, 0x6a, 0x36, 0x59, 0x44, 0x41, 0x67, 0x4d, 0x42, 0x41, 0x41, 0x47, + 0x6a, 0x5a, 0x6a, 0x42, 0x6b, 0x4d, 0x42, 0x45, 0x47, 0x43, 0x57, 0x43, + 0x47, 0x53, 0x41, 0x47, 0x47, 0x2b, 0x45, 0x49, 0x42, 0x41, 0x51, 0x51, + 0x45, 0x41, 0x77, 0x49, 0x41, 0x42, 0x7a, 0x41, 0x50, 0x42, 0x67, 0x4e, + 0x56, 0x48, 0x52, 0x4d, 0x42, 0x41, 0x66, 0x38, 0x45, 0x42, 0x54, 0x41, + 0x44, 0x0a, 0x41, 0x51, 0x48, 0x2f, 0x4d, 0x42, 0x38, 0x47, 0x41, 0x31, + 0x55, 0x64, 0x49, 0x77, 0x51, 0x59, 0x4d, 0x42, 0x61, 0x41, 0x46, 0x45, + 0x70, 0x34, 0x4d, 0x6c, 0x49, 0x52, 0x32, 0x31, 0x6b, 0x57, 0x4e, 0x6c, + 0x37, 0x66, 0x77, 0x52, 0x51, 0x32, 0x51, 0x47, 0x70, 0x48, 0x66, 0x45, + 0x79, 0x68, 0x4d, 0x42, 0x30, 0x47, 0x41, 0x31, 0x55, 0x64, 0x44, 0x67, + 0x51, 0x57, 0x42, 0x42, 0x52, 0x4b, 0x0a, 0x65, 0x44, 0x4a, 0x53, 0x45, + 0x64, 0x74, 0x5a, 0x46, 0x6a, 0x5a, 0x65, 0x33, 0x38, 0x45, 0x55, 0x4e, + 0x6b, 0x42, 0x71, 0x52, 0x33, 0x78, 0x4d, 0x6f, 0x54, 0x41, 0x4e, 0x42, + 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, + 0x51, 0x51, 0x46, 0x41, 0x41, 0x4f, 0x42, 0x67, 0x51, 0x42, 0x31, 0x57, + 0x36, 0x69, 0x62, 0x41, 0x78, 0x48, 0x6d, 0x36, 0x56, 0x5a, 0x4d, 0x0a, + 0x7a, 0x66, 0x6d, 0x70, 0x54, 0x4d, 0x41, 0x4e, 0x6d, 0x76, 0x50, 0x4d, + 0x5a, 0x57, 0x6e, 0x6d, 0x4a, 0x58, 0x62, 0x4d, 0x57, 0x62, 0x66, 0x57, + 0x56, 0x4d, 0x4d, 0x64, 0x7a, 0x5a, 0x6d, 0x73, 0x47, 0x64, 0x32, 0x30, + 0x68, 0x64, 0x58, 0x67, 0x50, 0x66, 0x78, 0x69, 0x49, 0x4b, 0x65, 0x45, + 0x53, 0x31, 0x68, 0x6c, 0x38, 0x65, 0x4c, 0x35, 0x6c, 0x53, 0x45, 0x2f, + 0x39, 0x64, 0x52, 0x2b, 0x0a, 0x57, 0x42, 0x35, 0x48, 0x68, 0x31, 0x51, + 0x2b, 0x57, 0x4b, 0x47, 0x31, 0x74, 0x66, 0x67, 0x71, 0x37, 0x33, 0x48, + 0x6e, 0x76, 0x4d, 0x50, 0x32, 0x73, 0x55, 0x6c, 0x47, 0x34, 0x74, 0x65, + 0x67, 0x61, 0x2b, 0x56, 0x57, 0x65, 0x70, 0x6f, 0x6e, 0x6d, 0x48, 0x78, + 0x47, 0x59, 0x68, 0x54, 0x6e, 0x79, 0x66, 0x78, 0x75, 0x41, 0x78, 0x4a, + 0x35, 0x67, 0x44, 0x67, 0x64, 0x53, 0x49, 0x4b, 0x4e, 0x0a, 0x2f, 0x42, + 0x66, 0x2b, 0x4b, 0x70, 0x59, 0x72, 0x74, 0x57, 0x4b, 0x6d, 0x70, 0x6a, + 0x32, 0x39, 0x66, 0x35, 0x4a, 0x5a, 0x7a, 0x56, 0x6f, 0x71, 0x67, 0x72, + 0x49, 0x33, 0x65, 0x51, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, + 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, + 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x4f, 0x3d, 0x45, 0x71, + 0x75, 0x69, 0x66, 0x61, 0x78, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, + 0x20, 0x4f, 0x55, 0x3d, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, 0x20, + 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x65, 0x42, 0x75, 0x73, 0x69, + 0x6e, 0x65, 0x73, 0x73, 0x20, 0x43, 0x41, 0x2d, 0x32, 0x0a, 0x23, 0x20, + 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x4f, 0x3d, 0x45, + 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, + 0x65, 0x20, 0x4f, 0x55, 0x3d, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, + 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x65, 0x42, 0x75, 0x73, + 0x69, 0x6e, 0x65, 0x73, 0x73, 0x20, 0x43, 0x41, 0x2d, 0x32, 0x0a, 0x23, + 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x45, 0x71, 0x75, + 0x69, 0x66, 0x61, 0x78, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, + 0x65, 0x42, 0x75, 0x73, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x20, 0x43, 0x41, + 0x20, 0x32, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, + 0x3a, 0x20, 0x39, 0x33, 0x30, 0x31, 0x34, 0x30, 0x30, 0x38, 0x35, 0x0a, + 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x61, 0x61, 0x3a, 0x62, 0x66, + 0x3a, 0x62, 0x66, 0x3a, 0x36, 0x34, 0x3a, 0x39, 0x37, 0x3a, 0x64, 0x61, + 0x3a, 0x39, 0x38, 0x3a, 0x31, 0x64, 0x3a, 0x36, 0x66, 0x3a, 0x63, 0x36, + 0x3a, 0x30, 0x38, 0x3a, 0x33, 0x61, 0x3a, 0x39, 0x35, 0x3a, 0x37, 0x30, + 0x3a, 0x33, 0x33, 0x3a, 0x63, 0x61, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, + 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, + 0x74, 0x3a, 0x20, 0x33, 0x39, 0x3a, 0x34, 0x66, 0x3a, 0x66, 0x36, 0x3a, + 0x38, 0x35, 0x3a, 0x30, 0x62, 0x3a, 0x30, 0x36, 0x3a, 0x62, 0x65, 0x3a, + 0x35, 0x32, 0x3a, 0x65, 0x35, 0x3a, 0x31, 0x38, 0x3a, 0x35, 0x36, 0x3a, + 0x63, 0x63, 0x3a, 0x31, 0x30, 0x3a, 0x65, 0x31, 0x3a, 0x38, 0x30, 0x3a, + 0x65, 0x38, 0x3a, 0x38, 0x32, 0x3a, 0x62, 0x33, 0x3a, 0x38, 0x35, 0x3a, + 0x63, 0x63, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, + 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, + 0x20, 0x32, 0x66, 0x3a, 0x32, 0x37, 0x3a, 0x34, 0x65, 0x3a, 0x34, 0x38, + 0x3a, 0x61, 0x62, 0x3a, 0x61, 0x34, 0x3a, 0x61, 0x63, 0x3a, 0x37, 0x62, + 0x3a, 0x37, 0x36, 0x3a, 0x35, 0x39, 0x3a, 0x33, 0x33, 0x3a, 0x31, 0x30, + 0x3a, 0x31, 0x37, 0x3a, 0x37, 0x35, 0x3a, 0x35, 0x30, 0x3a, 0x36, 0x64, + 0x3a, 0x63, 0x33, 0x3a, 0x30, 0x65, 0x3a, 0x65, 0x33, 0x3a, 0x38, 0x65, + 0x3a, 0x66, 0x36, 0x3a, 0x61, 0x63, 0x3a, 0x64, 0x35, 0x3a, 0x63, 0x30, + 0x3a, 0x34, 0x39, 0x3a, 0x33, 0x32, 0x3a, 0x63, 0x66, 0x3a, 0x65, 0x30, + 0x3a, 0x34, 0x31, 0x3a, 0x32, 0x33, 0x3a, 0x34, 0x32, 0x3a, 0x32, 0x30, + 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, + 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x44, 0x49, 0x44, 0x43, + 0x43, 0x41, 0x6f, 0x6d, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, + 0x45, 0x4e, 0x33, 0x44, 0x50, 0x74, 0x54, 0x41, 0x4e, 0x42, 0x67, 0x6b, + 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, + 0x46, 0x41, 0x44, 0x42, 0x4f, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, + 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, 0x0a, 0x55, 0x7a, + 0x45, 0x58, 0x4d, 0x42, 0x55, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, + 0x4d, 0x4f, 0x52, 0x58, 0x46, 0x31, 0x61, 0x57, 0x5a, 0x68, 0x65, 0x43, + 0x42, 0x54, 0x5a, 0x57, 0x4e, 0x31, 0x63, 0x6d, 0x55, 0x78, 0x4a, 0x6a, + 0x41, 0x6b, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, 0x48, 0x55, + 0x56, 0x78, 0x64, 0x57, 0x6c, 0x6d, 0x59, 0x58, 0x67, 0x67, 0x55, 0x32, + 0x56, 0x6a, 0x0a, 0x64, 0x58, 0x4a, 0x6c, 0x49, 0x47, 0x56, 0x43, 0x64, + 0x58, 0x4e, 0x70, 0x62, 0x6d, 0x56, 0x7a, 0x63, 0x79, 0x42, 0x44, 0x51, + 0x53, 0x30, 0x79, 0x4d, 0x42, 0x34, 0x58, 0x44, 0x54, 0x6b, 0x35, 0x4d, + 0x44, 0x59, 0x79, 0x4d, 0x7a, 0x45, 0x79, 0x4d, 0x54, 0x51, 0x30, 0x4e, + 0x56, 0x6f, 0x58, 0x44, 0x54, 0x45, 0x35, 0x4d, 0x44, 0x59, 0x79, 0x4d, + 0x7a, 0x45, 0x79, 0x4d, 0x54, 0x51, 0x30, 0x0a, 0x4e, 0x56, 0x6f, 0x77, + 0x54, 0x6a, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x42, 0x68, 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, 0x46, 0x7a, 0x41, 0x56, + 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, 0x44, 0x6b, 0x56, 0x78, + 0x64, 0x57, 0x6c, 0x6d, 0x59, 0x58, 0x67, 0x67, 0x55, 0x32, 0x56, 0x6a, + 0x64, 0x58, 0x4a, 0x6c, 0x4d, 0x53, 0x59, 0x77, 0x4a, 0x41, 0x59, 0x44, + 0x0a, 0x56, 0x51, 0x51, 0x4c, 0x45, 0x78, 0x31, 0x46, 0x63, 0x58, 0x56, + 0x70, 0x5a, 0x6d, 0x46, 0x34, 0x49, 0x46, 0x4e, 0x6c, 0x59, 0x33, 0x56, + 0x79, 0x5a, 0x53, 0x42, 0x6c, 0x51, 0x6e, 0x56, 0x7a, 0x61, 0x57, 0x35, + 0x6c, 0x63, 0x33, 0x4d, 0x67, 0x51, 0x30, 0x45, 0x74, 0x4d, 0x6a, 0x43, + 0x42, 0x6e, 0x7a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, + 0x47, 0x39, 0x77, 0x30, 0x42, 0x0a, 0x41, 0x51, 0x45, 0x46, 0x41, 0x41, + 0x4f, 0x42, 0x6a, 0x51, 0x41, 0x77, 0x67, 0x59, 0x6b, 0x43, 0x67, 0x59, + 0x45, 0x41, 0x35, 0x44, 0x6b, 0x35, 0x6b, 0x78, 0x35, 0x53, 0x42, 0x68, + 0x73, 0x6f, 0x4e, 0x76, 0x69, 0x79, 0x6f, 0x79, 0x6e, 0x46, 0x37, 0x59, + 0x36, 0x79, 0x45, 0x62, 0x33, 0x2b, 0x36, 0x2b, 0x65, 0x30, 0x64, 0x4d, + 0x4b, 0x50, 0x2f, 0x77, 0x58, 0x6e, 0x32, 0x5a, 0x30, 0x47, 0x0a, 0x76, + 0x78, 0x4c, 0x49, 0x50, 0x77, 0x37, 0x79, 0x31, 0x74, 0x45, 0x6b, 0x73, + 0x68, 0x48, 0x65, 0x30, 0x58, 0x4d, 0x4a, 0x69, 0x74, 0x53, 0x78, 0x4c, + 0x4a, 0x67, 0x4a, 0x44, 0x52, 0x35, 0x51, 0x52, 0x72, 0x4b, 0x44, 0x70, + 0x6b, 0x57, 0x4e, 0x59, 0x6d, 0x69, 0x37, 0x68, 0x52, 0x73, 0x67, 0x63, + 0x44, 0x4b, 0x71, 0x51, 0x4d, 0x32, 0x6d, 0x6c, 0x6c, 0x2f, 0x45, 0x63, + 0x54, 0x63, 0x2f, 0x0a, 0x42, 0x50, 0x4f, 0x33, 0x51, 0x53, 0x51, 0x35, + 0x42, 0x78, 0x6f, 0x65, 0x4c, 0x6d, 0x46, 0x59, 0x6f, 0x42, 0x49, 0x4c, + 0x35, 0x61, 0x58, 0x66, 0x78, 0x61, 0x76, 0x71, 0x4e, 0x33, 0x48, 0x4d, + 0x48, 0x4d, 0x67, 0x33, 0x4f, 0x72, 0x6d, 0x58, 0x55, 0x71, 0x65, 0x73, + 0x78, 0x57, 0x6f, 0x6b, 0x6c, 0x45, 0x36, 0x63, 0x65, 0x38, 0x2f, 0x41, + 0x61, 0x74, 0x62, 0x66, 0x49, 0x62, 0x30, 0x43, 0x0a, 0x41, 0x77, 0x45, + 0x41, 0x41, 0x61, 0x4f, 0x43, 0x41, 0x51, 0x6b, 0x77, 0x67, 0x67, 0x45, + 0x46, 0x4d, 0x48, 0x41, 0x47, 0x41, 0x31, 0x55, 0x64, 0x48, 0x77, 0x52, + 0x70, 0x4d, 0x47, 0x63, 0x77, 0x5a, 0x61, 0x42, 0x6a, 0x6f, 0x47, 0x47, + 0x6b, 0x58, 0x7a, 0x42, 0x64, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, + 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, + 0x58, 0x0a, 0x4d, 0x42, 0x55, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, + 0x4d, 0x4f, 0x52, 0x58, 0x46, 0x31, 0x61, 0x57, 0x5a, 0x68, 0x65, 0x43, + 0x42, 0x54, 0x5a, 0x57, 0x4e, 0x31, 0x63, 0x6d, 0x55, 0x78, 0x4a, 0x6a, + 0x41, 0x6b, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, 0x48, 0x55, + 0x56, 0x78, 0x64, 0x57, 0x6c, 0x6d, 0x59, 0x58, 0x67, 0x67, 0x55, 0x32, + 0x56, 0x6a, 0x64, 0x58, 0x4a, 0x6c, 0x0a, 0x49, 0x47, 0x56, 0x43, 0x64, + 0x58, 0x4e, 0x70, 0x62, 0x6d, 0x56, 0x7a, 0x63, 0x79, 0x42, 0x44, 0x51, + 0x53, 0x30, 0x79, 0x4d, 0x51, 0x30, 0x77, 0x43, 0x77, 0x59, 0x44, 0x56, + 0x51, 0x51, 0x44, 0x45, 0x77, 0x52, 0x44, 0x55, 0x6b, 0x77, 0x78, 0x4d, + 0x42, 0x6f, 0x47, 0x41, 0x31, 0x55, 0x64, 0x45, 0x41, 0x51, 0x54, 0x4d, + 0x42, 0x47, 0x42, 0x44, 0x7a, 0x49, 0x77, 0x4d, 0x54, 0x6b, 0x77, 0x0a, + 0x4e, 0x6a, 0x49, 0x7a, 0x4d, 0x54, 0x49, 0x78, 0x4e, 0x44, 0x51, 0x31, + 0x57, 0x6a, 0x41, 0x4c, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x38, 0x45, + 0x42, 0x41, 0x4d, 0x43, 0x41, 0x51, 0x59, 0x77, 0x48, 0x77, 0x59, 0x44, + 0x56, 0x52, 0x30, 0x6a, 0x42, 0x42, 0x67, 0x77, 0x46, 0x6f, 0x41, 0x55, + 0x55, 0x4a, 0x34, 0x4c, 0x36, 0x71, 0x39, 0x65, 0x75, 0x53, 0x42, 0x49, + 0x70, 0x6c, 0x42, 0x71, 0x0a, 0x79, 0x2f, 0x33, 0x59, 0x49, 0x48, 0x71, + 0x6e, 0x67, 0x6e, 0x59, 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, 0x52, 0x30, + 0x4f, 0x42, 0x42, 0x59, 0x45, 0x46, 0x46, 0x43, 0x65, 0x43, 0x2b, 0x71, + 0x76, 0x58, 0x72, 0x6b, 0x67, 0x53, 0x4b, 0x5a, 0x51, 0x61, 0x73, 0x76, + 0x39, 0x32, 0x43, 0x42, 0x36, 0x70, 0x34, 0x4a, 0x32, 0x4d, 0x41, 0x77, + 0x47, 0x41, 0x31, 0x55, 0x64, 0x45, 0x77, 0x51, 0x46, 0x0a, 0x4d, 0x41, + 0x4d, 0x42, 0x41, 0x66, 0x38, 0x77, 0x47, 0x67, 0x59, 0x4a, 0x4b, 0x6f, + 0x5a, 0x49, 0x68, 0x76, 0x5a, 0x39, 0x42, 0x30, 0x45, 0x41, 0x42, 0x41, + 0x30, 0x77, 0x43, 0x78, 0x73, 0x46, 0x56, 0x6a, 0x4d, 0x75, 0x4d, 0x47, + 0x4d, 0x44, 0x41, 0x67, 0x62, 0x41, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, + 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x42, 0x51, + 0x55, 0x41, 0x0a, 0x41, 0x34, 0x47, 0x42, 0x41, 0x41, 0x79, 0x47, 0x67, + 0x71, 0x33, 0x6f, 0x54, 0x68, 0x72, 0x31, 0x6a, 0x6f, 0x6b, 0x6e, 0x34, + 0x6a, 0x56, 0x59, 0x50, 0x53, 0x6d, 0x30, 0x42, 0x34, 0x38, 0x32, 0x55, + 0x4a, 0x57, 0x2f, 0x62, 0x73, 0x47, 0x65, 0x36, 0x38, 0x53, 0x51, 0x73, + 0x6f, 0x57, 0x6f, 0x75, 0x37, 0x64, 0x43, 0x34, 0x41, 0x38, 0x48, 0x4f, + 0x64, 0x2f, 0x37, 0x6e, 0x70, 0x43, 0x79, 0x0a, 0x30, 0x63, 0x45, 0x2b, + 0x55, 0x35, 0x38, 0x44, 0x52, 0x4c, 0x42, 0x2b, 0x53, 0x2f, 0x52, 0x76, + 0x35, 0x48, 0x77, 0x66, 0x35, 0x2b, 0x4b, 0x78, 0x35, 0x4c, 0x69, 0x61, + 0x37, 0x38, 0x4f, 0x39, 0x7a, 0x74, 0x34, 0x4c, 0x4d, 0x6a, 0x54, 0x5a, + 0x33, 0x69, 0x6a, 0x74, 0x4d, 0x32, 0x76, 0x45, 0x31, 0x4e, 0x63, 0x39, + 0x45, 0x6c, 0x69, 0x72, 0x66, 0x51, 0x6b, 0x74, 0x79, 0x33, 0x44, 0x31, + 0x0a, 0x45, 0x34, 0x71, 0x55, 0x6f, 0x53, 0x65, 0x6b, 0x31, 0x6e, 0x44, + 0x46, 0x62, 0x5a, 0x53, 0x31, 0x79, 0x58, 0x32, 0x64, 0x6f, 0x4e, 0x4c, + 0x47, 0x43, 0x45, 0x6e, 0x5a, 0x5a, 0x70, 0x75, 0x6d, 0x30, 0x2f, 0x51, + 0x4c, 0x33, 0x4d, 0x55, 0x6d, 0x56, 0x2b, 0x47, 0x52, 0x4d, 0x4f, 0x72, + 0x4e, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, + 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, + 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, + 0x73, 0x74, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x31, 0x20, 0x43, + 0x41, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x4f, 0x3d, 0x41, 0x64, 0x64, + 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x20, 0x4f, 0x55, 0x3d, + 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x54, 0x54, 0x50, + 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x0a, 0x23, 0x20, 0x53, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x41, + 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x43, 0x6c, 0x61, 0x73, + 0x73, 0x20, 0x31, 0x20, 0x43, 0x41, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, + 0x4f, 0x3d, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, + 0x42, 0x20, 0x4f, 0x55, 0x3d, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, + 0x6b, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, + 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4c, 0x6f, 0x77, + 0x2d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x22, 0x0a, 0x23, 0x20, + 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x31, 0x0a, 0x23, 0x20, + 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, + 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x31, 0x65, 0x3a, 0x34, 0x32, 0x3a, 0x39, + 0x35, 0x3a, 0x30, 0x32, 0x3a, 0x33, 0x33, 0x3a, 0x39, 0x32, 0x3a, 0x36, + 0x62, 0x3a, 0x62, 0x39, 0x3a, 0x35, 0x66, 0x3a, 0x63, 0x30, 0x3a, 0x37, + 0x66, 0x3a, 0x64, 0x61, 0x3a, 0x64, 0x36, 0x3a, 0x62, 0x32, 0x3a, 0x34, + 0x62, 0x3a, 0x66, 0x63, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, + 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, + 0x20, 0x63, 0x63, 0x3a, 0x61, 0x62, 0x3a, 0x30, 0x65, 0x3a, 0x61, 0x30, + 0x3a, 0x34, 0x63, 0x3a, 0x32, 0x33, 0x3a, 0x30, 0x31, 0x3a, 0x64, 0x36, + 0x3a, 0x36, 0x39, 0x3a, 0x37, 0x62, 0x3a, 0x64, 0x64, 0x3a, 0x33, 0x37, + 0x3a, 0x39, 0x66, 0x3a, 0x63, 0x64, 0x3a, 0x31, 0x32, 0x3a, 0x65, 0x62, + 0x3a, 0x32, 0x34, 0x3a, 0x65, 0x33, 0x3a, 0x39, 0x34, 0x3a, 0x39, 0x64, + 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, + 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x38, + 0x63, 0x3a, 0x37, 0x32, 0x3a, 0x30, 0x39, 0x3a, 0x32, 0x37, 0x3a, 0x39, + 0x61, 0x3a, 0x63, 0x30, 0x3a, 0x34, 0x65, 0x3a, 0x32, 0x37, 0x3a, 0x35, + 0x65, 0x3a, 0x31, 0x36, 0x3a, 0x64, 0x30, 0x3a, 0x37, 0x66, 0x3a, 0x64, + 0x33, 0x3a, 0x62, 0x37, 0x3a, 0x37, 0x35, 0x3a, 0x65, 0x38, 0x3a, 0x30, + 0x31, 0x3a, 0x35, 0x34, 0x3a, 0x62, 0x35, 0x3a, 0x39, 0x36, 0x3a, 0x38, + 0x30, 0x3a, 0x34, 0x36, 0x3a, 0x65, 0x33, 0x3a, 0x31, 0x66, 0x3a, 0x35, + 0x32, 0x3a, 0x64, 0x64, 0x3a, 0x32, 0x35, 0x3a, 0x37, 0x36, 0x3a, 0x36, + 0x33, 0x3a, 0x32, 0x34, 0x3a, 0x65, 0x39, 0x3a, 0x61, 0x37, 0x0a, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, + 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x45, 0x47, 0x44, 0x43, 0x43, 0x41, + 0x77, 0x43, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x42, 0x41, + 0x54, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, + 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x42, 0x6c, 0x4d, + 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, + 0x77, 0x4a, 0x54, 0x52, 0x54, 0x45, 0x55, 0x0a, 0x4d, 0x42, 0x49, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4c, 0x51, 0x57, 0x52, 0x6b, + 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x51, 0x67, 0x51, 0x55, 0x49, 0x78, + 0x48, 0x54, 0x41, 0x62, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, + 0x46, 0x45, 0x46, 0x6b, 0x5a, 0x46, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, + 0x49, 0x46, 0x52, 0x55, 0x55, 0x43, 0x42, 0x4f, 0x5a, 0x58, 0x52, 0x33, + 0x0a, 0x62, 0x33, 0x4a, 0x72, 0x4d, 0x53, 0x45, 0x77, 0x48, 0x77, 0x59, + 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, 0x78, 0x68, 0x42, 0x5a, 0x47, 0x52, + 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x44, 0x62, 0x47, 0x46, + 0x7a, 0x63, 0x79, 0x41, 0x78, 0x49, 0x45, 0x4e, 0x42, 0x49, 0x46, 0x4a, + 0x76, 0x62, 0x33, 0x51, 0x77, 0x48, 0x68, 0x63, 0x4e, 0x4d, 0x44, 0x41, + 0x77, 0x4e, 0x54, 0x4d, 0x77, 0x0a, 0x4d, 0x54, 0x41, 0x7a, 0x4f, 0x44, + 0x4d, 0x78, 0x57, 0x68, 0x63, 0x4e, 0x4d, 0x6a, 0x41, 0x77, 0x4e, 0x54, + 0x4d, 0x77, 0x4d, 0x54, 0x41, 0x7a, 0x4f, 0x44, 0x4d, 0x78, 0x57, 0x6a, + 0x42, 0x6c, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, + 0x51, 0x47, 0x45, 0x77, 0x4a, 0x54, 0x52, 0x54, 0x45, 0x55, 0x4d, 0x42, + 0x49, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4c, 0x0a, 0x51, + 0x57, 0x52, 0x6b, 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x51, 0x67, 0x51, + 0x55, 0x49, 0x78, 0x48, 0x54, 0x41, 0x62, 0x42, 0x67, 0x4e, 0x56, 0x42, + 0x41, 0x73, 0x54, 0x46, 0x45, 0x46, 0x6b, 0x5a, 0x46, 0x52, 0x79, 0x64, + 0x58, 0x4e, 0x30, 0x49, 0x46, 0x52, 0x55, 0x55, 0x43, 0x42, 0x4f, 0x5a, + 0x58, 0x52, 0x33, 0x62, 0x33, 0x4a, 0x72, 0x4d, 0x53, 0x45, 0x77, 0x48, + 0x77, 0x59, 0x44, 0x0a, 0x56, 0x51, 0x51, 0x44, 0x45, 0x78, 0x68, 0x42, + 0x5a, 0x47, 0x52, 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x44, + 0x62, 0x47, 0x46, 0x7a, 0x63, 0x79, 0x41, 0x78, 0x49, 0x45, 0x4e, 0x42, + 0x49, 0x46, 0x4a, 0x76, 0x62, 0x33, 0x51, 0x77, 0x67, 0x67, 0x45, 0x69, + 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, + 0x44, 0x51, 0x45, 0x42, 0x41, 0x51, 0x55, 0x41, 0x0a, 0x41, 0x34, 0x49, + 0x42, 0x44, 0x77, 0x41, 0x77, 0x67, 0x67, 0x45, 0x4b, 0x41, 0x6f, 0x49, + 0x42, 0x41, 0x51, 0x43, 0x57, 0x6c, 0x74, 0x51, 0x68, 0x53, 0x57, 0x44, + 0x69, 0x61, 0x2b, 0x68, 0x42, 0x42, 0x77, 0x7a, 0x65, 0x78, 0x4f, 0x44, + 0x63, 0x45, 0x79, 0x50, 0x4e, 0x77, 0x54, 0x58, 0x48, 0x2b, 0x39, 0x5a, + 0x4f, 0x45, 0x51, 0x70, 0x6e, 0x58, 0x76, 0x55, 0x47, 0x57, 0x32, 0x75, + 0x6c, 0x0a, 0x43, 0x44, 0x74, 0x62, 0x4b, 0x52, 0x59, 0x36, 0x35, 0x34, + 0x65, 0x79, 0x4e, 0x41, 0x62, 0x46, 0x76, 0x41, 0x57, 0x6c, 0x41, 0x33, + 0x79, 0x43, 0x79, 0x79, 0x6b, 0x51, 0x72, 0x75, 0x47, 0x49, 0x67, 0x62, + 0x33, 0x57, 0x6e, 0x74, 0x50, 0x2b, 0x4c, 0x56, 0x62, 0x42, 0x46, 0x63, + 0x37, 0x6a, 0x4a, 0x70, 0x30, 0x56, 0x4c, 0x68, 0x44, 0x37, 0x42, 0x6f, + 0x38, 0x77, 0x42, 0x4e, 0x36, 0x6e, 0x0a, 0x74, 0x47, 0x4f, 0x30, 0x2f, + 0x37, 0x47, 0x63, 0x72, 0x6a, 0x79, 0x76, 0x64, 0x37, 0x5a, 0x57, 0x78, + 0x62, 0x57, 0x72, 0x6f, 0x75, 0x6c, 0x70, 0x4f, 0x6a, 0x30, 0x4f, 0x4d, + 0x33, 0x6b, 0x79, 0x50, 0x33, 0x43, 0x43, 0x6b, 0x70, 0x6c, 0x68, 0x62, + 0x59, 0x30, 0x77, 0x43, 0x49, 0x39, 0x78, 0x50, 0x36, 0x5a, 0x49, 0x56, + 0x78, 0x6e, 0x34, 0x4a, 0x64, 0x78, 0x4c, 0x5a, 0x6c, 0x79, 0x6c, 0x0a, + 0x64, 0x49, 0x2b, 0x59, 0x72, 0x73, 0x6a, 0x35, 0x77, 0x41, 0x59, 0x69, + 0x35, 0x36, 0x78, 0x7a, 0x33, 0x36, 0x55, 0x75, 0x2b, 0x31, 0x4c, 0x63, + 0x73, 0x52, 0x56, 0x6c, 0x49, 0x50, 0x6f, 0x31, 0x5a, 0x6d, 0x6e, 0x65, + 0x33, 0x79, 0x7a, 0x78, 0x62, 0x72, 0x77, 0x77, 0x32, 0x79, 0x77, 0x6b, + 0x45, 0x74, 0x76, 0x72, 0x4e, 0x54, 0x56, 0x6f, 0x6b, 0x4d, 0x73, 0x41, + 0x73, 0x4a, 0x63, 0x68, 0x0a, 0x50, 0x58, 0x51, 0x68, 0x49, 0x32, 0x55, + 0x30, 0x4b, 0x37, 0x74, 0x34, 0x57, 0x61, 0x50, 0x57, 0x34, 0x58, 0x59, + 0x35, 0x6d, 0x71, 0x52, 0x4a, 0x6a, 0x6f, 0x78, 0x30, 0x72, 0x32, 0x36, + 0x6b, 0x6d, 0x71, 0x50, 0x5a, 0x6d, 0x39, 0x49, 0x34, 0x58, 0x4a, 0x75, + 0x69, 0x47, 0x4d, 0x78, 0x31, 0x49, 0x34, 0x53, 0x2b, 0x36, 0x2b, 0x4a, + 0x4e, 0x4d, 0x33, 0x47, 0x4f, 0x47, 0x76, 0x44, 0x43, 0x0a, 0x2b, 0x4d, + 0x63, 0x64, 0x6f, 0x71, 0x30, 0x44, 0x6c, 0x79, 0x7a, 0x34, 0x7a, 0x79, + 0x58, 0x47, 0x39, 0x72, 0x67, 0x6b, 0x4d, 0x62, 0x46, 0x6a, 0x58, 0x5a, + 0x4a, 0x2f, 0x59, 0x2f, 0x41, 0x6c, 0x79, 0x56, 0x4d, 0x75, 0x48, 0x37, + 0x39, 0x4e, 0x41, 0x67, 0x4d, 0x42, 0x41, 0x41, 0x47, 0x6a, 0x67, 0x64, + 0x49, 0x77, 0x67, 0x63, 0x38, 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, 0x52, + 0x30, 0x4f, 0x0a, 0x42, 0x42, 0x59, 0x45, 0x46, 0x4a, 0x57, 0x78, 0x74, + 0x50, 0x43, 0x55, 0x74, 0x72, 0x33, 0x48, 0x32, 0x74, 0x45, 0x52, 0x43, + 0x53, 0x47, 0x2b, 0x77, 0x61, 0x39, 0x4a, 0x2f, 0x52, 0x42, 0x37, 0x4d, + 0x41, 0x73, 0x47, 0x41, 0x31, 0x55, 0x64, 0x44, 0x77, 0x51, 0x45, 0x41, + 0x77, 0x49, 0x42, 0x42, 0x6a, 0x41, 0x50, 0x42, 0x67, 0x4e, 0x56, 0x48, + 0x52, 0x4d, 0x42, 0x41, 0x66, 0x38, 0x45, 0x0a, 0x42, 0x54, 0x41, 0x44, + 0x41, 0x51, 0x48, 0x2f, 0x4d, 0x49, 0x47, 0x50, 0x42, 0x67, 0x4e, 0x56, + 0x48, 0x53, 0x4d, 0x45, 0x67, 0x59, 0x63, 0x77, 0x67, 0x59, 0x53, 0x41, + 0x46, 0x4a, 0x57, 0x78, 0x74, 0x50, 0x43, 0x55, 0x74, 0x72, 0x33, 0x48, + 0x32, 0x74, 0x45, 0x52, 0x43, 0x53, 0x47, 0x2b, 0x77, 0x61, 0x39, 0x4a, + 0x2f, 0x52, 0x42, 0x37, 0x6f, 0x57, 0x6d, 0x6b, 0x5a, 0x7a, 0x42, 0x6c, + 0x0a, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, + 0x47, 0x45, 0x77, 0x4a, 0x54, 0x52, 0x54, 0x45, 0x55, 0x4d, 0x42, 0x49, + 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4c, 0x51, 0x57, 0x52, + 0x6b, 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x51, 0x67, 0x51, 0x55, 0x49, + 0x78, 0x48, 0x54, 0x41, 0x62, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, + 0x54, 0x46, 0x45, 0x46, 0x6b, 0x0a, 0x5a, 0x46, 0x52, 0x79, 0x64, 0x58, + 0x4e, 0x30, 0x49, 0x46, 0x52, 0x55, 0x55, 0x43, 0x42, 0x4f, 0x5a, 0x58, + 0x52, 0x33, 0x62, 0x33, 0x4a, 0x72, 0x4d, 0x53, 0x45, 0x77, 0x48, 0x77, + 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, 0x78, 0x68, 0x42, 0x5a, 0x47, + 0x52, 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x44, 0x62, 0x47, + 0x46, 0x7a, 0x63, 0x79, 0x41, 0x78, 0x49, 0x45, 0x4e, 0x42, 0x0a, 0x49, + 0x46, 0x4a, 0x76, 0x62, 0x33, 0x53, 0x43, 0x41, 0x51, 0x45, 0x77, 0x44, + 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, + 0x51, 0x45, 0x46, 0x42, 0x51, 0x41, 0x44, 0x67, 0x67, 0x45, 0x42, 0x41, + 0x43, 0x78, 0x74, 0x5a, 0x42, 0x73, 0x66, 0x7a, 0x51, 0x33, 0x64, 0x75, + 0x51, 0x48, 0x36, 0x6c, 0x6d, 0x4d, 0x30, 0x4d, 0x6b, 0x68, 0x48, 0x6d, + 0x61, 0x36, 0x58, 0x0a, 0x37, 0x66, 0x31, 0x79, 0x46, 0x71, 0x5a, 0x7a, + 0x52, 0x31, 0x72, 0x30, 0x36, 0x39, 0x33, 0x70, 0x39, 0x64, 0x62, 0x37, + 0x52, 0x63, 0x77, 0x70, 0x69, 0x55, 0x52, 0x64, 0x76, 0x30, 0x59, 0x35, + 0x50, 0x65, 0x6a, 0x75, 0x76, 0x45, 0x31, 0x55, 0x68, 0x68, 0x34, 0x64, + 0x62, 0x4f, 0x4d, 0x58, 0x4a, 0x30, 0x50, 0x68, 0x69, 0x56, 0x59, 0x72, + 0x71, 0x57, 0x39, 0x79, 0x54, 0x6b, 0x6b, 0x7a, 0x0a, 0x34, 0x33, 0x4a, + 0x38, 0x4b, 0x69, 0x4f, 0x61, 0x76, 0x44, 0x37, 0x2f, 0x4b, 0x43, 0x72, + 0x74, 0x6f, 0x2f, 0x38, 0x63, 0x49, 0x37, 0x70, 0x44, 0x56, 0x77, 0x6c, + 0x6e, 0x54, 0x55, 0x74, 0x69, 0x42, 0x69, 0x33, 0x34, 0x2f, 0x32, 0x79, + 0x64, 0x59, 0x42, 0x37, 0x59, 0x48, 0x45, 0x74, 0x39, 0x74, 0x54, 0x45, + 0x76, 0x32, 0x64, 0x42, 0x38, 0x58, 0x66, 0x6a, 0x65, 0x61, 0x34, 0x4d, + 0x59, 0x0a, 0x65, 0x44, 0x64, 0x58, 0x4c, 0x2b, 0x67, 0x7a, 0x42, 0x32, + 0x66, 0x66, 0x48, 0x73, 0x64, 0x72, 0x4b, 0x70, 0x56, 0x32, 0x72, 0x6f, + 0x39, 0x58, 0x6f, 0x2f, 0x44, 0x30, 0x55, 0x72, 0x53, 0x70, 0x55, 0x77, + 0x6a, 0x50, 0x34, 0x45, 0x2f, 0x54, 0x65, 0x6c, 0x4f, 0x4c, 0x2f, 0x62, + 0x73, 0x63, 0x56, 0x6a, 0x62, 0x79, 0x2f, 0x72, 0x4b, 0x32, 0x35, 0x58, + 0x61, 0x37, 0x31, 0x53, 0x4a, 0x6c, 0x0a, 0x70, 0x7a, 0x2f, 0x2b, 0x30, + 0x57, 0x61, 0x74, 0x43, 0x37, 0x78, 0x72, 0x6d, 0x59, 0x62, 0x76, 0x50, + 0x33, 0x33, 0x7a, 0x47, 0x44, 0x4c, 0x4b, 0x65, 0x38, 0x62, 0x6a, 0x71, + 0x32, 0x52, 0x47, 0x6c, 0x66, 0x67, 0x6d, 0x61, 0x64, 0x6c, 0x56, 0x67, + 0x33, 0x73, 0x73, 0x6c, 0x67, 0x66, 0x2f, 0x57, 0x53, 0x78, 0x45, 0x6f, + 0x38, 0x62, 0x6c, 0x36, 0x61, 0x6e, 0x63, 0x6f, 0x57, 0x4f, 0x41, 0x0a, + 0x57, 0x69, 0x46, 0x65, 0x49, 0x63, 0x39, 0x54, 0x56, 0x50, 0x43, 0x36, + 0x62, 0x34, 0x6e, 0x62, 0x71, 0x4b, 0x71, 0x56, 0x7a, 0x34, 0x76, 0x6a, + 0x63, 0x63, 0x77, 0x65, 0x47, 0x79, 0x42, 0x45, 0x43, 0x4d, 0x42, 0x36, + 0x74, 0x6b, 0x44, 0x39, 0x78, 0x4f, 0x51, 0x31, 0x34, 0x52, 0x30, 0x57, + 0x48, 0x4e, 0x43, 0x38, 0x4b, 0x34, 0x37, 0x57, 0x63, 0x64, 0x6b, 0x3d, + 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, + 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, + 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, + 0x41, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x4f, 0x3d, 0x41, 0x64, 0x64, + 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x20, 0x4f, 0x55, 0x3d, + 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, + 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x41, 0x64, 0x64, 0x54, + 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x4f, 0x3d, + 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x20, + 0x4f, 0x55, 0x3d, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, + 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, + 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x0a, 0x23, 0x20, 0x4c, + 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x41, 0x64, 0x64, 0x54, 0x72, + 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, + 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x31, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, + 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, + 0x3a, 0x20, 0x31, 0x64, 0x3a, 0x33, 0x35, 0x3a, 0x35, 0x34, 0x3a, 0x30, + 0x34, 0x3a, 0x38, 0x35, 0x3a, 0x37, 0x38, 0x3a, 0x62, 0x30, 0x3a, 0x33, + 0x66, 0x3a, 0x34, 0x32, 0x3a, 0x34, 0x32, 0x3a, 0x34, 0x64, 0x3a, 0x62, + 0x66, 0x3a, 0x32, 0x30, 0x3a, 0x37, 0x33, 0x3a, 0x30, 0x61, 0x3a, 0x33, + 0x66, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, + 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x30, 0x32, + 0x3a, 0x66, 0x61, 0x3a, 0x66, 0x33, 0x3a, 0x65, 0x32, 0x3a, 0x39, 0x31, + 0x3a, 0x34, 0x33, 0x3a, 0x35, 0x34, 0x3a, 0x36, 0x38, 0x3a, 0x36, 0x30, + 0x3a, 0x37, 0x38, 0x3a, 0x35, 0x37, 0x3a, 0x36, 0x39, 0x3a, 0x34, 0x64, + 0x3a, 0x66, 0x35, 0x3a, 0x65, 0x34, 0x3a, 0x35, 0x62, 0x3a, 0x36, 0x38, + 0x3a, 0x38, 0x35, 0x3a, 0x31, 0x38, 0x3a, 0x36, 0x38, 0x0a, 0x23, 0x20, + 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x36, 0x38, 0x3a, 0x37, + 0x66, 0x3a, 0x61, 0x34, 0x3a, 0x35, 0x31, 0x3a, 0x33, 0x38, 0x3a, 0x32, + 0x32, 0x3a, 0x37, 0x38, 0x3a, 0x66, 0x66, 0x3a, 0x66, 0x30, 0x3a, 0x63, + 0x38, 0x3a, 0x62, 0x31, 0x3a, 0x31, 0x66, 0x3a, 0x38, 0x64, 0x3a, 0x34, + 0x33, 0x3a, 0x64, 0x35, 0x3a, 0x37, 0x36, 0x3a, 0x36, 0x37, 0x3a, 0x31, + 0x63, 0x3a, 0x36, 0x65, 0x3a, 0x62, 0x32, 0x3a, 0x62, 0x63, 0x3a, 0x65, + 0x61, 0x3a, 0x62, 0x34, 0x3a, 0x31, 0x33, 0x3a, 0x66, 0x62, 0x3a, 0x38, + 0x33, 0x3a, 0x64, 0x39, 0x3a, 0x36, 0x35, 0x3a, 0x64, 0x30, 0x3a, 0x36, + 0x64, 0x3a, 0x32, 0x66, 0x3a, 0x66, 0x32, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, + 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, + 0x4d, 0x49, 0x49, 0x45, 0x4e, 0x6a, 0x43, 0x43, 0x41, 0x78, 0x36, 0x67, + 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x42, 0x41, 0x54, 0x41, 0x4e, + 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, + 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x42, 0x76, 0x4d, 0x51, 0x73, 0x77, + 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x54, + 0x52, 0x54, 0x45, 0x55, 0x0a, 0x4d, 0x42, 0x49, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x43, 0x68, 0x4d, 0x4c, 0x51, 0x57, 0x52, 0x6b, 0x56, 0x48, 0x4a, + 0x31, 0x63, 0x33, 0x51, 0x67, 0x51, 0x55, 0x49, 0x78, 0x4a, 0x6a, 0x41, + 0x6b, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, 0x48, 0x55, 0x46, + 0x6b, 0x5a, 0x46, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x49, 0x45, 0x56, + 0x34, 0x64, 0x47, 0x56, 0x79, 0x62, 0x6d, 0x46, 0x73, 0x0a, 0x49, 0x46, + 0x52, 0x55, 0x55, 0x43, 0x42, 0x4f, 0x5a, 0x58, 0x52, 0x33, 0x62, 0x33, + 0x4a, 0x72, 0x4d, 0x53, 0x49, 0x77, 0x49, 0x41, 0x59, 0x44, 0x56, 0x51, + 0x51, 0x44, 0x45, 0x78, 0x6c, 0x42, 0x5a, 0x47, 0x52, 0x55, 0x63, 0x6e, + 0x56, 0x7a, 0x64, 0x43, 0x42, 0x46, 0x65, 0x48, 0x52, 0x6c, 0x63, 0x6d, + 0x35, 0x68, 0x62, 0x43, 0x42, 0x44, 0x51, 0x53, 0x42, 0x53, 0x62, 0x32, + 0x39, 0x30, 0x0a, 0x4d, 0x42, 0x34, 0x58, 0x44, 0x54, 0x41, 0x77, 0x4d, + 0x44, 0x55, 0x7a, 0x4d, 0x44, 0x45, 0x77, 0x4e, 0x44, 0x67, 0x7a, 0x4f, + 0x46, 0x6f, 0x58, 0x44, 0x54, 0x49, 0x77, 0x4d, 0x44, 0x55, 0x7a, 0x4d, + 0x44, 0x45, 0x77, 0x4e, 0x44, 0x67, 0x7a, 0x4f, 0x46, 0x6f, 0x77, 0x62, + 0x7a, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, + 0x68, 0x4d, 0x43, 0x55, 0x30, 0x55, 0x78, 0x0a, 0x46, 0x44, 0x41, 0x53, + 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, 0x43, 0x30, 0x46, 0x6b, + 0x5a, 0x46, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x49, 0x45, 0x46, 0x43, + 0x4d, 0x53, 0x59, 0x77, 0x4a, 0x41, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, + 0x45, 0x78, 0x31, 0x42, 0x5a, 0x47, 0x52, 0x55, 0x63, 0x6e, 0x56, 0x7a, + 0x64, 0x43, 0x42, 0x46, 0x65, 0x48, 0x52, 0x6c, 0x63, 0x6d, 0x35, 0x68, + 0x0a, 0x62, 0x43, 0x42, 0x55, 0x56, 0x46, 0x41, 0x67, 0x54, 0x6d, 0x56, + 0x30, 0x64, 0x32, 0x39, 0x79, 0x61, 0x7a, 0x45, 0x69, 0x4d, 0x43, 0x41, + 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x5a, 0x51, 0x57, 0x52, + 0x6b, 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x51, 0x67, 0x52, 0x58, 0x68, + 0x30, 0x5a, 0x58, 0x4a, 0x75, 0x59, 0x57, 0x77, 0x67, 0x51, 0x30, 0x45, + 0x67, 0x55, 0x6d, 0x39, 0x76, 0x0a, 0x64, 0x44, 0x43, 0x43, 0x41, 0x53, + 0x49, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, + 0x63, 0x4e, 0x41, 0x51, 0x45, 0x42, 0x42, 0x51, 0x41, 0x44, 0x67, 0x67, + 0x45, 0x50, 0x41, 0x44, 0x43, 0x43, 0x41, 0x51, 0x6f, 0x43, 0x67, 0x67, + 0x45, 0x42, 0x41, 0x4c, 0x66, 0x33, 0x47, 0x6a, 0x50, 0x6d, 0x38, 0x67, + 0x41, 0x45, 0x4c, 0x54, 0x6e, 0x67, 0x54, 0x6c, 0x76, 0x74, 0x0a, 0x48, + 0x37, 0x78, 0x73, 0x44, 0x38, 0x32, 0x31, 0x2b, 0x69, 0x4f, 0x32, 0x7a, + 0x74, 0x36, 0x62, 0x45, 0x54, 0x4f, 0x58, 0x70, 0x43, 0x6c, 0x4d, 0x66, + 0x5a, 0x4f, 0x66, 0x76, 0x55, 0x71, 0x38, 0x6b, 0x2b, 0x30, 0x44, 0x47, + 0x75, 0x4f, 0x50, 0x7a, 0x2b, 0x56, 0x74, 0x55, 0x46, 0x72, 0x57, 0x6c, + 0x79, 0x6d, 0x55, 0x57, 0x6f, 0x43, 0x77, 0x53, 0x58, 0x72, 0x62, 0x4c, + 0x70, 0x58, 0x39, 0x0a, 0x75, 0x4d, 0x71, 0x2f, 0x4e, 0x7a, 0x67, 0x74, + 0x48, 0x6a, 0x36, 0x52, 0x51, 0x61, 0x31, 0x77, 0x56, 0x73, 0x66, 0x77, + 0x54, 0x7a, 0x2f, 0x6f, 0x4d, 0x70, 0x35, 0x30, 0x79, 0x73, 0x69, 0x51, + 0x56, 0x4f, 0x6e, 0x47, 0x58, 0x77, 0x39, 0x34, 0x6e, 0x5a, 0x70, 0x41, + 0x50, 0x41, 0x36, 0x73, 0x59, 0x61, 0x70, 0x65, 0x46, 0x49, 0x2b, 0x65, + 0x68, 0x36, 0x46, 0x71, 0x55, 0x4e, 0x7a, 0x58, 0x0a, 0x6d, 0x6b, 0x36, + 0x76, 0x42, 0x62, 0x4f, 0x6d, 0x63, 0x5a, 0x53, 0x63, 0x63, 0x62, 0x4e, + 0x51, 0x59, 0x41, 0x72, 0x48, 0x45, 0x35, 0x30, 0x34, 0x42, 0x34, 0x59, + 0x43, 0x71, 0x4f, 0x6d, 0x6f, 0x61, 0x53, 0x59, 0x59, 0x6b, 0x4b, 0x74, + 0x4d, 0x73, 0x45, 0x38, 0x6a, 0x71, 0x7a, 0x70, 0x50, 0x68, 0x4e, 0x6a, + 0x66, 0x7a, 0x70, 0x2f, 0x68, 0x61, 0x57, 0x2b, 0x37, 0x31, 0x30, 0x4c, + 0x58, 0x0a, 0x61, 0x30, 0x54, 0x6b, 0x78, 0x36, 0x33, 0x75, 0x62, 0x55, + 0x46, 0x66, 0x63, 0x6c, 0x70, 0x78, 0x43, 0x44, 0x65, 0x7a, 0x65, 0x57, + 0x57, 0x6b, 0x57, 0x61, 0x43, 0x55, 0x4e, 0x2f, 0x63, 0x41, 0x4c, 0x77, + 0x33, 0x43, 0x6b, 0x6e, 0x4c, 0x61, 0x30, 0x44, 0x68, 0x79, 0x32, 0x78, + 0x53, 0x6f, 0x52, 0x63, 0x52, 0x64, 0x4b, 0x6e, 0x32, 0x33, 0x74, 0x4e, + 0x62, 0x45, 0x37, 0x71, 0x7a, 0x4e, 0x0a, 0x45, 0x30, 0x53, 0x33, 0x79, + 0x53, 0x76, 0x64, 0x51, 0x77, 0x41, 0x6c, 0x2b, 0x6d, 0x47, 0x35, 0x61, + 0x57, 0x70, 0x59, 0x49, 0x78, 0x47, 0x33, 0x70, 0x7a, 0x4f, 0x50, 0x56, + 0x6e, 0x56, 0x5a, 0x39, 0x63, 0x30, 0x70, 0x31, 0x30, 0x61, 0x33, 0x43, + 0x69, 0x74, 0x6c, 0x74, 0x74, 0x4e, 0x43, 0x62, 0x78, 0x57, 0x79, 0x75, + 0x48, 0x76, 0x37, 0x37, 0x2b, 0x6c, 0x64, 0x55, 0x39, 0x55, 0x30, 0x0a, + 0x57, 0x69, 0x63, 0x43, 0x41, 0x77, 0x45, 0x41, 0x41, 0x61, 0x4f, 0x42, + 0x33, 0x44, 0x43, 0x42, 0x32, 0x54, 0x41, 0x64, 0x42, 0x67, 0x4e, 0x56, + 0x48, 0x51, 0x34, 0x45, 0x46, 0x67, 0x51, 0x55, 0x72, 0x62, 0x32, 0x59, + 0x65, 0x6a, 0x53, 0x30, 0x4a, 0x76, 0x66, 0x36, 0x78, 0x43, 0x5a, 0x55, + 0x37, 0x77, 0x4f, 0x39, 0x34, 0x43, 0x54, 0x4c, 0x56, 0x42, 0x6f, 0x77, + 0x43, 0x77, 0x59, 0x44, 0x0a, 0x56, 0x52, 0x30, 0x50, 0x42, 0x41, 0x51, + 0x44, 0x41, 0x67, 0x45, 0x47, 0x4d, 0x41, 0x38, 0x47, 0x41, 0x31, 0x55, + 0x64, 0x45, 0x77, 0x45, 0x42, 0x2f, 0x77, 0x51, 0x46, 0x4d, 0x41, 0x4d, + 0x42, 0x41, 0x66, 0x38, 0x77, 0x67, 0x5a, 0x6b, 0x47, 0x41, 0x31, 0x55, + 0x64, 0x49, 0x77, 0x53, 0x42, 0x6b, 0x54, 0x43, 0x42, 0x6a, 0x6f, 0x41, + 0x55, 0x72, 0x62, 0x32, 0x59, 0x65, 0x6a, 0x53, 0x30, 0x0a, 0x4a, 0x76, + 0x66, 0x36, 0x78, 0x43, 0x5a, 0x55, 0x37, 0x77, 0x4f, 0x39, 0x34, 0x43, + 0x54, 0x4c, 0x56, 0x42, 0x71, 0x68, 0x63, 0x36, 0x52, 0x78, 0x4d, 0x47, + 0x38, 0x78, 0x43, 0x7a, 0x41, 0x4a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, + 0x59, 0x54, 0x41, 0x6c, 0x4e, 0x46, 0x4d, 0x52, 0x51, 0x77, 0x45, 0x67, + 0x59, 0x44, 0x56, 0x51, 0x51, 0x4b, 0x45, 0x77, 0x74, 0x42, 0x5a, 0x47, + 0x52, 0x55, 0x0a, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x42, 0x51, + 0x6a, 0x45, 0x6d, 0x4d, 0x43, 0x51, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, + 0x78, 0x4d, 0x64, 0x51, 0x57, 0x52, 0x6b, 0x56, 0x48, 0x4a, 0x31, 0x63, + 0x33, 0x51, 0x67, 0x52, 0x58, 0x68, 0x30, 0x5a, 0x58, 0x4a, 0x75, 0x59, + 0x57, 0x77, 0x67, 0x56, 0x46, 0x52, 0x51, 0x49, 0x45, 0x35, 0x6c, 0x64, + 0x48, 0x64, 0x76, 0x63, 0x6d, 0x73, 0x78, 0x0a, 0x49, 0x6a, 0x41, 0x67, + 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x4d, 0x54, 0x47, 0x55, 0x46, 0x6b, + 0x5a, 0x46, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x49, 0x45, 0x56, 0x34, + 0x64, 0x47, 0x56, 0x79, 0x62, 0x6d, 0x46, 0x73, 0x49, 0x45, 0x4e, 0x42, + 0x49, 0x46, 0x4a, 0x76, 0x62, 0x33, 0x53, 0x43, 0x41, 0x51, 0x45, 0x77, + 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, + 0x0a, 0x41, 0x51, 0x45, 0x46, 0x42, 0x51, 0x41, 0x44, 0x67, 0x67, 0x45, + 0x42, 0x41, 0x4c, 0x43, 0x62, 0x34, 0x49, 0x55, 0x6c, 0x77, 0x74, 0x59, + 0x6a, 0x34, 0x67, 0x2b, 0x57, 0x42, 0x70, 0x4b, 0x64, 0x51, 0x5a, 0x69, + 0x63, 0x32, 0x59, 0x52, 0x35, 0x67, 0x64, 0x6b, 0x65, 0x57, 0x78, 0x51, + 0x48, 0x49, 0x7a, 0x5a, 0x6c, 0x6a, 0x37, 0x44, 0x59, 0x64, 0x37, 0x75, + 0x73, 0x51, 0x57, 0x78, 0x48, 0x0a, 0x59, 0x49, 0x4e, 0x52, 0x73, 0x50, + 0x6b, 0x79, 0x50, 0x65, 0x66, 0x38, 0x39, 0x69, 0x59, 0x54, 0x78, 0x34, + 0x41, 0x57, 0x70, 0x62, 0x39, 0x61, 0x2f, 0x49, 0x66, 0x50, 0x65, 0x48, + 0x6d, 0x4a, 0x49, 0x5a, 0x72, 0x69, 0x54, 0x41, 0x63, 0x4b, 0x68, 0x6a, + 0x57, 0x38, 0x38, 0x74, 0x35, 0x52, 0x78, 0x4e, 0x4b, 0x57, 0x74, 0x39, + 0x78, 0x2b, 0x54, 0x75, 0x35, 0x77, 0x2f, 0x52, 0x77, 0x35, 0x0a, 0x36, + 0x77, 0x77, 0x43, 0x55, 0x52, 0x51, 0x74, 0x6a, 0x72, 0x30, 0x57, 0x34, + 0x4d, 0x48, 0x66, 0x52, 0x6e, 0x58, 0x6e, 0x4a, 0x4b, 0x33, 0x73, 0x39, + 0x45, 0x4b, 0x30, 0x68, 0x5a, 0x4e, 0x77, 0x45, 0x47, 0x65, 0x36, 0x6e, + 0x51, 0x59, 0x31, 0x53, 0x68, 0x6a, 0x54, 0x4b, 0x33, 0x72, 0x4d, 0x55, + 0x55, 0x4b, 0x68, 0x65, 0x6d, 0x50, 0x52, 0x35, 0x72, 0x75, 0x68, 0x78, + 0x53, 0x76, 0x43, 0x0a, 0x4e, 0x72, 0x34, 0x54, 0x44, 0x65, 0x61, 0x39, + 0x59, 0x33, 0x35, 0x35, 0x65, 0x36, 0x63, 0x4a, 0x44, 0x55, 0x43, 0x72, + 0x61, 0x74, 0x32, 0x50, 0x69, 0x73, 0x50, 0x32, 0x39, 0x6f, 0x77, 0x61, + 0x51, 0x67, 0x56, 0x52, 0x31, 0x45, 0x58, 0x31, 0x6e, 0x36, 0x64, 0x69, + 0x49, 0x57, 0x67, 0x56, 0x49, 0x45, 0x4d, 0x38, 0x6d, 0x65, 0x64, 0x38, + 0x76, 0x53, 0x54, 0x59, 0x71, 0x5a, 0x45, 0x58, 0x0a, 0x63, 0x34, 0x67, + 0x2f, 0x56, 0x68, 0x73, 0x78, 0x4f, 0x42, 0x69, 0x30, 0x63, 0x51, 0x2b, + 0x61, 0x7a, 0x63, 0x67, 0x4f, 0x6e, 0x6f, 0x34, 0x75, 0x47, 0x2b, 0x47, + 0x4d, 0x6d, 0x49, 0x50, 0x4c, 0x48, 0x7a, 0x48, 0x78, 0x52, 0x45, 0x7a, + 0x47, 0x42, 0x48, 0x4e, 0x4a, 0x64, 0x6d, 0x41, 0x50, 0x78, 0x2f, 0x69, + 0x39, 0x46, 0x34, 0x42, 0x72, 0x4c, 0x75, 0x6e, 0x4d, 0x54, 0x41, 0x35, + 0x61, 0x0a, 0x6d, 0x6e, 0x6b, 0x50, 0x49, 0x41, 0x6f, 0x75, 0x31, 0x5a, + 0x35, 0x6a, 0x4a, 0x68, 0x35, 0x56, 0x6b, 0x70, 0x54, 0x59, 0x67, 0x68, + 0x64, 0x61, 0x65, 0x39, 0x43, 0x38, 0x78, 0x34, 0x39, 0x4f, 0x68, 0x67, + 0x51, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, + 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, + 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x41, 0x64, 0x64, 0x54, 0x72, + 0x75, 0x73, 0x74, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x43, + 0x41, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x4f, 0x3d, 0x41, 0x64, 0x64, + 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x20, 0x4f, 0x55, 0x3d, + 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x54, 0x54, 0x50, + 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x0a, 0x23, 0x20, 0x53, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x41, + 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x50, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x20, 0x43, 0x41, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x4f, + 0x3d, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, + 0x20, 0x4f, 0x55, 0x3d, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, + 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, + 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x41, + 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x50, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, + 0x52, 0x6f, 0x6f, 0x74, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, + 0x61, 0x6c, 0x3a, 0x20, 0x31, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, + 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, + 0x20, 0x63, 0x31, 0x3a, 0x36, 0x32, 0x3a, 0x33, 0x65, 0x3a, 0x32, 0x33, + 0x3a, 0x63, 0x35, 0x3a, 0x38, 0x32, 0x3a, 0x37, 0x33, 0x3a, 0x39, 0x63, + 0x3a, 0x30, 0x33, 0x3a, 0x35, 0x39, 0x3a, 0x34, 0x62, 0x3a, 0x32, 0x62, + 0x3a, 0x65, 0x39, 0x3a, 0x37, 0x37, 0x3a, 0x34, 0x39, 0x3a, 0x37, 0x66, + 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, + 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x32, 0x61, 0x3a, + 0x62, 0x36, 0x3a, 0x32, 0x38, 0x3a, 0x34, 0x38, 0x3a, 0x35, 0x65, 0x3a, + 0x37, 0x38, 0x3a, 0x66, 0x62, 0x3a, 0x66, 0x33, 0x3a, 0x61, 0x64, 0x3a, + 0x39, 0x65, 0x3a, 0x37, 0x39, 0x3a, 0x31, 0x30, 0x3a, 0x64, 0x64, 0x3a, + 0x36, 0x62, 0x3a, 0x64, 0x66, 0x3a, 0x39, 0x39, 0x3a, 0x37, 0x32, 0x3a, + 0x32, 0x63, 0x3a, 0x39, 0x36, 0x3a, 0x65, 0x35, 0x0a, 0x23, 0x20, 0x53, + 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x30, 0x37, 0x3a, 0x39, 0x31, + 0x3a, 0x63, 0x61, 0x3a, 0x30, 0x37, 0x3a, 0x34, 0x39, 0x3a, 0x62, 0x32, + 0x3a, 0x30, 0x37, 0x3a, 0x38, 0x32, 0x3a, 0x61, 0x61, 0x3a, 0x64, 0x33, + 0x3a, 0x63, 0x37, 0x3a, 0x64, 0x37, 0x3a, 0x62, 0x64, 0x3a, 0x30, 0x63, + 0x3a, 0x64, 0x66, 0x3a, 0x63, 0x39, 0x3a, 0x34, 0x38, 0x3a, 0x35, 0x38, + 0x3a, 0x33, 0x35, 0x3a, 0x38, 0x34, 0x3a, 0x33, 0x65, 0x3a, 0x62, 0x32, + 0x3a, 0x64, 0x37, 0x3a, 0x39, 0x39, 0x3a, 0x36, 0x30, 0x3a, 0x30, 0x39, + 0x3a, 0x63, 0x65, 0x3a, 0x34, 0x33, 0x3a, 0x61, 0x62, 0x3a, 0x36, 0x63, + 0x3a, 0x36, 0x39, 0x3a, 0x32, 0x37, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, + 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, + 0x49, 0x49, 0x45, 0x46, 0x54, 0x43, 0x43, 0x41, 0x76, 0x32, 0x67, 0x41, + 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x42, 0x41, 0x54, 0x41, 0x4e, 0x42, + 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, + 0x51, 0x55, 0x46, 0x41, 0x44, 0x42, 0x6b, 0x4d, 0x51, 0x73, 0x77, 0x43, + 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x54, 0x52, + 0x54, 0x45, 0x55, 0x0a, 0x4d, 0x42, 0x49, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x43, 0x68, 0x4d, 0x4c, 0x51, 0x57, 0x52, 0x6b, 0x56, 0x48, 0x4a, 0x31, + 0x63, 0x33, 0x51, 0x67, 0x51, 0x55, 0x49, 0x78, 0x48, 0x54, 0x41, 0x62, + 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, 0x46, 0x45, 0x46, 0x6b, + 0x5a, 0x46, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x49, 0x46, 0x52, 0x55, + 0x55, 0x43, 0x42, 0x4f, 0x5a, 0x58, 0x52, 0x33, 0x0a, 0x62, 0x33, 0x4a, + 0x72, 0x4d, 0x53, 0x41, 0x77, 0x48, 0x67, 0x59, 0x44, 0x56, 0x51, 0x51, + 0x44, 0x45, 0x78, 0x64, 0x42, 0x5a, 0x47, 0x52, 0x55, 0x63, 0x6e, 0x56, + 0x7a, 0x64, 0x43, 0x42, 0x51, 0x64, 0x57, 0x4a, 0x73, 0x61, 0x57, 0x4d, + 0x67, 0x51, 0x30, 0x45, 0x67, 0x55, 0x6d, 0x39, 0x76, 0x64, 0x44, 0x41, + 0x65, 0x46, 0x77, 0x30, 0x77, 0x4d, 0x44, 0x41, 0x31, 0x4d, 0x7a, 0x41, + 0x78, 0x0a, 0x4d, 0x44, 0x51, 0x78, 0x4e, 0x54, 0x42, 0x61, 0x46, 0x77, + 0x30, 0x79, 0x4d, 0x44, 0x41, 0x31, 0x4d, 0x7a, 0x41, 0x78, 0x4d, 0x44, + 0x51, 0x78, 0x4e, 0x54, 0x42, 0x61, 0x4d, 0x47, 0x51, 0x78, 0x43, 0x7a, + 0x41, 0x4a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x59, 0x54, 0x41, 0x6c, + 0x4e, 0x46, 0x4d, 0x52, 0x51, 0x77, 0x45, 0x67, 0x59, 0x44, 0x56, 0x51, + 0x51, 0x4b, 0x45, 0x77, 0x74, 0x42, 0x0a, 0x5a, 0x47, 0x52, 0x55, 0x63, + 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x42, 0x51, 0x6a, 0x45, 0x64, 0x4d, + 0x42, 0x73, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x78, 0x4d, 0x55, 0x51, + 0x57, 0x52, 0x6b, 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x51, 0x67, 0x56, + 0x46, 0x52, 0x51, 0x49, 0x45, 0x35, 0x6c, 0x64, 0x48, 0x64, 0x76, 0x63, + 0x6d, 0x73, 0x78, 0x49, 0x44, 0x41, 0x65, 0x42, 0x67, 0x4e, 0x56, 0x0a, + 0x42, 0x41, 0x4d, 0x54, 0x46, 0x30, 0x46, 0x6b, 0x5a, 0x46, 0x52, 0x79, + 0x64, 0x58, 0x4e, 0x30, 0x49, 0x46, 0x42, 0x31, 0x59, 0x6d, 0x78, 0x70, + 0x59, 0x79, 0x42, 0x44, 0x51, 0x53, 0x42, 0x53, 0x62, 0x32, 0x39, 0x30, + 0x4d, 0x49, 0x49, 0x42, 0x49, 0x6a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, + 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x45, 0x46, + 0x41, 0x41, 0x4f, 0x43, 0x0a, 0x41, 0x51, 0x38, 0x41, 0x4d, 0x49, 0x49, + 0x42, 0x43, 0x67, 0x4b, 0x43, 0x41, 0x51, 0x45, 0x41, 0x36, 0x52, 0x6f, + 0x77, 0x6a, 0x34, 0x4f, 0x49, 0x46, 0x4d, 0x45, 0x67, 0x32, 0x44, 0x79, + 0x62, 0x6a, 0x78, 0x74, 0x2b, 0x41, 0x33, 0x53, 0x37, 0x32, 0x6d, 0x6e, + 0x54, 0x52, 0x71, 0x58, 0x34, 0x6a, 0x73, 0x49, 0x4d, 0x45, 0x5a, 0x42, + 0x52, 0x70, 0x53, 0x39, 0x6d, 0x56, 0x45, 0x42, 0x56, 0x0a, 0x36, 0x74, + 0x73, 0x66, 0x53, 0x6c, 0x62, 0x75, 0x6e, 0x79, 0x4e, 0x75, 0x39, 0x44, + 0x6e, 0x4c, 0x6f, 0x62, 0x6c, 0x76, 0x38, 0x6e, 0x37, 0x35, 0x58, 0x59, + 0x63, 0x6d, 0x59, 0x5a, 0x34, 0x63, 0x2b, 0x4f, 0x4c, 0x73, 0x70, 0x6f, + 0x48, 0x34, 0x49, 0x63, 0x55, 0x6b, 0x7a, 0x42, 0x45, 0x4d, 0x50, 0x39, + 0x73, 0x6d, 0x63, 0x6e, 0x72, 0x48, 0x41, 0x5a, 0x63, 0x48, 0x46, 0x2f, + 0x6e, 0x58, 0x0a, 0x47, 0x43, 0x77, 0x77, 0x66, 0x51, 0x35, 0x36, 0x48, + 0x6d, 0x49, 0x65, 0x78, 0x6b, 0x76, 0x41, 0x2f, 0x58, 0x31, 0x69, 0x64, + 0x39, 0x4e, 0x45, 0x48, 0x69, 0x66, 0x32, 0x50, 0x30, 0x74, 0x45, 0x73, + 0x37, 0x63, 0x34, 0x32, 0x54, 0x6b, 0x66, 0x59, 0x4e, 0x56, 0x52, 0x6b, + 0x6e, 0x4d, 0x44, 0x74, 0x41, 0x42, 0x70, 0x34, 0x2f, 0x4d, 0x55, 0x54, + 0x75, 0x37, 0x52, 0x33, 0x41, 0x6e, 0x50, 0x0a, 0x64, 0x7a, 0x52, 0x47, + 0x55, 0x4c, 0x44, 0x34, 0x45, 0x66, 0x4c, 0x2b, 0x4f, 0x48, 0x6e, 0x33, + 0x42, 0x7a, 0x6e, 0x2b, 0x55, 0x5a, 0x4b, 0x58, 0x43, 0x31, 0x73, 0x49, + 0x58, 0x7a, 0x53, 0x47, 0x41, 0x61, 0x32, 0x49, 0x6c, 0x2b, 0x74, 0x6d, + 0x7a, 0x56, 0x37, 0x52, 0x2f, 0x39, 0x78, 0x39, 0x38, 0x6f, 0x54, 0x61, + 0x75, 0x6e, 0x65, 0x74, 0x33, 0x49, 0x41, 0x49, 0x78, 0x36, 0x65, 0x48, + 0x0a, 0x31, 0x6c, 0x57, 0x66, 0x6c, 0x32, 0x72, 0x6f, 0x79, 0x42, 0x46, + 0x6b, 0x75, 0x75, 0x63, 0x5a, 0x4b, 0x54, 0x38, 0x52, 0x73, 0x33, 0x69, + 0x51, 0x68, 0x43, 0x42, 0x53, 0x57, 0x78, 0x48, 0x76, 0x65, 0x4e, 0x43, + 0x44, 0x39, 0x74, 0x56, 0x49, 0x6b, 0x4e, 0x41, 0x77, 0x48, 0x4d, 0x2b, + 0x41, 0x2b, 0x57, 0x44, 0x2b, 0x65, 0x65, 0x53, 0x49, 0x38, 0x74, 0x30, + 0x41, 0x36, 0x35, 0x52, 0x46, 0x0a, 0x36, 0x32, 0x57, 0x55, 0x61, 0x55, + 0x43, 0x36, 0x77, 0x4e, 0x57, 0x30, 0x75, 0x4c, 0x70, 0x39, 0x42, 0x42, + 0x47, 0x6f, 0x36, 0x7a, 0x45, 0x46, 0x6c, 0x70, 0x52, 0x4f, 0x57, 0x43, + 0x47, 0x4f, 0x6e, 0x39, 0x42, 0x67, 0x2f, 0x51, 0x49, 0x44, 0x41, 0x51, + 0x41, 0x42, 0x6f, 0x34, 0x48, 0x52, 0x4d, 0x49, 0x48, 0x4f, 0x4d, 0x42, + 0x30, 0x47, 0x41, 0x31, 0x55, 0x64, 0x44, 0x67, 0x51, 0x57, 0x0a, 0x42, + 0x42, 0x53, 0x42, 0x50, 0x6a, 0x66, 0x59, 0x6b, 0x72, 0x41, 0x66, 0x64, + 0x35, 0x39, 0x63, 0x74, 0x4b, 0x74, 0x7a, 0x71, 0x75, 0x66, 0x32, 0x4e, + 0x47, 0x41, 0x76, 0x2b, 0x6a, 0x41, 0x4c, 0x42, 0x67, 0x4e, 0x56, 0x48, + 0x51, 0x38, 0x45, 0x42, 0x41, 0x4d, 0x43, 0x41, 0x51, 0x59, 0x77, 0x44, + 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, 0x54, 0x41, 0x51, 0x48, 0x2f, 0x42, + 0x41, 0x55, 0x77, 0x0a, 0x41, 0x77, 0x45, 0x42, 0x2f, 0x7a, 0x43, 0x42, + 0x6a, 0x67, 0x59, 0x44, 0x56, 0x52, 0x30, 0x6a, 0x42, 0x49, 0x47, 0x47, + 0x4d, 0x49, 0x47, 0x44, 0x67, 0x42, 0x53, 0x42, 0x50, 0x6a, 0x66, 0x59, + 0x6b, 0x72, 0x41, 0x66, 0x64, 0x35, 0x39, 0x63, 0x74, 0x4b, 0x74, 0x7a, + 0x71, 0x75, 0x66, 0x32, 0x4e, 0x47, 0x41, 0x76, 0x2b, 0x71, 0x46, 0x6f, + 0x70, 0x47, 0x59, 0x77, 0x5a, 0x44, 0x45, 0x4c, 0x0a, 0x4d, 0x41, 0x6b, + 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x55, 0x30, 0x55, + 0x78, 0x46, 0x44, 0x41, 0x53, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, + 0x54, 0x43, 0x30, 0x46, 0x6b, 0x5a, 0x46, 0x52, 0x79, 0x64, 0x58, 0x4e, + 0x30, 0x49, 0x45, 0x46, 0x43, 0x4d, 0x52, 0x30, 0x77, 0x47, 0x77, 0x59, + 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, 0x78, 0x52, 0x42, 0x5a, 0x47, 0x52, + 0x55, 0x0a, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x55, 0x56, 0x46, + 0x41, 0x67, 0x54, 0x6d, 0x56, 0x30, 0x64, 0x32, 0x39, 0x79, 0x61, 0x7a, + 0x45, 0x67, 0x4d, 0x42, 0x34, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, + 0x4d, 0x58, 0x51, 0x57, 0x52, 0x6b, 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, + 0x51, 0x67, 0x55, 0x48, 0x56, 0x69, 0x62, 0x47, 0x6c, 0x6a, 0x49, 0x45, + 0x4e, 0x42, 0x49, 0x46, 0x4a, 0x76, 0x0a, 0x62, 0x33, 0x53, 0x43, 0x41, + 0x51, 0x45, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, + 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x46, 0x42, 0x51, 0x41, 0x44, 0x67, + 0x67, 0x45, 0x42, 0x41, 0x41, 0x50, 0x33, 0x46, 0x55, 0x72, 0x34, 0x4a, + 0x4e, 0x6f, 0x6a, 0x56, 0x68, 0x61, 0x54, 0x64, 0x74, 0x30, 0x32, 0x4b, + 0x4c, 0x6d, 0x75, 0x47, 0x37, 0x6a, 0x44, 0x38, 0x57, 0x53, 0x36, 0x0a, + 0x49, 0x42, 0x68, 0x34, 0x6c, 0x53, 0x6b, 0x6e, 0x56, 0x77, 0x57, 0x38, + 0x66, 0x43, 0x72, 0x30, 0x75, 0x56, 0x46, 0x56, 0x32, 0x6f, 0x63, 0x43, + 0x33, 0x67, 0x38, 0x57, 0x46, 0x7a, 0x48, 0x34, 0x71, 0x6e, 0x6b, 0x75, + 0x43, 0x52, 0x4f, 0x37, 0x72, 0x37, 0x49, 0x67, 0x47, 0x52, 0x4c, 0x6c, + 0x6b, 0x2f, 0x6c, 0x4c, 0x2b, 0x59, 0x50, 0x6f, 0x52, 0x4e, 0x57, 0x79, + 0x51, 0x53, 0x57, 0x2f, 0x0a, 0x69, 0x48, 0x56, 0x76, 0x2f, 0x78, 0x44, + 0x38, 0x53, 0x6c, 0x54, 0x51, 0x58, 0x2f, 0x44, 0x36, 0x37, 0x7a, 0x5a, + 0x7a, 0x66, 0x52, 0x73, 0x32, 0x52, 0x63, 0x59, 0x68, 0x62, 0x62, 0x51, + 0x56, 0x75, 0x45, 0x37, 0x50, 0x6e, 0x46, 0x79, 0x6c, 0x50, 0x56, 0x6f, + 0x41, 0x6a, 0x67, 0x62, 0x6a, 0x50, 0x47, 0x73, 0x79, 0x65, 0x2f, 0x4b, + 0x66, 0x38, 0x4c, 0x62, 0x39, 0x33, 0x2f, 0x41, 0x6f, 0x0a, 0x47, 0x45, + 0x6a, 0x77, 0x78, 0x72, 0x7a, 0x51, 0x76, 0x7a, 0x53, 0x41, 0x6c, 0x73, + 0x4a, 0x4b, 0x73, 0x57, 0x32, 0x4f, 0x78, 0x35, 0x42, 0x46, 0x33, 0x69, + 0x39, 0x6e, 0x72, 0x45, 0x55, 0x45, 0x6f, 0x33, 0x72, 0x63, 0x56, 0x5a, + 0x4c, 0x4a, 0x52, 0x32, 0x62, 0x59, 0x47, 0x6f, 0x7a, 0x48, 0x37, 0x5a, + 0x78, 0x4f, 0x6d, 0x75, 0x41, 0x53, 0x75, 0x37, 0x56, 0x71, 0x54, 0x49, + 0x54, 0x68, 0x0a, 0x34, 0x53, 0x49, 0x4e, 0x68, 0x77, 0x42, 0x6b, 0x2f, + 0x6f, 0x78, 0x39, 0x59, 0x6a, 0x6c, 0x6c, 0x70, 0x75, 0x39, 0x43, 0x74, + 0x6f, 0x41, 0x6c, 0x45, 0x6d, 0x45, 0x42, 0x71, 0x43, 0x51, 0x54, 0x63, + 0x41, 0x41, 0x52, 0x4a, 0x6c, 0x2f, 0x36, 0x4e, 0x56, 0x44, 0x46, 0x53, + 0x4d, 0x77, 0x47, 0x52, 0x2b, 0x67, 0x6e, 0x32, 0x48, 0x43, 0x4e, 0x58, + 0x32, 0x54, 0x6d, 0x6f, 0x55, 0x51, 0x6d, 0x0a, 0x58, 0x69, 0x4c, 0x73, + 0x6b, 0x73, 0x33, 0x2f, 0x51, 0x70, 0x70, 0x45, 0x49, 0x57, 0x31, 0x63, + 0x78, 0x65, 0x4d, 0x69, 0x48, 0x56, 0x39, 0x48, 0x45, 0x75, 0x66, 0x4f, + 0x58, 0x31, 0x33, 0x36, 0x32, 0x4b, 0x71, 0x78, 0x4d, 0x79, 0x33, 0x5a, + 0x64, 0x76, 0x4a, 0x4f, 0x4f, 0x6a, 0x4d, 0x4d, 0x4b, 0x37, 0x4d, 0x74, + 0x6b, 0x41, 0x59, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, + 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, + 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, + 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x41, 0x64, 0x64, + 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x66, + 0x69, 0x65, 0x64, 0x20, 0x43, 0x41, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, + 0x4f, 0x3d, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, + 0x42, 0x20, 0x4f, 0x55, 0x3d, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, + 0x6b, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, + 0x20, 0x43, 0x4e, 0x3d, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, + 0x20, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x43, + 0x41, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x4f, 0x3d, 0x41, 0x64, 0x64, + 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x20, 0x4f, 0x55, 0x3d, + 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x54, 0x54, 0x50, + 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x0a, 0x23, 0x20, 0x4c, + 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x41, 0x64, 0x64, 0x54, 0x72, + 0x75, 0x73, 0x74, 0x20, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x65, + 0x64, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x65, 0x73, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x22, 0x0a, 0x23, 0x20, 0x53, + 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x31, 0x0a, 0x23, 0x20, 0x4d, + 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x3a, 0x20, 0x32, 0x37, 0x3a, 0x65, 0x63, 0x3a, 0x33, 0x39, + 0x3a, 0x34, 0x37, 0x3a, 0x63, 0x64, 0x3a, 0x64, 0x61, 0x3a, 0x35, 0x61, + 0x3a, 0x61, 0x66, 0x3a, 0x65, 0x32, 0x3a, 0x39, 0x61, 0x3a, 0x30, 0x31, + 0x3a, 0x36, 0x35, 0x3a, 0x32, 0x31, 0x3a, 0x61, 0x39, 0x3a, 0x34, 0x63, + 0x3a, 0x62, 0x62, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x34, 0x64, 0x3a, 0x32, 0x33, 0x3a, 0x37, 0x38, 0x3a, 0x65, 0x63, 0x3a, + 0x39, 0x31, 0x3a, 0x39, 0x35, 0x3a, 0x33, 0x39, 0x3a, 0x62, 0x35, 0x3a, + 0x30, 0x30, 0x3a, 0x37, 0x66, 0x3a, 0x37, 0x35, 0x3a, 0x38, 0x66, 0x3a, + 0x30, 0x33, 0x3a, 0x33, 0x62, 0x3a, 0x32, 0x31, 0x3a, 0x31, 0x65, 0x3a, + 0x63, 0x35, 0x3a, 0x34, 0x64, 0x3a, 0x38, 0x62, 0x3a, 0x63, 0x66, 0x0a, + 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, + 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x38, 0x30, + 0x3a, 0x39, 0x35, 0x3a, 0x32, 0x31, 0x3a, 0x30, 0x38, 0x3a, 0x30, 0x35, + 0x3a, 0x64, 0x62, 0x3a, 0x34, 0x62, 0x3a, 0x62, 0x63, 0x3a, 0x33, 0x35, + 0x3a, 0x35, 0x65, 0x3a, 0x34, 0x34, 0x3a, 0x32, 0x38, 0x3a, 0x64, 0x38, + 0x3a, 0x66, 0x64, 0x3a, 0x36, 0x65, 0x3a, 0x63, 0x32, 0x3a, 0x63, 0x64, + 0x3a, 0x65, 0x33, 0x3a, 0x61, 0x62, 0x3a, 0x35, 0x66, 0x3a, 0x62, 0x39, + 0x3a, 0x37, 0x61, 0x3a, 0x39, 0x39, 0x3a, 0x34, 0x32, 0x3a, 0x39, 0x38, + 0x3a, 0x38, 0x65, 0x3a, 0x62, 0x38, 0x3a, 0x66, 0x34, 0x3a, 0x64, 0x63, + 0x3a, 0x64, 0x30, 0x3a, 0x36, 0x30, 0x3a, 0x31, 0x36, 0x0a, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, + 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x45, 0x48, 0x6a, 0x43, 0x43, 0x41, 0x77, + 0x61, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x42, 0x41, 0x54, + 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, + 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x42, 0x6e, 0x4d, 0x51, + 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, + 0x4a, 0x54, 0x52, 0x54, 0x45, 0x55, 0x0a, 0x4d, 0x42, 0x49, 0x47, 0x41, + 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4c, 0x51, 0x57, 0x52, 0x6b, 0x56, + 0x48, 0x4a, 0x31, 0x63, 0x33, 0x51, 0x67, 0x51, 0x55, 0x49, 0x78, 0x48, + 0x54, 0x41, 0x62, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, 0x46, + 0x45, 0x46, 0x6b, 0x5a, 0x46, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x49, + 0x46, 0x52, 0x55, 0x55, 0x43, 0x42, 0x4f, 0x5a, 0x58, 0x52, 0x33, 0x0a, + 0x62, 0x33, 0x4a, 0x72, 0x4d, 0x53, 0x4d, 0x77, 0x49, 0x51, 0x59, 0x44, + 0x56, 0x51, 0x51, 0x44, 0x45, 0x78, 0x70, 0x42, 0x5a, 0x47, 0x52, 0x55, + 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x52, 0x64, 0x57, 0x46, 0x73, + 0x61, 0x57, 0x5a, 0x70, 0x5a, 0x57, 0x51, 0x67, 0x51, 0x30, 0x45, 0x67, + 0x55, 0x6d, 0x39, 0x76, 0x64, 0x44, 0x41, 0x65, 0x46, 0x77, 0x30, 0x77, + 0x4d, 0x44, 0x41, 0x31, 0x0a, 0x4d, 0x7a, 0x41, 0x78, 0x4d, 0x44, 0x51, + 0x30, 0x4e, 0x54, 0x42, 0x61, 0x46, 0x77, 0x30, 0x79, 0x4d, 0x44, 0x41, + 0x31, 0x4d, 0x7a, 0x41, 0x78, 0x4d, 0x44, 0x51, 0x30, 0x4e, 0x54, 0x42, + 0x61, 0x4d, 0x47, 0x63, 0x78, 0x43, 0x7a, 0x41, 0x4a, 0x42, 0x67, 0x4e, + 0x56, 0x42, 0x41, 0x59, 0x54, 0x41, 0x6c, 0x4e, 0x46, 0x4d, 0x52, 0x51, + 0x77, 0x45, 0x67, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4b, 0x0a, 0x45, 0x77, + 0x74, 0x42, 0x5a, 0x47, 0x52, 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, + 0x42, 0x42, 0x51, 0x6a, 0x45, 0x64, 0x4d, 0x42, 0x73, 0x47, 0x41, 0x31, + 0x55, 0x45, 0x43, 0x78, 0x4d, 0x55, 0x51, 0x57, 0x52, 0x6b, 0x56, 0x48, + 0x4a, 0x31, 0x63, 0x33, 0x51, 0x67, 0x56, 0x46, 0x52, 0x51, 0x49, 0x45, + 0x35, 0x6c, 0x64, 0x48, 0x64, 0x76, 0x63, 0x6d, 0x73, 0x78, 0x49, 0x7a, + 0x41, 0x68, 0x0a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x4d, 0x54, 0x47, + 0x6b, 0x46, 0x6b, 0x5a, 0x46, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x49, + 0x46, 0x46, 0x31, 0x59, 0x57, 0x78, 0x70, 0x5a, 0x6d, 0x6c, 0x6c, 0x5a, + 0x43, 0x42, 0x44, 0x51, 0x53, 0x42, 0x53, 0x62, 0x32, 0x39, 0x30, 0x4d, + 0x49, 0x49, 0x42, 0x49, 0x6a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, + 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x0a, 0x41, 0x51, 0x45, 0x46, + 0x41, 0x41, 0x4f, 0x43, 0x41, 0x51, 0x38, 0x41, 0x4d, 0x49, 0x49, 0x42, + 0x43, 0x67, 0x4b, 0x43, 0x41, 0x51, 0x45, 0x41, 0x35, 0x42, 0x36, 0x61, + 0x2f, 0x74, 0x77, 0x4a, 0x57, 0x6f, 0x65, 0x6b, 0x6e, 0x30, 0x65, 0x2b, + 0x45, 0x56, 0x2b, 0x76, 0x68, 0x44, 0x54, 0x62, 0x59, 0x6a, 0x78, 0x35, + 0x65, 0x4c, 0x66, 0x70, 0x4d, 0x4c, 0x58, 0x73, 0x44, 0x42, 0x77, 0x71, + 0x0a, 0x78, 0x42, 0x62, 0x2f, 0x34, 0x4f, 0x78, 0x78, 0x36, 0x34, 0x72, + 0x31, 0x45, 0x57, 0x37, 0x74, 0x54, 0x77, 0x32, 0x52, 0x30, 0x68, 0x49, + 0x59, 0x4c, 0x55, 0x6b, 0x56, 0x41, 0x63, 0x4b, 0x6b, 0x49, 0x68, 0x50, + 0x48, 0x45, 0x57, 0x54, 0x2f, 0x49, 0x68, 0x4b, 0x61, 0x75, 0x59, 0x35, + 0x63, 0x4c, 0x77, 0x6a, 0x50, 0x63, 0x57, 0x71, 0x7a, 0x5a, 0x77, 0x46, + 0x5a, 0x38, 0x56, 0x31, 0x47, 0x0a, 0x38, 0x37, 0x42, 0x34, 0x70, 0x66, + 0x59, 0x4f, 0x51, 0x6e, 0x72, 0x6a, 0x66, 0x78, 0x76, 0x4d, 0x30, 0x50, + 0x43, 0x33, 0x4b, 0x50, 0x30, 0x71, 0x36, 0x70, 0x36, 0x7a, 0x73, 0x4c, + 0x6b, 0x45, 0x71, 0x76, 0x33, 0x32, 0x78, 0x37, 0x53, 0x78, 0x75, 0x43, + 0x71, 0x67, 0x2b, 0x31, 0x6a, 0x78, 0x47, 0x61, 0x42, 0x76, 0x63, 0x43, + 0x56, 0x2b, 0x50, 0x6d, 0x6c, 0x4b, 0x66, 0x77, 0x38, 0x69, 0x0a, 0x32, + 0x4f, 0x2b, 0x74, 0x43, 0x42, 0x47, 0x61, 0x4b, 0x5a, 0x6e, 0x68, 0x71, + 0x6b, 0x52, 0x46, 0x6d, 0x68, 0x4a, 0x65, 0x50, 0x70, 0x31, 0x74, 0x55, + 0x76, 0x7a, 0x6e, 0x6f, 0x44, 0x31, 0x6f, 0x4c, 0x2f, 0x42, 0x4c, 0x63, + 0x48, 0x77, 0x54, 0x4f, 0x4b, 0x32, 0x38, 0x46, 0x53, 0x58, 0x78, 0x31, + 0x73, 0x36, 0x72, 0x6f, 0x73, 0x41, 0x78, 0x31, 0x69, 0x2b, 0x66, 0x34, + 0x50, 0x38, 0x55, 0x0a, 0x57, 0x66, 0x79, 0x45, 0x6b, 0x39, 0x6d, 0x48, + 0x66, 0x45, 0x78, 0x55, 0x45, 0x2b, 0x75, 0x66, 0x30, 0x53, 0x30, 0x52, + 0x2b, 0x42, 0x67, 0x36, 0x4f, 0x74, 0x34, 0x6c, 0x32, 0x66, 0x66, 0x54, + 0x51, 0x4f, 0x32, 0x6b, 0x42, 0x68, 0x4c, 0x45, 0x4f, 0x2b, 0x47, 0x52, + 0x77, 0x56, 0x59, 0x31, 0x38, 0x42, 0x54, 0x63, 0x5a, 0x54, 0x59, 0x4a, + 0x62, 0x71, 0x75, 0x6b, 0x42, 0x38, 0x63, 0x31, 0x0a, 0x30, 0x63, 0x49, + 0x44, 0x4d, 0x7a, 0x5a, 0x62, 0x64, 0x53, 0x5a, 0x74, 0x51, 0x76, 0x45, + 0x53, 0x61, 0x30, 0x4e, 0x76, 0x53, 0x33, 0x47, 0x55, 0x2b, 0x6a, 0x51, + 0x64, 0x37, 0x52, 0x4e, 0x75, 0x79, 0x6f, 0x42, 0x2f, 0x6d, 0x43, 0x39, + 0x73, 0x75, 0x57, 0x58, 0x59, 0x36, 0x51, 0x49, 0x44, 0x41, 0x51, 0x41, + 0x42, 0x6f, 0x34, 0x48, 0x55, 0x4d, 0x49, 0x48, 0x52, 0x4d, 0x42, 0x30, + 0x47, 0x0a, 0x41, 0x31, 0x55, 0x64, 0x44, 0x67, 0x51, 0x57, 0x42, 0x42, + 0x51, 0x35, 0x6c, 0x59, 0x74, 0x69, 0x69, 0x31, 0x7a, 0x4a, 0x31, 0x49, + 0x43, 0x36, 0x57, 0x41, 0x2b, 0x58, 0x50, 0x78, 0x55, 0x49, 0x51, 0x38, + 0x79, 0x59, 0x70, 0x7a, 0x41, 0x4c, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, + 0x38, 0x45, 0x42, 0x41, 0x4d, 0x43, 0x41, 0x51, 0x59, 0x77, 0x44, 0x77, + 0x59, 0x44, 0x56, 0x52, 0x30, 0x54, 0x0a, 0x41, 0x51, 0x48, 0x2f, 0x42, + 0x41, 0x55, 0x77, 0x41, 0x77, 0x45, 0x42, 0x2f, 0x7a, 0x43, 0x42, 0x6b, + 0x51, 0x59, 0x44, 0x56, 0x52, 0x30, 0x6a, 0x42, 0x49, 0x47, 0x4a, 0x4d, + 0x49, 0x47, 0x47, 0x67, 0x42, 0x51, 0x35, 0x6c, 0x59, 0x74, 0x69, 0x69, + 0x31, 0x7a, 0x4a, 0x31, 0x49, 0x43, 0x36, 0x57, 0x41, 0x2b, 0x58, 0x50, + 0x78, 0x55, 0x49, 0x51, 0x38, 0x79, 0x59, 0x70, 0x36, 0x46, 0x72, 0x0a, + 0x70, 0x47, 0x6b, 0x77, 0x5a, 0x7a, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x55, 0x30, 0x55, 0x78, + 0x46, 0x44, 0x41, 0x53, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, + 0x43, 0x30, 0x46, 0x6b, 0x5a, 0x46, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, + 0x49, 0x45, 0x46, 0x43, 0x4d, 0x52, 0x30, 0x77, 0x47, 0x77, 0x59, 0x44, + 0x56, 0x51, 0x51, 0x4c, 0x0a, 0x45, 0x78, 0x52, 0x42, 0x5a, 0x47, 0x52, + 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x55, 0x56, 0x46, 0x41, + 0x67, 0x54, 0x6d, 0x56, 0x30, 0x64, 0x32, 0x39, 0x79, 0x61, 0x7a, 0x45, + 0x6a, 0x4d, 0x43, 0x45, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, + 0x61, 0x51, 0x57, 0x52, 0x6b, 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x51, + 0x67, 0x55, 0x58, 0x56, 0x68, 0x62, 0x47, 0x6c, 0x6d, 0x0a, 0x61, 0x57, + 0x56, 0x6b, 0x49, 0x45, 0x4e, 0x42, 0x49, 0x46, 0x4a, 0x76, 0x62, 0x33, + 0x53, 0x43, 0x41, 0x51, 0x45, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, + 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x46, 0x42, 0x51, + 0x41, 0x44, 0x67, 0x67, 0x45, 0x42, 0x41, 0x42, 0x6d, 0x72, 0x64, 0x65, + 0x72, 0x34, 0x69, 0x32, 0x56, 0x68, 0x6c, 0x52, 0x4f, 0x36, 0x61, 0x51, + 0x54, 0x76, 0x0a, 0x68, 0x73, 0x6f, 0x54, 0x6f, 0x4d, 0x65, 0x71, 0x54, + 0x32, 0x51, 0x62, 0x50, 0x78, 0x6a, 0x32, 0x71, 0x43, 0x30, 0x73, 0x56, + 0x59, 0x38, 0x46, 0x74, 0x7a, 0x44, 0x71, 0x51, 0x6d, 0x6f, 0x64, 0x77, + 0x43, 0x56, 0x52, 0x4c, 0x61, 0x65, 0x2f, 0x44, 0x4c, 0x50, 0x74, 0x37, + 0x77, 0x68, 0x2f, 0x62, 0x44, 0x78, 0x47, 0x47, 0x75, 0x6f, 0x59, 0x51, + 0x39, 0x39, 0x32, 0x7a, 0x50, 0x6c, 0x6d, 0x0a, 0x68, 0x70, 0x77, 0x73, + 0x61, 0x50, 0x58, 0x70, 0x46, 0x2f, 0x67, 0x78, 0x73, 0x78, 0x6a, 0x45, + 0x31, 0x6b, 0x68, 0x39, 0x49, 0x30, 0x78, 0x6f, 0x77, 0x58, 0x36, 0x37, + 0x41, 0x52, 0x52, 0x76, 0x78, 0x64, 0x6c, 0x75, 0x33, 0x72, 0x73, 0x45, + 0x51, 0x6d, 0x72, 0x34, 0x39, 0x6c, 0x78, 0x39, 0x35, 0x64, 0x72, 0x36, + 0x68, 0x2b, 0x73, 0x4e, 0x4e, 0x56, 0x4a, 0x6e, 0x30, 0x4a, 0x36, 0x58, + 0x0a, 0x64, 0x67, 0x57, 0x54, 0x50, 0x35, 0x58, 0x48, 0x41, 0x65, 0x5a, + 0x70, 0x56, 0x54, 0x68, 0x2f, 0x45, 0x47, 0x47, 0x5a, 0x79, 0x65, 0x4e, + 0x66, 0x70, 0x73, 0x6f, 0x2b, 0x67, 0x6d, 0x4e, 0x49, 0x71, 0x75, 0x49, + 0x49, 0x53, 0x44, 0x36, 0x71, 0x38, 0x72, 0x4b, 0x46, 0x59, 0x71, 0x61, + 0x30, 0x70, 0x39, 0x6d, 0x39, 0x4e, 0x35, 0x78, 0x6f, 0x74, 0x53, 0x31, + 0x57, 0x66, 0x62, 0x43, 0x33, 0x0a, 0x50, 0x36, 0x43, 0x78, 0x42, 0x39, + 0x62, 0x70, 0x54, 0x39, 0x7a, 0x65, 0x52, 0x58, 0x45, 0x77, 0x4d, 0x6e, + 0x38, 0x62, 0x4c, 0x67, 0x6e, 0x35, 0x76, 0x31, 0x4b, 0x68, 0x37, 0x73, + 0x4b, 0x41, 0x50, 0x67, 0x5a, 0x63, 0x4c, 0x6c, 0x56, 0x41, 0x77, 0x52, + 0x76, 0x31, 0x63, 0x45, 0x57, 0x77, 0x33, 0x46, 0x33, 0x36, 0x39, 0x6e, + 0x4a, 0x61, 0x64, 0x39, 0x4a, 0x6a, 0x7a, 0x63, 0x39, 0x59, 0x0a, 0x69, + 0x51, 0x42, 0x43, 0x59, 0x7a, 0x39, 0x35, 0x4f, 0x64, 0x42, 0x45, 0x73, + 0x49, 0x4a, 0x75, 0x51, 0x52, 0x6e, 0x6f, 0x33, 0x65, 0x44, 0x42, 0x69, + 0x46, 0x72, 0x52, 0x48, 0x6e, 0x47, 0x54, 0x48, 0x79, 0x51, 0x77, 0x64, + 0x4f, 0x55, 0x65, 0x71, 0x4e, 0x34, 0x38, 0x4a, 0x7a, 0x64, 0x2f, 0x67, + 0x36, 0x36, 0x65, 0x64, 0x38, 0x2f, 0x77, 0x4d, 0x4c, 0x48, 0x2f, 0x53, + 0x35, 0x6e, 0x6f, 0x0a, 0x78, 0x71, 0x45, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, + 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, + 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, + 0x3d, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f, + 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, + 0x79, 0x20, 0x4f, 0x3d, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, + 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x77, 0x77, 0x77, + 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, + 0x2f, 0x43, 0x50, 0x53, 0x20, 0x69, 0x73, 0x20, 0x69, 0x6e, 0x63, 0x6f, + 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, + 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x2f, 0x28, 0x63, + 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, + 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x53, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x45, + 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, + 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, + 0x4f, 0x3d, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, + 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x77, 0x77, 0x77, 0x2e, 0x65, + 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x43, + 0x50, 0x53, 0x20, 0x69, 0x73, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x72, 0x70, + 0x6f, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x72, 0x65, + 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x2f, 0x28, 0x63, 0x29, 0x20, + 0x32, 0x30, 0x30, 0x36, 0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, + 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, + 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, + 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, + 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x31, 0x31, 0x36, 0x34, 0x36, 0x36, 0x30, + 0x38, 0x32, 0x30, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, + 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x64, + 0x36, 0x3a, 0x61, 0x35, 0x3a, 0x63, 0x33, 0x3a, 0x65, 0x64, 0x3a, 0x35, + 0x64, 0x3a, 0x64, 0x64, 0x3a, 0x33, 0x65, 0x3a, 0x30, 0x30, 0x3a, 0x63, + 0x31, 0x3a, 0x33, 0x64, 0x3a, 0x38, 0x37, 0x3a, 0x39, 0x32, 0x3a, 0x31, + 0x66, 0x3a, 0x31, 0x64, 0x3a, 0x33, 0x66, 0x3a, 0x65, 0x34, 0x0a, 0x23, + 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x62, 0x33, 0x3a, 0x31, 0x65, + 0x3a, 0x62, 0x31, 0x3a, 0x62, 0x37, 0x3a, 0x34, 0x30, 0x3a, 0x65, 0x33, + 0x3a, 0x36, 0x63, 0x3a, 0x38, 0x34, 0x3a, 0x30, 0x32, 0x3a, 0x64, 0x61, + 0x3a, 0x64, 0x63, 0x3a, 0x33, 0x37, 0x3a, 0x64, 0x34, 0x3a, 0x34, 0x64, + 0x3a, 0x66, 0x35, 0x3a, 0x64, 0x34, 0x3a, 0x36, 0x37, 0x3a, 0x34, 0x39, + 0x3a, 0x35, 0x32, 0x3a, 0x66, 0x39, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, + 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, + 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x37, 0x33, 0x3a, 0x63, 0x31, 0x3a, 0x37, + 0x36, 0x3a, 0x34, 0x33, 0x3a, 0x34, 0x66, 0x3a, 0x31, 0x62, 0x3a, 0x63, + 0x36, 0x3a, 0x64, 0x35, 0x3a, 0x61, 0x64, 0x3a, 0x66, 0x34, 0x3a, 0x35, + 0x62, 0x3a, 0x30, 0x65, 0x3a, 0x37, 0x36, 0x3a, 0x65, 0x37, 0x3a, 0x32, + 0x37, 0x3a, 0x32, 0x38, 0x3a, 0x37, 0x63, 0x3a, 0x38, 0x64, 0x3a, 0x65, + 0x35, 0x3a, 0x37, 0x36, 0x3a, 0x31, 0x36, 0x3a, 0x63, 0x31, 0x3a, 0x65, + 0x36, 0x3a, 0x65, 0x36, 0x3a, 0x31, 0x34, 0x3a, 0x31, 0x61, 0x3a, 0x32, + 0x62, 0x3a, 0x32, 0x63, 0x3a, 0x62, 0x63, 0x3a, 0x37, 0x64, 0x3a, 0x38, + 0x65, 0x3a, 0x34, 0x63, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, + 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, + 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, + 0x45, 0x6b, 0x54, 0x43, 0x43, 0x41, 0x33, 0x6d, 0x67, 0x41, 0x77, 0x49, + 0x42, 0x41, 0x67, 0x49, 0x45, 0x52, 0x57, 0x74, 0x51, 0x56, 0x44, 0x41, + 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, + 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x43, 0x42, 0x73, 0x44, 0x45, + 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, + 0x43, 0x0a, 0x56, 0x56, 0x4d, 0x78, 0x46, 0x6a, 0x41, 0x55, 0x42, 0x67, + 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, 0x44, 0x55, 0x56, 0x75, 0x64, 0x48, + 0x4a, 0x31, 0x63, 0x33, 0x51, 0x73, 0x49, 0x45, 0x6c, 0x75, 0x59, 0x79, + 0x34, 0x78, 0x4f, 0x54, 0x41, 0x33, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, + 0x73, 0x54, 0x4d, 0x48, 0x64, 0x33, 0x64, 0x79, 0x35, 0x6c, 0x62, 0x6e, + 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x0a, 0x4c, 0x6d, 0x35, 0x6c, 0x64, + 0x43, 0x39, 0x44, 0x55, 0x46, 0x4d, 0x67, 0x61, 0x58, 0x4d, 0x67, 0x61, + 0x57, 0x35, 0x6a, 0x62, 0x33, 0x4a, 0x77, 0x62, 0x33, 0x4a, 0x68, 0x64, + 0x47, 0x56, 0x6b, 0x49, 0x47, 0x4a, 0x35, 0x49, 0x48, 0x4a, 0x6c, 0x5a, + 0x6d, 0x56, 0x79, 0x5a, 0x57, 0x35, 0x6a, 0x5a, 0x54, 0x45, 0x66, 0x4d, + 0x42, 0x30, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x78, 0x4d, 0x57, 0x0a, + 0x4b, 0x47, 0x4d, 0x70, 0x49, 0x44, 0x49, 0x77, 0x4d, 0x44, 0x59, 0x67, + 0x52, 0x57, 0x35, 0x30, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x77, 0x67, + 0x53, 0x57, 0x35, 0x6a, 0x4c, 0x6a, 0x45, 0x74, 0x4d, 0x43, 0x73, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x6b, 0x52, 0x57, 0x35, 0x30, + 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x53, 0x62, 0x32, 0x39, 0x30, + 0x49, 0x45, 0x4e, 0x6c, 0x0a, 0x63, 0x6e, 0x52, 0x70, 0x5a, 0x6d, 0x6c, + 0x6a, 0x59, 0x58, 0x52, 0x70, 0x62, 0x32, 0x34, 0x67, 0x51, 0x58, 0x56, + 0x30, 0x61, 0x47, 0x39, 0x79, 0x61, 0x58, 0x52, 0x35, 0x4d, 0x42, 0x34, + 0x58, 0x44, 0x54, 0x41, 0x32, 0x4d, 0x54, 0x45, 0x79, 0x4e, 0x7a, 0x49, + 0x77, 0x4d, 0x6a, 0x4d, 0x30, 0x4d, 0x6c, 0x6f, 0x58, 0x44, 0x54, 0x49, + 0x32, 0x4d, 0x54, 0x45, 0x79, 0x4e, 0x7a, 0x49, 0x77, 0x0a, 0x4e, 0x54, + 0x4d, 0x30, 0x4d, 0x6c, 0x6f, 0x77, 0x67, 0x62, 0x41, 0x78, 0x43, 0x7a, + 0x41, 0x4a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x59, 0x54, 0x41, 0x6c, + 0x56, 0x54, 0x4d, 0x52, 0x59, 0x77, 0x46, 0x41, 0x59, 0x44, 0x56, 0x51, + 0x51, 0x4b, 0x45, 0x77, 0x31, 0x46, 0x62, 0x6e, 0x52, 0x79, 0x64, 0x58, + 0x4e, 0x30, 0x4c, 0x43, 0x42, 0x4a, 0x62, 0x6d, 0x4d, 0x75, 0x4d, 0x54, + 0x6b, 0x77, 0x0a, 0x4e, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, + 0x7a, 0x42, 0x33, 0x64, 0x33, 0x63, 0x75, 0x5a, 0x57, 0x35, 0x30, 0x63, + 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x35, 0x75, 0x5a, 0x58, 0x51, 0x76, 0x51, + 0x31, 0x42, 0x54, 0x49, 0x47, 0x6c, 0x7a, 0x49, 0x47, 0x6c, 0x75, 0x59, + 0x32, 0x39, 0x79, 0x63, 0x47, 0x39, 0x79, 0x59, 0x58, 0x52, 0x6c, 0x5a, + 0x43, 0x42, 0x69, 0x65, 0x53, 0x42, 0x79, 0x0a, 0x5a, 0x57, 0x5a, 0x6c, + 0x63, 0x6d, 0x56, 0x75, 0x59, 0x32, 0x55, 0x78, 0x48, 0x7a, 0x41, 0x64, + 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, 0x46, 0x69, 0x68, 0x6a, + 0x4b, 0x53, 0x41, 0x79, 0x4d, 0x44, 0x41, 0x32, 0x49, 0x45, 0x56, 0x75, + 0x64, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x51, 0x73, 0x49, 0x45, 0x6c, 0x75, + 0x59, 0x79, 0x34, 0x78, 0x4c, 0x54, 0x41, 0x72, 0x42, 0x67, 0x4e, 0x56, + 0x0a, 0x42, 0x41, 0x4d, 0x54, 0x4a, 0x45, 0x56, 0x75, 0x64, 0x48, 0x4a, + 0x31, 0x63, 0x33, 0x51, 0x67, 0x55, 0x6d, 0x39, 0x76, 0x64, 0x43, 0x42, + 0x44, 0x5a, 0x58, 0x4a, 0x30, 0x61, 0x57, 0x5a, 0x70, 0x59, 0x32, 0x46, + 0x30, 0x61, 0x57, 0x39, 0x75, 0x49, 0x45, 0x46, 0x31, 0x64, 0x47, 0x68, + 0x76, 0x63, 0x6d, 0x6c, 0x30, 0x65, 0x54, 0x43, 0x43, 0x41, 0x53, 0x49, + 0x77, 0x44, 0x51, 0x59, 0x4a, 0x0a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, + 0x63, 0x4e, 0x41, 0x51, 0x45, 0x42, 0x42, 0x51, 0x41, 0x44, 0x67, 0x67, + 0x45, 0x50, 0x41, 0x44, 0x43, 0x43, 0x41, 0x51, 0x6f, 0x43, 0x67, 0x67, + 0x45, 0x42, 0x41, 0x4c, 0x61, 0x56, 0x74, 0x6b, 0x4e, 0x43, 0x2b, 0x73, + 0x5a, 0x74, 0x4b, 0x6d, 0x39, 0x49, 0x33, 0x35, 0x52, 0x4d, 0x4f, 0x56, + 0x63, 0x46, 0x37, 0x73, 0x4e, 0x35, 0x45, 0x55, 0x46, 0x6f, 0x0a, 0x4e, + 0x75, 0x33, 0x73, 0x2f, 0x70, 0x6f, 0x42, 0x6a, 0x36, 0x45, 0x34, 0x4b, + 0x50, 0x7a, 0x33, 0x45, 0x45, 0x5a, 0x6d, 0x4c, 0x6b, 0x30, 0x65, 0x47, + 0x72, 0x45, 0x61, 0x54, 0x73, 0x62, 0x52, 0x77, 0x4a, 0x57, 0x49, 0x73, + 0x4d, 0x6e, 0x2f, 0x4d, 0x59, 0x73, 0x7a, 0x41, 0x39, 0x75, 0x33, 0x67, + 0x33, 0x73, 0x2b, 0x49, 0x49, 0x52, 0x65, 0x37, 0x62, 0x4a, 0x57, 0x4b, + 0x4b, 0x66, 0x34, 0x0a, 0x34, 0x4c, 0x6c, 0x41, 0x63, 0x54, 0x66, 0x46, + 0x79, 0x30, 0x63, 0x4f, 0x6c, 0x79, 0x70, 0x6f, 0x77, 0x43, 0x4b, 0x56, + 0x59, 0x68, 0x58, 0x62, 0x52, 0x39, 0x6e, 0x31, 0x30, 0x43, 0x76, 0x2f, + 0x67, 0x6b, 0x76, 0x4a, 0x72, 0x54, 0x37, 0x65, 0x54, 0x4e, 0x75, 0x51, + 0x67, 0x46, 0x41, 0x2f, 0x43, 0x59, 0x71, 0x45, 0x41, 0x4f, 0x77, 0x77, + 0x43, 0x6a, 0x30, 0x59, 0x7a, 0x66, 0x76, 0x39, 0x0a, 0x4b, 0x6c, 0x6d, + 0x61, 0x49, 0x35, 0x55, 0x58, 0x4c, 0x45, 0x57, 0x65, 0x48, 0x32, 0x35, + 0x44, 0x65, 0x57, 0x30, 0x4d, 0x58, 0x4a, 0x6a, 0x2b, 0x53, 0x4b, 0x66, + 0x46, 0x49, 0x30, 0x64, 0x63, 0x58, 0x76, 0x31, 0x75, 0x35, 0x78, 0x36, + 0x30, 0x39, 0x6d, 0x68, 0x46, 0x30, 0x59, 0x61, 0x44, 0x57, 0x36, 0x4b, + 0x4b, 0x6a, 0x62, 0x48, 0x6a, 0x4b, 0x59, 0x44, 0x2b, 0x4a, 0x58, 0x47, + 0x49, 0x0a, 0x72, 0x62, 0x36, 0x38, 0x6a, 0x36, 0x78, 0x53, 0x6c, 0x6b, + 0x75, 0x71, 0x55, 0x59, 0x33, 0x6b, 0x45, 0x7a, 0x45, 0x5a, 0x36, 0x45, + 0x35, 0x4e, 0x6e, 0x39, 0x75, 0x73, 0x73, 0x32, 0x72, 0x56, 0x76, 0x44, + 0x6c, 0x55, 0x63, 0x63, 0x70, 0x36, 0x65, 0x6e, 0x2b, 0x51, 0x33, 0x58, + 0x30, 0x64, 0x67, 0x4e, 0x6d, 0x42, 0x75, 0x31, 0x6b, 0x6d, 0x77, 0x68, + 0x48, 0x2b, 0x35, 0x70, 0x50, 0x69, 0x0a, 0x39, 0x34, 0x44, 0x6b, 0x5a, + 0x66, 0x73, 0x30, 0x4e, 0x77, 0x34, 0x70, 0x67, 0x48, 0x42, 0x4e, 0x72, + 0x7a, 0x69, 0x47, 0x4c, 0x70, 0x35, 0x2f, 0x56, 0x36, 0x2b, 0x65, 0x46, + 0x36, 0x37, 0x72, 0x48, 0x4d, 0x73, 0x6f, 0x49, 0x56, 0x2b, 0x32, 0x48, + 0x4e, 0x6a, 0x6e, 0x6f, 0x67, 0x51, 0x69, 0x2b, 0x64, 0x50, 0x61, 0x32, + 0x4d, 0x73, 0x43, 0x41, 0x77, 0x45, 0x41, 0x41, 0x61, 0x4f, 0x42, 0x0a, + 0x73, 0x44, 0x43, 0x42, 0x72, 0x54, 0x41, 0x4f, 0x42, 0x67, 0x4e, 0x56, + 0x48, 0x51, 0x38, 0x42, 0x41, 0x66, 0x38, 0x45, 0x42, 0x41, 0x4d, 0x43, + 0x41, 0x51, 0x59, 0x77, 0x44, 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, 0x54, + 0x41, 0x51, 0x48, 0x2f, 0x42, 0x41, 0x55, 0x77, 0x41, 0x77, 0x45, 0x42, + 0x2f, 0x7a, 0x41, 0x72, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x52, 0x41, 0x45, + 0x4a, 0x44, 0x41, 0x69, 0x0a, 0x67, 0x41, 0x38, 0x79, 0x4d, 0x44, 0x41, + 0x32, 0x4d, 0x54, 0x45, 0x79, 0x4e, 0x7a, 0x49, 0x77, 0x4d, 0x6a, 0x4d, + 0x30, 0x4d, 0x6c, 0x71, 0x42, 0x44, 0x7a, 0x49, 0x77, 0x4d, 0x6a, 0x59, + 0x78, 0x4d, 0x54, 0x49, 0x33, 0x4d, 0x6a, 0x41, 0x31, 0x4d, 0x7a, 0x51, + 0x79, 0x57, 0x6a, 0x41, 0x66, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x53, 0x4d, + 0x45, 0x47, 0x44, 0x41, 0x57, 0x67, 0x42, 0x52, 0x6f, 0x0a, 0x6b, 0x4f, + 0x52, 0x6e, 0x70, 0x4b, 0x5a, 0x54, 0x67, 0x4d, 0x65, 0x47, 0x5a, 0x71, + 0x54, 0x78, 0x39, 0x30, 0x74, 0x44, 0x2b, 0x34, 0x53, 0x39, 0x62, 0x54, + 0x41, 0x64, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x34, 0x45, 0x46, 0x67, + 0x51, 0x55, 0x61, 0x4a, 0x44, 0x6b, 0x5a, 0x36, 0x53, 0x6d, 0x55, 0x34, + 0x44, 0x48, 0x68, 0x6d, 0x61, 0x6b, 0x38, 0x66, 0x64, 0x4c, 0x51, 0x2f, + 0x75, 0x45, 0x0a, 0x76, 0x57, 0x30, 0x77, 0x48, 0x51, 0x59, 0x4a, 0x4b, + 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x5a, 0x39, 0x42, 0x30, 0x45, 0x41, 0x42, + 0x42, 0x41, 0x77, 0x44, 0x68, 0x73, 0x49, 0x56, 0x6a, 0x63, 0x75, 0x4d, + 0x54, 0x6f, 0x30, 0x4c, 0x6a, 0x41, 0x44, 0x41, 0x67, 0x53, 0x51, 0x4d, + 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, + 0x51, 0x45, 0x42, 0x42, 0x51, 0x55, 0x41, 0x0a, 0x41, 0x34, 0x49, 0x42, + 0x41, 0x51, 0x43, 0x54, 0x31, 0x44, 0x43, 0x77, 0x31, 0x77, 0x4d, 0x67, + 0x4b, 0x74, 0x44, 0x35, 0x59, 0x2b, 0x69, 0x52, 0x44, 0x41, 0x55, 0x67, + 0x71, 0x56, 0x38, 0x5a, 0x79, 0x6e, 0x74, 0x79, 0x54, 0x74, 0x53, 0x78, + 0x32, 0x39, 0x43, 0x57, 0x2b, 0x31, 0x52, 0x61, 0x47, 0x53, 0x77, 0x4d, + 0x43, 0x50, 0x65, 0x79, 0x76, 0x49, 0x57, 0x6f, 0x6e, 0x58, 0x39, 0x74, + 0x0a, 0x4f, 0x31, 0x4b, 0x7a, 0x4b, 0x74, 0x76, 0x6e, 0x31, 0x49, 0x53, + 0x4d, 0x59, 0x2f, 0x59, 0x50, 0x79, 0x79, 0x59, 0x42, 0x6b, 0x56, 0x42, + 0x73, 0x39, 0x46, 0x38, 0x55, 0x34, 0x70, 0x4e, 0x30, 0x77, 0x42, 0x4f, + 0x65, 0x4d, 0x44, 0x70, 0x51, 0x34, 0x37, 0x52, 0x67, 0x78, 0x52, 0x7a, + 0x77, 0x49, 0x6b, 0x53, 0x4e, 0x63, 0x55, 0x65, 0x73, 0x79, 0x42, 0x72, + 0x4a, 0x36, 0x5a, 0x75, 0x61, 0x0a, 0x41, 0x47, 0x41, 0x54, 0x2f, 0x33, + 0x42, 0x2b, 0x58, 0x78, 0x46, 0x4e, 0x53, 0x52, 0x75, 0x7a, 0x46, 0x56, + 0x4a, 0x37, 0x79, 0x56, 0x54, 0x61, 0x76, 0x35, 0x32, 0x56, 0x72, 0x32, + 0x75, 0x61, 0x32, 0x4a, 0x37, 0x70, 0x38, 0x65, 0x52, 0x44, 0x6a, 0x65, + 0x49, 0x52, 0x52, 0x44, 0x71, 0x2f, 0x72, 0x37, 0x32, 0x44, 0x51, 0x6e, + 0x4e, 0x53, 0x69, 0x36, 0x71, 0x37, 0x70, 0x79, 0x6e, 0x50, 0x0a, 0x39, + 0x57, 0x51, 0x63, 0x43, 0x6b, 0x33, 0x52, 0x76, 0x4b, 0x71, 0x73, 0x6e, + 0x79, 0x72, 0x51, 0x2f, 0x33, 0x39, 0x2f, 0x32, 0x6e, 0x33, 0x71, 0x73, + 0x65, 0x30, 0x77, 0x4a, 0x63, 0x47, 0x45, 0x32, 0x6a, 0x54, 0x53, 0x57, + 0x33, 0x69, 0x44, 0x56, 0x75, 0x79, 0x63, 0x4e, 0x73, 0x4d, 0x6d, 0x34, + 0x68, 0x48, 0x32, 0x5a, 0x30, 0x6b, 0x64, 0x6b, 0x71, 0x75, 0x4d, 0x2b, + 0x2b, 0x76, 0x2f, 0x0a, 0x65, 0x75, 0x36, 0x46, 0x53, 0x71, 0x64, 0x51, + 0x67, 0x50, 0x43, 0x6e, 0x58, 0x45, 0x71, 0x55, 0x4c, 0x6c, 0x38, 0x46, + 0x6d, 0x54, 0x78, 0x53, 0x51, 0x65, 0x44, 0x4e, 0x74, 0x47, 0x50, 0x50, + 0x41, 0x55, 0x4f, 0x36, 0x6e, 0x49, 0x50, 0x63, 0x6a, 0x32, 0x41, 0x37, + 0x38, 0x31, 0x71, 0x30, 0x74, 0x48, 0x75, 0x75, 0x32, 0x67, 0x75, 0x51, + 0x4f, 0x48, 0x58, 0x76, 0x67, 0x52, 0x31, 0x6d, 0x0a, 0x30, 0x76, 0x64, + 0x58, 0x63, 0x44, 0x61, 0x7a, 0x76, 0x2f, 0x77, 0x6f, 0x72, 0x33, 0x45, + 0x6c, 0x68, 0x56, 0x73, 0x54, 0x2f, 0x68, 0x35, 0x2f, 0x57, 0x72, 0x51, + 0x38, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, + 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, + 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, + 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x43, 0x41, + 0x20, 0x4f, 0x3d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, + 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x47, 0x65, 0x6f, 0x54, 0x72, + 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x43, + 0x41, 0x20, 0x4f, 0x3d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, + 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, + 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x22, 0x0a, + 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x31, 0x34, + 0x34, 0x34, 0x37, 0x30, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x66, 0x37, 0x3a, 0x37, 0x35, 0x3a, 0x61, 0x62, 0x3a, 0x32, 0x39, 0x3a, + 0x66, 0x62, 0x3a, 0x35, 0x31, 0x3a, 0x34, 0x65, 0x3a, 0x62, 0x37, 0x3a, + 0x37, 0x37, 0x3a, 0x35, 0x65, 0x3a, 0x66, 0x66, 0x3a, 0x30, 0x35, 0x3a, + 0x33, 0x63, 0x3a, 0x39, 0x39, 0x3a, 0x38, 0x65, 0x3a, 0x66, 0x35, 0x0a, + 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x64, 0x65, 0x3a, 0x32, + 0x38, 0x3a, 0x66, 0x34, 0x3a, 0x61, 0x34, 0x3a, 0x66, 0x66, 0x3a, 0x65, + 0x35, 0x3a, 0x62, 0x39, 0x3a, 0x32, 0x66, 0x3a, 0x61, 0x33, 0x3a, 0x63, + 0x35, 0x3a, 0x30, 0x33, 0x3a, 0x64, 0x31, 0x3a, 0x61, 0x33, 0x3a, 0x34, + 0x39, 0x3a, 0x61, 0x37, 0x3a, 0x66, 0x39, 0x3a, 0x39, 0x36, 0x3a, 0x32, + 0x61, 0x3a, 0x38, 0x32, 0x3a, 0x31, 0x32, 0x0a, 0x23, 0x20, 0x53, 0x48, + 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, + 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x66, 0x66, 0x3a, 0x38, 0x35, 0x3a, + 0x36, 0x61, 0x3a, 0x32, 0x64, 0x3a, 0x32, 0x35, 0x3a, 0x31, 0x64, 0x3a, + 0x63, 0x64, 0x3a, 0x38, 0x38, 0x3a, 0x64, 0x33, 0x3a, 0x36, 0x36, 0x3a, + 0x35, 0x36, 0x3a, 0x66, 0x34, 0x3a, 0x35, 0x30, 0x3a, 0x31, 0x32, 0x3a, + 0x36, 0x37, 0x3a, 0x39, 0x38, 0x3a, 0x63, 0x66, 0x3a, 0x61, 0x62, 0x3a, + 0x61, 0x61, 0x3a, 0x64, 0x65, 0x3a, 0x34, 0x30, 0x3a, 0x37, 0x39, 0x3a, + 0x39, 0x63, 0x3a, 0x37, 0x32, 0x3a, 0x32, 0x64, 0x3a, 0x65, 0x34, 0x3a, + 0x64, 0x32, 0x3a, 0x62, 0x35, 0x3a, 0x64, 0x62, 0x3a, 0x33, 0x36, 0x3a, + 0x61, 0x37, 0x3a, 0x33, 0x61, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, + 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, + 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, + 0x49, 0x44, 0x56, 0x44, 0x43, 0x43, 0x41, 0x6a, 0x79, 0x67, 0x41, 0x77, + 0x49, 0x42, 0x41, 0x67, 0x49, 0x44, 0x41, 0x6a, 0x52, 0x57, 0x4d, 0x41, + 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, + 0x45, 0x42, 0x42, 0x51, 0x55, 0x41, 0x4d, 0x45, 0x49, 0x78, 0x43, 0x7a, + 0x41, 0x4a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x59, 0x54, 0x41, 0x6c, + 0x56, 0x54, 0x0a, 0x4d, 0x52, 0x59, 0x77, 0x46, 0x41, 0x59, 0x44, 0x56, + 0x51, 0x51, 0x4b, 0x45, 0x77, 0x31, 0x48, 0x5a, 0x57, 0x39, 0x55, 0x63, + 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x4a, 0x62, 0x6d, 0x4d, 0x75, 0x4d, + 0x52, 0x73, 0x77, 0x47, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, + 0x78, 0x4a, 0x48, 0x5a, 0x57, 0x39, 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x64, + 0x43, 0x42, 0x48, 0x62, 0x47, 0x39, 0x69, 0x0a, 0x59, 0x57, 0x77, 0x67, + 0x51, 0x30, 0x45, 0x77, 0x48, 0x68, 0x63, 0x4e, 0x4d, 0x44, 0x49, 0x77, + 0x4e, 0x54, 0x49, 0x78, 0x4d, 0x44, 0x51, 0x77, 0x4d, 0x44, 0x41, 0x77, + 0x57, 0x68, 0x63, 0x4e, 0x4d, 0x6a, 0x49, 0x77, 0x4e, 0x54, 0x49, 0x78, + 0x4d, 0x44, 0x51, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x57, 0x6a, 0x42, 0x43, + 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, + 0x0a, 0x45, 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, 0x57, 0x4d, 0x42, 0x51, + 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4e, 0x52, 0x32, 0x56, + 0x76, 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x51, 0x67, 0x53, 0x57, 0x35, + 0x6a, 0x4c, 0x6a, 0x45, 0x62, 0x4d, 0x42, 0x6b, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x41, 0x78, 0x4d, 0x53, 0x52, 0x32, 0x56, 0x76, 0x56, 0x48, 0x4a, + 0x31, 0x63, 0x33, 0x51, 0x67, 0x0a, 0x52, 0x32, 0x78, 0x76, 0x59, 0x6d, + 0x46, 0x73, 0x49, 0x45, 0x4e, 0x42, 0x4d, 0x49, 0x49, 0x42, 0x49, 0x6a, + 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, + 0x30, 0x42, 0x41, 0x51, 0x45, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x51, + 0x38, 0x41, 0x4d, 0x49, 0x49, 0x42, 0x43, 0x67, 0x4b, 0x43, 0x41, 0x51, + 0x45, 0x41, 0x32, 0x73, 0x77, 0x59, 0x59, 0x7a, 0x44, 0x39, 0x0a, 0x39, + 0x42, 0x63, 0x6a, 0x47, 0x6c, 0x5a, 0x2b, 0x57, 0x39, 0x38, 0x38, 0x62, + 0x44, 0x6a, 0x6b, 0x63, 0x62, 0x64, 0x34, 0x6b, 0x64, 0x53, 0x38, 0x6f, + 0x64, 0x68, 0x4d, 0x2b, 0x4b, 0x68, 0x44, 0x74, 0x67, 0x50, 0x70, 0x54, + 0x53, 0x45, 0x48, 0x43, 0x49, 0x6a, 0x61, 0x57, 0x43, 0x39, 0x6d, 0x4f, + 0x53, 0x6d, 0x39, 0x42, 0x58, 0x69, 0x4c, 0x6e, 0x54, 0x6a, 0x6f, 0x42, + 0x62, 0x64, 0x71, 0x0a, 0x66, 0x6e, 0x47, 0x6b, 0x35, 0x73, 0x52, 0x67, + 0x70, 0x72, 0x44, 0x76, 0x67, 0x4f, 0x53, 0x4a, 0x4b, 0x41, 0x2b, 0x65, + 0x4a, 0x64, 0x62, 0x74, 0x67, 0x2f, 0x4f, 0x74, 0x70, 0x70, 0x48, 0x48, + 0x6d, 0x4d, 0x6c, 0x43, 0x47, 0x44, 0x55, 0x55, 0x6e, 0x61, 0x32, 0x59, + 0x52, 0x70, 0x49, 0x75, 0x54, 0x38, 0x72, 0x78, 0x68, 0x30, 0x50, 0x42, + 0x46, 0x70, 0x56, 0x58, 0x4c, 0x56, 0x44, 0x76, 0x0a, 0x69, 0x53, 0x32, + 0x41, 0x65, 0x6c, 0x65, 0x74, 0x38, 0x75, 0x35, 0x66, 0x61, 0x39, 0x49, + 0x41, 0x6a, 0x62, 0x6b, 0x55, 0x2b, 0x42, 0x51, 0x56, 0x4e, 0x64, 0x6e, + 0x41, 0x52, 0x71, 0x4e, 0x37, 0x63, 0x73, 0x69, 0x52, 0x76, 0x38, 0x6c, + 0x56, 0x4b, 0x38, 0x33, 0x51, 0x6c, 0x7a, 0x36, 0x63, 0x4a, 0x6d, 0x54, + 0x4d, 0x33, 0x38, 0x36, 0x44, 0x47, 0x58, 0x48, 0x4b, 0x54, 0x75, 0x62, + 0x55, 0x0a, 0x31, 0x58, 0x75, 0x70, 0x47, 0x63, 0x31, 0x56, 0x33, 0x73, + 0x6a, 0x73, 0x30, 0x6c, 0x34, 0x34, 0x55, 0x2b, 0x56, 0x63, 0x54, 0x34, + 0x77, 0x74, 0x2f, 0x6c, 0x41, 0x6a, 0x4e, 0x76, 0x78, 0x6d, 0x35, 0x73, + 0x75, 0x4f, 0x70, 0x44, 0x6b, 0x5a, 0x41, 0x4c, 0x65, 0x56, 0x41, 0x6a, + 0x6d, 0x52, 0x43, 0x77, 0x37, 0x2b, 0x4f, 0x43, 0x37, 0x52, 0x48, 0x51, + 0x57, 0x61, 0x39, 0x6b, 0x30, 0x2b, 0x0a, 0x62, 0x77, 0x38, 0x48, 0x48, + 0x61, 0x38, 0x73, 0x48, 0x6f, 0x39, 0x67, 0x4f, 0x65, 0x4c, 0x36, 0x4e, + 0x6c, 0x4d, 0x54, 0x4f, 0x64, 0x52, 0x65, 0x4a, 0x69, 0x76, 0x62, 0x50, + 0x61, 0x67, 0x55, 0x76, 0x54, 0x4c, 0x72, 0x47, 0x41, 0x4d, 0x6f, 0x55, + 0x67, 0x52, 0x78, 0x35, 0x61, 0x73, 0x7a, 0x50, 0x65, 0x45, 0x34, 0x75, + 0x77, 0x63, 0x32, 0x68, 0x47, 0x4b, 0x63, 0x65, 0x65, 0x6f, 0x57, 0x0a, + 0x4d, 0x50, 0x52, 0x66, 0x77, 0x43, 0x76, 0x6f, 0x63, 0x57, 0x76, 0x6b, + 0x2b, 0x51, 0x49, 0x44, 0x41, 0x51, 0x41, 0x42, 0x6f, 0x31, 0x4d, 0x77, + 0x55, 0x54, 0x41, 0x50, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x52, 0x4d, 0x42, + 0x41, 0x66, 0x38, 0x45, 0x42, 0x54, 0x41, 0x44, 0x41, 0x51, 0x48, 0x2f, + 0x4d, 0x42, 0x30, 0x47, 0x41, 0x31, 0x55, 0x64, 0x44, 0x67, 0x51, 0x57, + 0x42, 0x42, 0x54, 0x41, 0x0a, 0x65, 0x70, 0x68, 0x6f, 0x6a, 0x59, 0x6e, + 0x37, 0x71, 0x77, 0x56, 0x6b, 0x44, 0x42, 0x46, 0x39, 0x71, 0x6e, 0x31, + 0x6c, 0x75, 0x4d, 0x72, 0x4d, 0x54, 0x6a, 0x41, 0x66, 0x42, 0x67, 0x4e, + 0x56, 0x48, 0x53, 0x4d, 0x45, 0x47, 0x44, 0x41, 0x57, 0x67, 0x42, 0x54, + 0x41, 0x65, 0x70, 0x68, 0x6f, 0x6a, 0x59, 0x6e, 0x37, 0x71, 0x77, 0x56, + 0x6b, 0x44, 0x42, 0x46, 0x39, 0x71, 0x6e, 0x31, 0x6c, 0x0a, 0x75, 0x4d, + 0x72, 0x4d, 0x54, 0x6a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, + 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x41, + 0x4f, 0x43, 0x41, 0x51, 0x45, 0x41, 0x4e, 0x65, 0x4d, 0x70, 0x61, 0x75, + 0x55, 0x76, 0x58, 0x56, 0x53, 0x4f, 0x4b, 0x56, 0x43, 0x55, 0x6e, 0x35, + 0x6b, 0x61, 0x46, 0x4f, 0x53, 0x50, 0x65, 0x43, 0x70, 0x69, 0x6c, 0x4b, + 0x49, 0x6e, 0x0a, 0x5a, 0x35, 0x37, 0x51, 0x7a, 0x78, 0x70, 0x65, 0x52, + 0x2b, 0x6e, 0x42, 0x73, 0x71, 0x54, 0x50, 0x33, 0x55, 0x45, 0x61, 0x42, + 0x55, 0x36, 0x62, 0x53, 0x2b, 0x35, 0x4b, 0x62, 0x31, 0x56, 0x53, 0x73, + 0x79, 0x53, 0x68, 0x4e, 0x77, 0x72, 0x72, 0x5a, 0x48, 0x59, 0x71, 0x4c, + 0x69, 0x7a, 0x7a, 0x2f, 0x54, 0x74, 0x31, 0x6b, 0x4c, 0x2f, 0x36, 0x63, + 0x64, 0x6a, 0x48, 0x50, 0x54, 0x66, 0x53, 0x0a, 0x74, 0x51, 0x57, 0x56, + 0x59, 0x72, 0x6d, 0x6d, 0x33, 0x6f, 0x6b, 0x39, 0x4e, 0x6e, 0x73, 0x34, + 0x64, 0x30, 0x69, 0x58, 0x72, 0x4b, 0x59, 0x67, 0x6a, 0x79, 0x36, 0x6d, + 0x79, 0x51, 0x7a, 0x43, 0x73, 0x70, 0x6c, 0x46, 0x41, 0x4d, 0x66, 0x4f, + 0x45, 0x56, 0x45, 0x69, 0x49, 0x75, 0x43, 0x6c, 0x36, 0x72, 0x59, 0x56, + 0x53, 0x41, 0x6c, 0x6b, 0x36, 0x6c, 0x35, 0x50, 0x64, 0x50, 0x63, 0x46, + 0x0a, 0x50, 0x73, 0x65, 0x4b, 0x55, 0x67, 0x7a, 0x62, 0x46, 0x62, 0x53, + 0x39, 0x62, 0x5a, 0x76, 0x6c, 0x78, 0x72, 0x46, 0x55, 0x61, 0x4b, 0x6e, + 0x6a, 0x61, 0x5a, 0x43, 0x32, 0x6d, 0x71, 0x55, 0x50, 0x75, 0x4c, 0x6b, + 0x2f, 0x49, 0x48, 0x32, 0x75, 0x53, 0x72, 0x57, 0x34, 0x6e, 0x4f, 0x51, + 0x64, 0x74, 0x71, 0x76, 0x6d, 0x6c, 0x4b, 0x58, 0x42, 0x78, 0x34, 0x4f, + 0x74, 0x32, 0x2f, 0x55, 0x6e, 0x0a, 0x68, 0x77, 0x34, 0x45, 0x62, 0x4e, + 0x58, 0x2f, 0x33, 0x61, 0x42, 0x64, 0x37, 0x59, 0x64, 0x53, 0x74, 0x79, + 0x73, 0x56, 0x41, 0x71, 0x34, 0x35, 0x70, 0x6d, 0x70, 0x30, 0x36, 0x64, + 0x72, 0x45, 0x35, 0x37, 0x78, 0x4e, 0x4e, 0x42, 0x36, 0x70, 0x58, 0x45, + 0x30, 0x7a, 0x58, 0x35, 0x49, 0x4a, 0x4c, 0x34, 0x68, 0x6d, 0x58, 0x58, + 0x65, 0x58, 0x78, 0x78, 0x31, 0x32, 0x45, 0x36, 0x6e, 0x56, 0x0a, 0x35, + 0x66, 0x45, 0x57, 0x43, 0x52, 0x45, 0x31, 0x31, 0x61, 0x7a, 0x62, 0x4a, + 0x48, 0x46, 0x77, 0x4c, 0x4a, 0x68, 0x57, 0x43, 0x39, 0x6b, 0x58, 0x74, + 0x4e, 0x48, 0x6a, 0x55, 0x53, 0x74, 0x65, 0x64, 0x65, 0x6a, 0x56, 0x30, + 0x4e, 0x78, 0x50, 0x4e, 0x4f, 0x33, 0x43, 0x42, 0x57, 0x61, 0x41, 0x6f, + 0x63, 0x76, 0x6d, 0x4d, 0x77, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, + 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, + 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, + 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, + 0x62, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x32, 0x20, 0x4f, 0x3d, 0x47, + 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, + 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, + 0x43, 0x4e, 0x3d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, + 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x32, 0x20, + 0x4f, 0x3d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, + 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, + 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x32, 0x22, 0x0a, + 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x31, 0x0a, + 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x30, 0x65, 0x3a, 0x34, 0x30, + 0x3a, 0x61, 0x37, 0x3a, 0x36, 0x63, 0x3a, 0x64, 0x65, 0x3a, 0x30, 0x33, + 0x3a, 0x35, 0x64, 0x3a, 0x38, 0x66, 0x3a, 0x64, 0x31, 0x3a, 0x30, 0x66, + 0x3a, 0x65, 0x34, 0x3a, 0x64, 0x31, 0x3a, 0x38, 0x64, 0x3a, 0x66, 0x39, + 0x3a, 0x36, 0x63, 0x3a, 0x61, 0x39, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, + 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, + 0x74, 0x3a, 0x20, 0x61, 0x39, 0x3a, 0x65, 0x39, 0x3a, 0x37, 0x38, 0x3a, + 0x30, 0x38, 0x3a, 0x31, 0x34, 0x3a, 0x33, 0x37, 0x3a, 0x35, 0x38, 0x3a, + 0x38, 0x38, 0x3a, 0x66, 0x32, 0x3a, 0x30, 0x35, 0x3a, 0x31, 0x39, 0x3a, + 0x62, 0x30, 0x3a, 0x36, 0x64, 0x3a, 0x32, 0x62, 0x3a, 0x30, 0x64, 0x3a, + 0x32, 0x62, 0x3a, 0x36, 0x30, 0x3a, 0x31, 0x36, 0x3a, 0x39, 0x30, 0x3a, + 0x37, 0x64, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, + 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, + 0x20, 0x63, 0x61, 0x3a, 0x32, 0x64, 0x3a, 0x38, 0x32, 0x3a, 0x61, 0x30, + 0x3a, 0x38, 0x36, 0x3a, 0x37, 0x37, 0x3a, 0x30, 0x37, 0x3a, 0x32, 0x66, + 0x3a, 0x38, 0x61, 0x3a, 0x62, 0x36, 0x3a, 0x37, 0x36, 0x3a, 0x34, 0x66, + 0x3a, 0x66, 0x30, 0x3a, 0x33, 0x35, 0x3a, 0x36, 0x37, 0x3a, 0x36, 0x63, + 0x3a, 0x66, 0x65, 0x3a, 0x33, 0x65, 0x3a, 0x35, 0x65, 0x3a, 0x33, 0x32, + 0x3a, 0x35, 0x65, 0x3a, 0x30, 0x31, 0x3a, 0x32, 0x31, 0x3a, 0x37, 0x32, + 0x3a, 0x64, 0x66, 0x3a, 0x33, 0x66, 0x3a, 0x39, 0x32, 0x3a, 0x30, 0x39, + 0x3a, 0x36, 0x64, 0x3a, 0x62, 0x37, 0x3a, 0x39, 0x62, 0x3a, 0x38, 0x35, + 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, + 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x44, 0x5a, 0x6a, 0x43, + 0x43, 0x41, 0x6b, 0x36, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, + 0x42, 0x41, 0x54, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, + 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x42, + 0x45, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, + 0x47, 0x45, 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, 0x57, 0x0a, 0x4d, 0x42, + 0x51, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4e, 0x52, 0x32, + 0x56, 0x76, 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x51, 0x67, 0x53, 0x57, + 0x35, 0x6a, 0x4c, 0x6a, 0x45, 0x64, 0x4d, 0x42, 0x73, 0x47, 0x41, 0x31, + 0x55, 0x45, 0x41, 0x78, 0x4d, 0x55, 0x52, 0x32, 0x56, 0x76, 0x56, 0x48, + 0x4a, 0x31, 0x63, 0x33, 0x51, 0x67, 0x52, 0x32, 0x78, 0x76, 0x59, 0x6d, + 0x46, 0x73, 0x0a, 0x49, 0x45, 0x4e, 0x42, 0x49, 0x44, 0x49, 0x77, 0x48, + 0x68, 0x63, 0x4e, 0x4d, 0x44, 0x51, 0x77, 0x4d, 0x7a, 0x41, 0x30, 0x4d, + 0x44, 0x55, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x57, 0x68, 0x63, 0x4e, 0x4d, + 0x54, 0x6b, 0x77, 0x4d, 0x7a, 0x41, 0x30, 0x4d, 0x44, 0x55, 0x77, 0x4d, + 0x44, 0x41, 0x77, 0x57, 0x6a, 0x42, 0x45, 0x4d, 0x51, 0x73, 0x77, 0x43, + 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x0a, 0x45, 0x77, 0x4a, 0x56, + 0x55, 0x7a, 0x45, 0x57, 0x4d, 0x42, 0x51, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x43, 0x68, 0x4d, 0x4e, 0x52, 0x32, 0x56, 0x76, 0x56, 0x48, 0x4a, 0x31, + 0x63, 0x33, 0x51, 0x67, 0x53, 0x57, 0x35, 0x6a, 0x4c, 0x6a, 0x45, 0x64, + 0x4d, 0x42, 0x73, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x55, + 0x52, 0x32, 0x56, 0x76, 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x51, 0x67, + 0x0a, 0x52, 0x32, 0x78, 0x76, 0x59, 0x6d, 0x46, 0x73, 0x49, 0x45, 0x4e, + 0x42, 0x49, 0x44, 0x49, 0x77, 0x67, 0x67, 0x45, 0x69, 0x4d, 0x41, 0x30, + 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, + 0x42, 0x41, 0x51, 0x55, 0x41, 0x41, 0x34, 0x49, 0x42, 0x44, 0x77, 0x41, + 0x77, 0x67, 0x67, 0x45, 0x4b, 0x41, 0x6f, 0x49, 0x42, 0x41, 0x51, 0x44, + 0x76, 0x50, 0x45, 0x31, 0x41, 0x0a, 0x50, 0x52, 0x44, 0x66, 0x4f, 0x31, + 0x4d, 0x41, 0x34, 0x57, 0x66, 0x2b, 0x6c, 0x47, 0x41, 0x56, 0x50, 0x6f, + 0x57, 0x49, 0x38, 0x59, 0x6b, 0x4e, 0x6b, 0x4d, 0x67, 0x6f, 0x49, 0x35, + 0x6b, 0x46, 0x36, 0x43, 0x73, 0x67, 0x6e, 0x63, 0x62, 0x7a, 0x59, 0x45, + 0x62, 0x59, 0x77, 0x62, 0x4c, 0x56, 0x6a, 0x44, 0x48, 0x5a, 0x33, 0x43, + 0x42, 0x35, 0x4a, 0x49, 0x47, 0x2f, 0x4e, 0x54, 0x4c, 0x38, 0x0a, 0x59, + 0x32, 0x6e, 0x62, 0x73, 0x53, 0x70, 0x72, 0x37, 0x69, 0x46, 0x59, 0x38, + 0x67, 0x6a, 0x70, 0x65, 0x4d, 0x74, 0x76, 0x79, 0x2f, 0x77, 0x57, 0x55, + 0x73, 0x69, 0x52, 0x78, 0x50, 0x38, 0x39, 0x63, 0x39, 0x36, 0x78, 0x50, + 0x71, 0x66, 0x43, 0x66, 0x57, 0x62, 0x42, 0x39, 0x58, 0x35, 0x53, 0x4a, + 0x42, 0x72, 0x69, 0x31, 0x57, 0x65, 0x52, 0x30, 0x49, 0x49, 0x51, 0x31, + 0x33, 0x68, 0x4c, 0x0a, 0x54, 0x79, 0x74, 0x43, 0x4f, 0x62, 0x31, 0x6b, + 0x4c, 0x55, 0x43, 0x67, 0x73, 0x42, 0x44, 0x54, 0x4f, 0x45, 0x68, 0x47, + 0x69, 0x4b, 0x45, 0x4d, 0x75, 0x7a, 0x6f, 0x7a, 0x4b, 0x6d, 0x4b, 0x59, + 0x2b, 0x77, 0x43, 0x64, 0x45, 0x31, 0x6c, 0x2f, 0x62, 0x7a, 0x74, 0x79, + 0x71, 0x75, 0x36, 0x6d, 0x44, 0x34, 0x62, 0x35, 0x42, 0x57, 0x48, 0x71, + 0x5a, 0x33, 0x38, 0x4d, 0x4e, 0x35, 0x61, 0x4c, 0x0a, 0x35, 0x6d, 0x6b, + 0x57, 0x52, 0x78, 0x48, 0x43, 0x4a, 0x31, 0x6b, 0x44, 0x73, 0x36, 0x5a, + 0x67, 0x77, 0x69, 0x46, 0x41, 0x56, 0x76, 0x71, 0x67, 0x78, 0x33, 0x30, + 0x36, 0x45, 0x2b, 0x50, 0x73, 0x56, 0x38, 0x65, 0x7a, 0x31, 0x71, 0x36, + 0x64, 0x69, 0x59, 0x44, 0x33, 0x41, 0x65, 0x63, 0x73, 0x39, 0x70, 0x59, + 0x72, 0x45, 0x77, 0x31, 0x35, 0x4c, 0x4e, 0x6e, 0x41, 0x35, 0x49, 0x5a, + 0x37, 0x0a, 0x53, 0x34, 0x77, 0x4d, 0x63, 0x6f, 0x4b, 0x4b, 0x2b, 0x78, + 0x66, 0x4e, 0x41, 0x47, 0x77, 0x36, 0x45, 0x7a, 0x79, 0x77, 0x68, 0x49, + 0x64, 0x4c, 0x46, 0x6e, 0x6f, 0x70, 0x73, 0x6b, 0x2f, 0x62, 0x48, 0x64, + 0x51, 0x4c, 0x38, 0x32, 0x59, 0x33, 0x76, 0x64, 0x6a, 0x32, 0x56, 0x37, + 0x74, 0x65, 0x4a, 0x48, 0x71, 0x34, 0x50, 0x49, 0x75, 0x35, 0x2b, 0x70, + 0x49, 0x61, 0x47, 0x6f, 0x53, 0x65, 0x0a, 0x32, 0x48, 0x53, 0x50, 0x71, + 0x68, 0x74, 0x2f, 0x58, 0x76, 0x54, 0x2b, 0x52, 0x53, 0x49, 0x68, 0x41, + 0x67, 0x4d, 0x42, 0x41, 0x41, 0x47, 0x6a, 0x59, 0x7a, 0x42, 0x68, 0x4d, + 0x41, 0x38, 0x47, 0x41, 0x31, 0x55, 0x64, 0x45, 0x77, 0x45, 0x42, 0x2f, + 0x77, 0x51, 0x46, 0x4d, 0x41, 0x4d, 0x42, 0x41, 0x66, 0x38, 0x77, 0x48, + 0x51, 0x59, 0x44, 0x56, 0x52, 0x30, 0x4f, 0x42, 0x42, 0x59, 0x45, 0x0a, + 0x46, 0x48, 0x45, 0x34, 0x4e, 0x76, 0x49, 0x43, 0x4d, 0x56, 0x4e, 0x48, + 0x4b, 0x32, 0x36, 0x36, 0x5a, 0x55, 0x61, 0x70, 0x45, 0x42, 0x56, 0x59, + 0x49, 0x41, 0x55, 0x4a, 0x4d, 0x42, 0x38, 0x47, 0x41, 0x31, 0x55, 0x64, + 0x49, 0x77, 0x51, 0x59, 0x4d, 0x42, 0x61, 0x41, 0x46, 0x48, 0x45, 0x34, + 0x4e, 0x76, 0x49, 0x43, 0x4d, 0x56, 0x4e, 0x48, 0x4b, 0x32, 0x36, 0x36, + 0x5a, 0x55, 0x61, 0x70, 0x0a, 0x45, 0x42, 0x56, 0x59, 0x49, 0x41, 0x55, + 0x4a, 0x4d, 0x41, 0x34, 0x47, 0x41, 0x31, 0x55, 0x64, 0x44, 0x77, 0x45, + 0x42, 0x2f, 0x77, 0x51, 0x45, 0x41, 0x77, 0x49, 0x42, 0x68, 0x6a, 0x41, + 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, + 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x51, 0x45, + 0x41, 0x41, 0x2f, 0x65, 0x31, 0x4b, 0x36, 0x74, 0x64, 0x0a, 0x45, 0x50, + 0x78, 0x37, 0x73, 0x72, 0x4a, 0x65, 0x72, 0x4a, 0x73, 0x4f, 0x66, 0x6c, + 0x4e, 0x34, 0x57, 0x54, 0x35, 0x43, 0x42, 0x50, 0x35, 0x31, 0x6f, 0x36, + 0x32, 0x73, 0x67, 0x55, 0x37, 0x58, 0x41, 0x6f, 0x74, 0x65, 0x78, 0x43, + 0x33, 0x49, 0x55, 0x6e, 0x62, 0x48, 0x4c, 0x42, 0x2f, 0x38, 0x67, 0x54, + 0x4b, 0x59, 0x30, 0x55, 0x76, 0x47, 0x6b, 0x70, 0x4d, 0x7a, 0x4e, 0x54, + 0x45, 0x76, 0x0a, 0x2f, 0x4e, 0x67, 0x64, 0x52, 0x4e, 0x33, 0x67, 0x67, + 0x58, 0x2b, 0x64, 0x36, 0x59, 0x76, 0x68, 0x5a, 0x4a, 0x46, 0x69, 0x43, + 0x7a, 0x6b, 0x49, 0x6a, 0x4b, 0x78, 0x30, 0x6e, 0x56, 0x6e, 0x5a, 0x65, + 0x6c, 0x6c, 0x53, 0x6c, 0x78, 0x47, 0x35, 0x46, 0x6e, 0x74, 0x76, 0x52, + 0x64, 0x4f, 0x57, 0x32, 0x54, 0x46, 0x39, 0x41, 0x6a, 0x59, 0x50, 0x6e, + 0x44, 0x74, 0x75, 0x7a, 0x79, 0x77, 0x4e, 0x0a, 0x41, 0x30, 0x5a, 0x46, + 0x36, 0x36, 0x44, 0x30, 0x66, 0x30, 0x68, 0x45, 0x78, 0x67, 0x68, 0x41, + 0x7a, 0x4e, 0x34, 0x62, 0x63, 0x4c, 0x55, 0x70, 0x72, 0x62, 0x71, 0x4c, + 0x4f, 0x7a, 0x52, 0x6c, 0x64, 0x52, 0x74, 0x78, 0x49, 0x52, 0x30, 0x73, + 0x46, 0x41, 0x71, 0x77, 0x6c, 0x70, 0x57, 0x34, 0x31, 0x75, 0x72, 0x79, + 0x5a, 0x66, 0x73, 0x70, 0x75, 0x6b, 0x2f, 0x71, 0x6b, 0x5a, 0x4e, 0x30, + 0x0a, 0x61, 0x62, 0x62, 0x79, 0x2f, 0x2b, 0x45, 0x61, 0x30, 0x41, 0x7a, + 0x52, 0x64, 0x6f, 0x58, 0x4c, 0x69, 0x69, 0x57, 0x39, 0x6c, 0x31, 0x34, + 0x73, 0x62, 0x78, 0x57, 0x5a, 0x4a, 0x75, 0x65, 0x32, 0x4b, 0x66, 0x38, + 0x69, 0x37, 0x4d, 0x6b, 0x43, 0x78, 0x31, 0x59, 0x41, 0x7a, 0x55, 0x6d, + 0x35, 0x73, 0x32, 0x78, 0x37, 0x55, 0x77, 0x51, 0x61, 0x34, 0x71, 0x6a, + 0x4a, 0x71, 0x68, 0x49, 0x46, 0x0a, 0x49, 0x38, 0x4c, 0x4f, 0x35, 0x37, + 0x73, 0x45, 0x41, 0x73, 0x7a, 0x41, 0x52, 0x36, 0x4c, 0x6b, 0x78, 0x43, + 0x6b, 0x76, 0x57, 0x30, 0x56, 0x58, 0x69, 0x56, 0x48, 0x75, 0x50, 0x4f, + 0x74, 0x53, 0x43, 0x50, 0x38, 0x48, 0x4e, 0x52, 0x36, 0x66, 0x4e, 0x57, + 0x70, 0x48, 0x53, 0x6c, 0x61, 0x59, 0x30, 0x56, 0x71, 0x46, 0x48, 0x34, + 0x7a, 0x31, 0x49, 0x72, 0x2b, 0x72, 0x7a, 0x6f, 0x50, 0x7a, 0x0a, 0x34, + 0x69, 0x49, 0x70, 0x72, 0x6e, 0x32, 0x44, 0x51, 0x4b, 0x69, 0x36, 0x62, + 0x41, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, + 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, + 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x47, 0x65, 0x6f, 0x54, + 0x72, 0x75, 0x73, 0x74, 0x20, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, + 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x4f, 0x3d, 0x47, 0x65, 0x6f, 0x54, + 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, + 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, + 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x55, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x4f, 0x3d, + 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, + 0x2e, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, + 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x55, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x22, 0x0a, 0x23, + 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x31, 0x0a, 0x23, + 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, + 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x39, 0x32, 0x3a, 0x36, 0x35, 0x3a, + 0x35, 0x38, 0x3a, 0x38, 0x62, 0x3a, 0x61, 0x32, 0x3a, 0x31, 0x61, 0x3a, + 0x33, 0x31, 0x3a, 0x37, 0x32, 0x3a, 0x37, 0x33, 0x3a, 0x36, 0x38, 0x3a, + 0x35, 0x63, 0x3a, 0x62, 0x34, 0x3a, 0x61, 0x35, 0x3a, 0x37, 0x61, 0x3a, + 0x30, 0x37, 0x3a, 0x34, 0x38, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, + 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, + 0x3a, 0x20, 0x65, 0x36, 0x3a, 0x32, 0x31, 0x3a, 0x66, 0x33, 0x3a, 0x33, + 0x35, 0x3a, 0x34, 0x33, 0x3a, 0x37, 0x39, 0x3a, 0x30, 0x35, 0x3a, 0x39, + 0x61, 0x3a, 0x34, 0x62, 0x3a, 0x36, 0x38, 0x3a, 0x33, 0x30, 0x3a, 0x39, + 0x64, 0x3a, 0x38, 0x61, 0x3a, 0x32, 0x66, 0x3a, 0x37, 0x34, 0x3a, 0x32, + 0x32, 0x3a, 0x31, 0x35, 0x3a, 0x38, 0x37, 0x3a, 0x65, 0x63, 0x3a, 0x37, + 0x39, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x61, 0x30, 0x3a, 0x34, 0x35, 0x3a, 0x39, 0x62, 0x3a, 0x39, 0x66, 0x3a, + 0x36, 0x33, 0x3a, 0x62, 0x32, 0x3a, 0x32, 0x35, 0x3a, 0x35, 0x39, 0x3a, + 0x66, 0x35, 0x3a, 0x66, 0x61, 0x3a, 0x35, 0x64, 0x3a, 0x34, 0x63, 0x3a, + 0x36, 0x64, 0x3a, 0x62, 0x33, 0x3a, 0x66, 0x39, 0x3a, 0x66, 0x37, 0x3a, + 0x32, 0x66, 0x3a, 0x66, 0x31, 0x3a, 0x39, 0x33, 0x3a, 0x34, 0x32, 0x3a, + 0x30, 0x33, 0x3a, 0x33, 0x35, 0x3a, 0x37, 0x38, 0x3a, 0x66, 0x30, 0x3a, + 0x37, 0x33, 0x3a, 0x62, 0x66, 0x3a, 0x31, 0x64, 0x3a, 0x31, 0x62, 0x3a, + 0x34, 0x36, 0x3a, 0x63, 0x62, 0x3a, 0x62, 0x39, 0x3a, 0x31, 0x32, 0x0a, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, + 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x46, 0x61, 0x44, 0x43, 0x43, + 0x41, 0x31, 0x43, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x42, + 0x41, 0x54, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, + 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x42, 0x46, + 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, + 0x45, 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, 0x57, 0x0a, 0x4d, 0x42, 0x51, + 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4e, 0x52, 0x32, 0x56, + 0x76, 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x51, 0x67, 0x53, 0x57, 0x35, + 0x6a, 0x4c, 0x6a, 0x45, 0x65, 0x4d, 0x42, 0x77, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x41, 0x78, 0x4d, 0x56, 0x52, 0x32, 0x56, 0x76, 0x56, 0x48, 0x4a, + 0x31, 0x63, 0x33, 0x51, 0x67, 0x56, 0x57, 0x35, 0x70, 0x64, 0x6d, 0x56, + 0x79, 0x0a, 0x63, 0x32, 0x46, 0x73, 0x49, 0x45, 0x4e, 0x42, 0x4d, 0x42, + 0x34, 0x58, 0x44, 0x54, 0x41, 0x30, 0x4d, 0x44, 0x4d, 0x77, 0x4e, 0x44, + 0x41, 0x31, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x46, 0x6f, 0x58, 0x44, 0x54, + 0x49, 0x35, 0x4d, 0x44, 0x4d, 0x77, 0x4e, 0x44, 0x41, 0x31, 0x4d, 0x44, + 0x41, 0x77, 0x4d, 0x46, 0x6f, 0x77, 0x52, 0x54, 0x45, 0x4c, 0x4d, 0x41, + 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x0a, 0x42, 0x68, 0x4d, 0x43, 0x56, + 0x56, 0x4d, 0x78, 0x46, 0x6a, 0x41, 0x55, 0x42, 0x67, 0x4e, 0x56, 0x42, + 0x41, 0x6f, 0x54, 0x44, 0x55, 0x64, 0x6c, 0x62, 0x31, 0x52, 0x79, 0x64, + 0x58, 0x4e, 0x30, 0x49, 0x45, 0x6c, 0x75, 0x59, 0x79, 0x34, 0x78, 0x48, + 0x6a, 0x41, 0x63, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x4d, 0x54, 0x46, + 0x55, 0x64, 0x6c, 0x62, 0x31, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x0a, + 0x49, 0x46, 0x56, 0x75, 0x61, 0x58, 0x5a, 0x6c, 0x63, 0x6e, 0x4e, 0x68, + 0x62, 0x43, 0x42, 0x44, 0x51, 0x54, 0x43, 0x43, 0x41, 0x69, 0x49, 0x77, + 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, + 0x41, 0x51, 0x45, 0x42, 0x42, 0x51, 0x41, 0x44, 0x67, 0x67, 0x49, 0x50, + 0x41, 0x44, 0x43, 0x43, 0x41, 0x67, 0x6f, 0x43, 0x67, 0x67, 0x49, 0x42, + 0x41, 0x4b, 0x59, 0x56, 0x0a, 0x56, 0x61, 0x43, 0x6a, 0x78, 0x75, 0x41, + 0x66, 0x6a, 0x4a, 0x30, 0x68, 0x55, 0x4e, 0x66, 0x42, 0x76, 0x69, 0x74, + 0x62, 0x74, 0x61, 0x53, 0x65, 0x6f, 0x64, 0x6c, 0x79, 0x57, 0x4c, 0x30, + 0x41, 0x47, 0x30, 0x79, 0x2f, 0x59, 0x63, 0x6b, 0x55, 0x48, 0x55, 0x57, + 0x43, 0x71, 0x38, 0x59, 0x64, 0x67, 0x4e, 0x59, 0x39, 0x36, 0x78, 0x43, + 0x63, 0x4f, 0x71, 0x39, 0x74, 0x4a, 0x50, 0x69, 0x38, 0x0a, 0x63, 0x51, + 0x47, 0x65, 0x42, 0x76, 0x56, 0x38, 0x58, 0x78, 0x37, 0x42, 0x44, 0x6c, + 0x58, 0x4b, 0x67, 0x35, 0x70, 0x5a, 0x4d, 0x4b, 0x34, 0x5a, 0x79, 0x7a, + 0x42, 0x49, 0x6c, 0x65, 0x30, 0x69, 0x4e, 0x34, 0x33, 0x30, 0x53, 0x70, + 0x70, 0x79, 0x5a, 0x6a, 0x36, 0x74, 0x6c, 0x63, 0x44, 0x67, 0x46, 0x67, + 0x44, 0x67, 0x45, 0x42, 0x38, 0x72, 0x4d, 0x51, 0x37, 0x58, 0x6c, 0x46, + 0x54, 0x54, 0x0a, 0x51, 0x6a, 0x4f, 0x67, 0x4e, 0x42, 0x30, 0x65, 0x52, + 0x58, 0x62, 0x64, 0x54, 0x38, 0x6f, 0x59, 0x4e, 0x2b, 0x79, 0x46, 0x46, + 0x58, 0x6f, 0x5a, 0x43, 0x50, 0x7a, 0x56, 0x78, 0x35, 0x7a, 0x77, 0x38, + 0x71, 0x6b, 0x75, 0x45, 0x4b, 0x6d, 0x53, 0x35, 0x6a, 0x31, 0x59, 0x50, + 0x61, 0x6b, 0x57, 0x61, 0x44, 0x77, 0x76, 0x64, 0x53, 0x45, 0x59, 0x66, + 0x79, 0x68, 0x33, 0x70, 0x65, 0x46, 0x68, 0x0a, 0x46, 0x37, 0x65, 0x6d, + 0x36, 0x66, 0x67, 0x65, 0x6d, 0x64, 0x74, 0x7a, 0x62, 0x76, 0x51, 0x4b, + 0x6f, 0x69, 0x46, 0x73, 0x37, 0x74, 0x71, 0x71, 0x68, 0x5a, 0x4a, 0x6d, + 0x72, 0x2f, 0x5a, 0x36, 0x61, 0x34, 0x4c, 0x61, 0x75, 0x69, 0x49, 0x49, + 0x4e, 0x51, 0x2f, 0x50, 0x51, 0x76, 0x45, 0x31, 0x2b, 0x6d, 0x72, 0x75, + 0x66, 0x69, 0x73, 0x6c, 0x7a, 0x44, 0x6f, 0x52, 0x35, 0x47, 0x32, 0x76, + 0x0a, 0x63, 0x37, 0x4a, 0x32, 0x48, 0x61, 0x33, 0x51, 0x73, 0x6e, 0x68, + 0x6e, 0x47, 0x71, 0x51, 0x35, 0x48, 0x46, 0x45, 0x4c, 0x5a, 0x31, 0x61, + 0x44, 0x2f, 0x54, 0x68, 0x64, 0x44, 0x63, 0x37, 0x64, 0x38, 0x4c, 0x73, + 0x72, 0x6c, 0x68, 0x2f, 0x65, 0x65, 0x7a, 0x4a, 0x53, 0x2f, 0x52, 0x32, + 0x37, 0x74, 0x51, 0x61, 0x68, 0x73, 0x69, 0x46, 0x65, 0x70, 0x64, 0x61, + 0x56, 0x61, 0x48, 0x2f, 0x77, 0x0a, 0x6d, 0x5a, 0x37, 0x63, 0x52, 0x51, + 0x67, 0x2b, 0x35, 0x39, 0x49, 0x4a, 0x44, 0x54, 0x57, 0x55, 0x33, 0x59, + 0x42, 0x4f, 0x55, 0x35, 0x66, 0x58, 0x74, 0x51, 0x6c, 0x45, 0x49, 0x47, + 0x51, 0x57, 0x46, 0x77, 0x4d, 0x43, 0x54, 0x46, 0x4d, 0x4e, 0x61, 0x4e, + 0x37, 0x56, 0x71, 0x6e, 0x4a, 0x4e, 0x6b, 0x32, 0x32, 0x43, 0x44, 0x74, + 0x75, 0x63, 0x76, 0x63, 0x2b, 0x30, 0x38, 0x31, 0x78, 0x64, 0x0a, 0x56, + 0x48, 0x70, 0x70, 0x43, 0x5a, 0x62, 0x57, 0x32, 0x78, 0x48, 0x42, 0x6a, + 0x58, 0x57, 0x6f, 0x74, 0x4d, 0x38, 0x35, 0x79, 0x4d, 0x34, 0x38, 0x76, + 0x43, 0x52, 0x38, 0x35, 0x6d, 0x4c, 0x4b, 0x34, 0x62, 0x31, 0x39, 0x70, + 0x37, 0x31, 0x58, 0x5a, 0x51, 0x76, 0x6b, 0x2f, 0x69, 0x58, 0x74, 0x74, + 0x6d, 0x6b, 0x51, 0x33, 0x43, 0x67, 0x61, 0x52, 0x72, 0x30, 0x42, 0x48, + 0x64, 0x43, 0x58, 0x0a, 0x74, 0x65, 0x47, 0x59, 0x4f, 0x38, 0x41, 0x33, + 0x5a, 0x4e, 0x59, 0x39, 0x6c, 0x4f, 0x34, 0x4c, 0x34, 0x66, 0x55, 0x6f, + 0x72, 0x67, 0x74, 0x57, 0x76, 0x33, 0x47, 0x4c, 0x49, 0x79, 0x6c, 0x42, + 0x6a, 0x6f, 0x62, 0x46, 0x53, 0x31, 0x4a, 0x37, 0x32, 0x48, 0x47, 0x72, + 0x48, 0x34, 0x6f, 0x56, 0x70, 0x6a, 0x75, 0x44, 0x57, 0x74, 0x64, 0x59, + 0x41, 0x56, 0x48, 0x47, 0x54, 0x45, 0x48, 0x5a, 0x0a, 0x66, 0x39, 0x68, + 0x42, 0x5a, 0x33, 0x4b, 0x69, 0x4b, 0x4e, 0x39, 0x67, 0x67, 0x36, 0x6d, + 0x65, 0x79, 0x48, 0x76, 0x38, 0x55, 0x33, 0x4e, 0x79, 0x57, 0x66, 0x57, + 0x54, 0x65, 0x68, 0x64, 0x32, 0x44, 0x73, 0x37, 0x33, 0x35, 0x56, 0x7a, + 0x5a, 0x43, 0x31, 0x55, 0x30, 0x6f, 0x71, 0x70, 0x62, 0x74, 0x57, 0x70, + 0x55, 0x35, 0x78, 0x50, 0x4b, 0x56, 0x2b, 0x79, 0x58, 0x62, 0x66, 0x52, + 0x65, 0x0a, 0x42, 0x69, 0x39, 0x46, 0x69, 0x31, 0x6a, 0x55, 0x49, 0x78, + 0x61, 0x53, 0x35, 0x42, 0x5a, 0x75, 0x4b, 0x47, 0x4e, 0x5a, 0x4d, 0x4e, + 0x39, 0x51, 0x41, 0x5a, 0x78, 0x6a, 0x69, 0x52, 0x71, 0x66, 0x32, 0x78, + 0x65, 0x55, 0x67, 0x6e, 0x41, 0x33, 0x77, 0x79, 0x53, 0x65, 0x6d, 0x6b, + 0x66, 0x57, 0x57, 0x73, 0x70, 0x4f, 0x71, 0x47, 0x6d, 0x4a, 0x63, 0x68, + 0x2b, 0x52, 0x62, 0x4e, 0x74, 0x2b, 0x0a, 0x6e, 0x68, 0x75, 0x74, 0x78, + 0x78, 0x39, 0x7a, 0x33, 0x53, 0x78, 0x50, 0x47, 0x57, 0x58, 0x39, 0x66, + 0x35, 0x4e, 0x41, 0x45, 0x43, 0x37, 0x53, 0x38, 0x4f, 0x30, 0x38, 0x6e, + 0x69, 0x34, 0x6f, 0x50, 0x6d, 0x6b, 0x6d, 0x4d, 0x38, 0x56, 0x37, 0x41, + 0x67, 0x4d, 0x42, 0x41, 0x41, 0x47, 0x6a, 0x59, 0x7a, 0x42, 0x68, 0x4d, + 0x41, 0x38, 0x47, 0x41, 0x31, 0x55, 0x64, 0x45, 0x77, 0x45, 0x42, 0x0a, + 0x2f, 0x77, 0x51, 0x46, 0x4d, 0x41, 0x4d, 0x42, 0x41, 0x66, 0x38, 0x77, + 0x48, 0x51, 0x59, 0x44, 0x56, 0x52, 0x30, 0x4f, 0x42, 0x42, 0x59, 0x45, + 0x46, 0x4e, 0x71, 0x37, 0x4c, 0x71, 0x71, 0x77, 0x44, 0x4c, 0x69, 0x49, + 0x4a, 0x6c, 0x46, 0x30, 0x58, 0x47, 0x30, 0x44, 0x30, 0x38, 0x44, 0x59, + 0x6a, 0x33, 0x72, 0x57, 0x4d, 0x42, 0x38, 0x47, 0x41, 0x31, 0x55, 0x64, + 0x49, 0x77, 0x51, 0x59, 0x0a, 0x4d, 0x42, 0x61, 0x41, 0x46, 0x4e, 0x71, + 0x37, 0x4c, 0x71, 0x71, 0x77, 0x44, 0x4c, 0x69, 0x49, 0x4a, 0x6c, 0x46, + 0x30, 0x58, 0x47, 0x30, 0x44, 0x30, 0x38, 0x44, 0x59, 0x6a, 0x33, 0x72, + 0x57, 0x4d, 0x41, 0x34, 0x47, 0x41, 0x31, 0x55, 0x64, 0x44, 0x77, 0x45, + 0x42, 0x2f, 0x77, 0x51, 0x45, 0x41, 0x77, 0x49, 0x42, 0x68, 0x6a, 0x41, + 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x0a, 0x39, 0x77, + 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x67, + 0x45, 0x41, 0x4d, 0x58, 0x6a, 0x6d, 0x78, 0x37, 0x58, 0x66, 0x75, 0x4a, + 0x52, 0x41, 0x79, 0x58, 0x48, 0x45, 0x71, 0x44, 0x58, 0x73, 0x52, 0x68, + 0x33, 0x43, 0x68, 0x66, 0x4d, 0x6f, 0x57, 0x49, 0x61, 0x77, 0x43, 0x2f, + 0x79, 0x4f, 0x73, 0x6a, 0x6d, 0x50, 0x52, 0x46, 0x57, 0x72, 0x5a, 0x49, + 0x52, 0x63, 0x0a, 0x61, 0x61, 0x6e, 0x51, 0x6d, 0x6a, 0x67, 0x38, 0x2b, + 0x75, 0x55, 0x66, 0x4e, 0x65, 0x56, 0x45, 0x34, 0x34, 0x42, 0x35, 0x6c, + 0x47, 0x69, 0x6b, 0x75, 0x38, 0x53, 0x66, 0x50, 0x65, 0x45, 0x30, 0x7a, + 0x54, 0x42, 0x47, 0x69, 0x31, 0x51, 0x72, 0x6c, 0x61, 0x58, 0x76, 0x39, + 0x7a, 0x2b, 0x5a, 0x68, 0x50, 0x30, 0x31, 0x35, 0x73, 0x38, 0x78, 0x78, + 0x74, 0x78, 0x71, 0x76, 0x36, 0x66, 0x58, 0x0a, 0x49, 0x77, 0x6a, 0x68, + 0x6d, 0x46, 0x37, 0x44, 0x57, 0x67, 0x68, 0x32, 0x71, 0x61, 0x61, 0x76, + 0x64, 0x79, 0x2b, 0x33, 0x59, 0x4c, 0x31, 0x45, 0x52, 0x6d, 0x72, 0x76, + 0x6c, 0x2f, 0x39, 0x7a, 0x6c, 0x63, 0x47, 0x4f, 0x36, 0x4a, 0x50, 0x37, + 0x2f, 0x54, 0x47, 0x33, 0x37, 0x46, 0x63, 0x52, 0x45, 0x55, 0x57, 0x62, + 0x4d, 0x50, 0x45, 0x61, 0x69, 0x44, 0x6e, 0x42, 0x54, 0x7a, 0x79, 0x6e, + 0x0a, 0x41, 0x4e, 0x58, 0x48, 0x2f, 0x4b, 0x74, 0x74, 0x67, 0x43, 0x4a, + 0x77, 0x70, 0x51, 0x7a, 0x67, 0x58, 0x51, 0x51, 0x70, 0x41, 0x76, 0x76, + 0x4c, 0x6f, 0x4a, 0x48, 0x52, 0x66, 0x4e, 0x62, 0x44, 0x66, 0x6c, 0x44, + 0x56, 0x6e, 0x56, 0x69, 0x2b, 0x51, 0x54, 0x6a, 0x72, 0x75, 0x58, 0x55, + 0x38, 0x46, 0x64, 0x6d, 0x62, 0x79, 0x55, 0x71, 0x44, 0x57, 0x63, 0x44, + 0x61, 0x55, 0x2f, 0x30, 0x7a, 0x0a, 0x75, 0x7a, 0x59, 0x59, 0x6d, 0x34, + 0x55, 0x50, 0x46, 0x64, 0x33, 0x75, 0x4c, 0x61, 0x78, 0x32, 0x6b, 0x37, + 0x6e, 0x5a, 0x41, 0x59, 0x31, 0x49, 0x45, 0x4b, 0x6a, 0x37, 0x39, 0x54, + 0x69, 0x47, 0x38, 0x64, 0x73, 0x4b, 0x78, 0x72, 0x32, 0x45, 0x6f, 0x79, + 0x4e, 0x42, 0x33, 0x74, 0x5a, 0x33, 0x62, 0x34, 0x58, 0x55, 0x68, 0x52, + 0x78, 0x51, 0x34, 0x4b, 0x35, 0x52, 0x69, 0x72, 0x71, 0x4e, 0x0a, 0x50, + 0x6e, 0x62, 0x69, 0x75, 0x63, 0x6f, 0x6e, 0x38, 0x6c, 0x2b, 0x66, 0x37, + 0x32, 0x35, 0x5a, 0x44, 0x51, 0x62, 0x59, 0x4b, 0x78, 0x65, 0x6b, 0x30, + 0x6e, 0x78, 0x72, 0x75, 0x31, 0x38, 0x55, 0x47, 0x6b, 0x69, 0x50, 0x47, + 0x6b, 0x7a, 0x6e, 0x73, 0x30, 0x63, 0x63, 0x6a, 0x6b, 0x78, 0x46, 0x4b, + 0x79, 0x44, 0x75, 0x53, 0x4e, 0x2f, 0x6e, 0x33, 0x51, 0x6d, 0x4f, 0x47, + 0x4b, 0x6a, 0x61, 0x0a, 0x51, 0x49, 0x32, 0x53, 0x4a, 0x68, 0x46, 0x54, + 0x59, 0x58, 0x4e, 0x64, 0x36, 0x37, 0x33, 0x6e, 0x78, 0x45, 0x30, 0x70, + 0x4e, 0x32, 0x48, 0x72, 0x72, 0x44, 0x6b, 0x74, 0x5a, 0x79, 0x34, 0x57, + 0x31, 0x76, 0x55, 0x41, 0x67, 0x34, 0x57, 0x68, 0x7a, 0x48, 0x39, 0x32, + 0x78, 0x48, 0x33, 0x6b, 0x74, 0x30, 0x74, 0x6d, 0x37, 0x77, 0x4e, 0x46, + 0x59, 0x47, 0x6d, 0x32, 0x44, 0x46, 0x4b, 0x57, 0x0a, 0x6b, 0x6f, 0x52, + 0x65, 0x70, 0x71, 0x4f, 0x31, 0x70, 0x44, 0x34, 0x72, 0x32, 0x63, 0x7a, + 0x59, 0x47, 0x30, 0x65, 0x71, 0x38, 0x6b, 0x54, 0x61, 0x54, 0x2f, 0x6b, + 0x44, 0x36, 0x50, 0x41, 0x55, 0x79, 0x7a, 0x2f, 0x7a, 0x67, 0x39, 0x37, + 0x51, 0x77, 0x56, 0x54, 0x6a, 0x74, 0x2b, 0x67, 0x4b, 0x4e, 0x30, 0x32, + 0x4c, 0x49, 0x46, 0x6b, 0x44, 0x4d, 0x42, 0x6d, 0x68, 0x4c, 0x4d, 0x69, + 0x39, 0x0a, 0x45, 0x52, 0x2f, 0x66, 0x72, 0x73, 0x6c, 0x4b, 0x78, 0x66, + 0x4d, 0x6e, 0x5a, 0x6d, 0x61, 0x47, 0x72, 0x47, 0x69, 0x52, 0x2f, 0x39, + 0x6e, 0x6d, 0x55, 0x78, 0x77, 0x50, 0x69, 0x31, 0x78, 0x70, 0x5a, 0x51, + 0x6f, 0x6d, 0x79, 0x42, 0x34, 0x30, 0x77, 0x31, 0x31, 0x52, 0x65, 0x39, + 0x65, 0x70, 0x6e, 0x41, 0x61, 0x68, 0x4e, 0x74, 0x33, 0x56, 0x69, 0x5a, + 0x53, 0x38, 0x32, 0x65, 0x51, 0x74, 0x0a, 0x44, 0x46, 0x34, 0x4a, 0x62, + 0x41, 0x69, 0x58, 0x66, 0x4b, 0x4d, 0x39, 0x66, 0x4a, 0x50, 0x2f, 0x50, + 0x36, 0x45, 0x55, 0x70, 0x38, 0x2b, 0x31, 0x58, 0x65, 0x76, 0x62, 0x32, + 0x78, 0x7a, 0x45, 0x64, 0x74, 0x2b, 0x49, 0x75, 0x62, 0x31, 0x46, 0x42, + 0x5a, 0x55, 0x62, 0x72, 0x76, 0x78, 0x47, 0x61, 0x6b, 0x79, 0x76, 0x53, + 0x4f, 0x50, 0x4f, 0x72, 0x67, 0x2f, 0x53, 0x66, 0x75, 0x76, 0x6d, 0x0a, + 0x62, 0x4a, 0x78, 0x50, 0x67, 0x57, 0x70, 0x36, 0x5a, 0x4b, 0x79, 0x37, + 0x50, 0x74, 0x58, 0x6e, 0x79, 0x33, 0x59, 0x75, 0x78, 0x61, 0x64, 0x49, + 0x77, 0x56, 0x79, 0x51, 0x44, 0x38, 0x76, 0x49, 0x50, 0x2f, 0x72, 0x6d, + 0x4d, 0x75, 0x47, 0x4e, 0x47, 0x32, 0x2b, 0x6b, 0x35, 0x6f, 0x37, 0x59, + 0x2b, 0x53, 0x6c, 0x49, 0x69, 0x73, 0x35, 0x7a, 0x2f, 0x69, 0x77, 0x3d, + 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, + 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, + 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x20, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x20, + 0x43, 0x41, 0x20, 0x32, 0x20, 0x4f, 0x3d, 0x47, 0x65, 0x6f, 0x54, 0x72, + 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x53, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x47, + 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x55, 0x6e, 0x69, 0x76, + 0x65, 0x72, 0x73, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x32, 0x20, 0x4f, + 0x3d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, + 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, + 0x22, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x55, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x32, + 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, + 0x31, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, + 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x33, 0x34, 0x3a, + 0x66, 0x63, 0x3a, 0x62, 0x38, 0x3a, 0x64, 0x30, 0x3a, 0x33, 0x36, 0x3a, + 0x64, 0x62, 0x3a, 0x39, 0x65, 0x3a, 0x31, 0x34, 0x3a, 0x62, 0x33, 0x3a, + 0x63, 0x32, 0x3a, 0x66, 0x32, 0x3a, 0x64, 0x62, 0x3a, 0x38, 0x66, 0x3a, + 0x65, 0x34, 0x3a, 0x39, 0x34, 0x3a, 0x63, 0x37, 0x0a, 0x23, 0x20, 0x53, + 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, + 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x33, 0x37, 0x3a, 0x39, 0x61, 0x3a, 0x31, + 0x39, 0x3a, 0x37, 0x62, 0x3a, 0x34, 0x31, 0x3a, 0x38, 0x35, 0x3a, 0x34, + 0x35, 0x3a, 0x33, 0x35, 0x3a, 0x30, 0x63, 0x3a, 0x61, 0x36, 0x3a, 0x30, + 0x33, 0x3a, 0x36, 0x39, 0x3a, 0x66, 0x33, 0x3a, 0x33, 0x63, 0x3a, 0x32, + 0x65, 0x3a, 0x61, 0x66, 0x3a, 0x34, 0x37, 0x3a, 0x34, 0x66, 0x3a, 0x32, + 0x30, 0x3a, 0x37, 0x39, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, + 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, + 0x74, 0x3a, 0x20, 0x61, 0x30, 0x3a, 0x32, 0x33, 0x3a, 0x34, 0x66, 0x3a, + 0x33, 0x62, 0x3a, 0x63, 0x38, 0x3a, 0x35, 0x32, 0x3a, 0x37, 0x63, 0x3a, + 0x61, 0x35, 0x3a, 0x36, 0x32, 0x3a, 0x38, 0x65, 0x3a, 0x65, 0x63, 0x3a, + 0x38, 0x31, 0x3a, 0x61, 0x64, 0x3a, 0x35, 0x64, 0x3a, 0x36, 0x39, 0x3a, + 0x38, 0x39, 0x3a, 0x35, 0x64, 0x3a, 0x61, 0x35, 0x3a, 0x36, 0x38, 0x3a, + 0x30, 0x64, 0x3a, 0x63, 0x39, 0x3a, 0x31, 0x64, 0x3a, 0x31, 0x63, 0x3a, + 0x62, 0x38, 0x3a, 0x34, 0x37, 0x3a, 0x37, 0x66, 0x3a, 0x33, 0x33, 0x3a, + 0x66, 0x38, 0x3a, 0x37, 0x38, 0x3a, 0x62, 0x39, 0x3a, 0x35, 0x62, 0x3a, + 0x30, 0x62, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, + 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, + 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x46, 0x62, + 0x44, 0x43, 0x43, 0x41, 0x31, 0x53, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, + 0x67, 0x49, 0x42, 0x41, 0x54, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, + 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, + 0x44, 0x42, 0x48, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, + 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, 0x57, 0x0a, + 0x4d, 0x42, 0x51, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4e, + 0x52, 0x32, 0x56, 0x76, 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x51, 0x67, + 0x53, 0x57, 0x35, 0x6a, 0x4c, 0x6a, 0x45, 0x67, 0x4d, 0x42, 0x34, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x58, 0x52, 0x32, 0x56, 0x76, + 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x51, 0x67, 0x56, 0x57, 0x35, 0x70, + 0x64, 0x6d, 0x56, 0x79, 0x0a, 0x63, 0x32, 0x46, 0x73, 0x49, 0x45, 0x4e, + 0x42, 0x49, 0x44, 0x49, 0x77, 0x48, 0x68, 0x63, 0x4e, 0x4d, 0x44, 0x51, + 0x77, 0x4d, 0x7a, 0x41, 0x30, 0x4d, 0x44, 0x55, 0x77, 0x4d, 0x44, 0x41, + 0x77, 0x57, 0x68, 0x63, 0x4e, 0x4d, 0x6a, 0x6b, 0x77, 0x4d, 0x7a, 0x41, + 0x30, 0x4d, 0x44, 0x55, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x57, 0x6a, 0x42, + 0x48, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x0a, 0x56, 0x51, + 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, 0x57, 0x4d, 0x42, + 0x51, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4e, 0x52, 0x32, + 0x56, 0x76, 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x51, 0x67, 0x53, 0x57, + 0x35, 0x6a, 0x4c, 0x6a, 0x45, 0x67, 0x4d, 0x42, 0x34, 0x47, 0x41, 0x31, + 0x55, 0x45, 0x41, 0x78, 0x4d, 0x58, 0x52, 0x32, 0x56, 0x76, 0x56, 0x48, + 0x4a, 0x31, 0x0a, 0x63, 0x33, 0x51, 0x67, 0x56, 0x57, 0x35, 0x70, 0x64, + 0x6d, 0x56, 0x79, 0x63, 0x32, 0x46, 0x73, 0x49, 0x45, 0x4e, 0x42, 0x49, + 0x44, 0x49, 0x77, 0x67, 0x67, 0x49, 0x69, 0x4d, 0x41, 0x30, 0x47, 0x43, + 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x41, + 0x51, 0x55, 0x41, 0x41, 0x34, 0x49, 0x43, 0x44, 0x77, 0x41, 0x77, 0x67, + 0x67, 0x49, 0x4b, 0x41, 0x6f, 0x49, 0x43, 0x0a, 0x41, 0x51, 0x43, 0x7a, + 0x56, 0x46, 0x4c, 0x42, 0x79, 0x54, 0x37, 0x79, 0x32, 0x64, 0x79, 0x78, + 0x55, 0x78, 0x70, 0x5a, 0x4b, 0x65, 0x65, 0x78, 0x77, 0x30, 0x55, 0x6f, + 0x35, 0x64, 0x66, 0x52, 0x37, 0x63, 0x58, 0x46, 0x53, 0x36, 0x47, 0x71, + 0x64, 0x48, 0x74, 0x58, 0x72, 0x30, 0x6f, 0x6d, 0x2f, 0x4e, 0x6a, 0x31, + 0x58, 0x71, 0x64, 0x75, 0x47, 0x64, 0x74, 0x30, 0x44, 0x45, 0x38, 0x31, + 0x0a, 0x57, 0x7a, 0x49, 0x4c, 0x41, 0x65, 0x50, 0x62, 0x36, 0x33, 0x70, + 0x33, 0x4e, 0x65, 0x71, 0x71, 0x57, 0x75, 0x44, 0x57, 0x36, 0x4b, 0x46, + 0x58, 0x6c, 0x50, 0x43, 0x51, 0x6f, 0x33, 0x52, 0x57, 0x6c, 0x45, 0x51, + 0x77, 0x41, 0x78, 0x35, 0x63, 0x54, 0x69, 0x75, 0x46, 0x4a, 0x6e, 0x53, + 0x43, 0x65, 0x67, 0x78, 0x32, 0x6f, 0x47, 0x39, 0x4e, 0x7a, 0x6b, 0x45, + 0x74, 0x6f, 0x42, 0x55, 0x47, 0x0a, 0x46, 0x46, 0x2b, 0x33, 0x51, 0x73, + 0x31, 0x37, 0x6a, 0x31, 0x68, 0x68, 0x4e, 0x4e, 0x77, 0x71, 0x43, 0x50, + 0x6b, 0x75, 0x77, 0x77, 0x47, 0x6d, 0x49, 0x6b, 0x51, 0x63, 0x54, 0x41, + 0x65, 0x43, 0x35, 0x6c, 0x76, 0x4f, 0x30, 0x45, 0x70, 0x38, 0x42, 0x4e, + 0x4d, 0x5a, 0x63, 0x79, 0x66, 0x77, 0x71, 0x70, 0x68, 0x2f, 0x4c, 0x71, + 0x39, 0x4f, 0x36, 0x34, 0x63, 0x65, 0x4a, 0x48, 0x64, 0x71, 0x0a, 0x58, + 0x62, 0x62, 0x6f, 0x57, 0x30, 0x57, 0x36, 0x33, 0x4d, 0x4f, 0x68, 0x42, + 0x57, 0x39, 0x57, 0x6a, 0x6f, 0x38, 0x51, 0x4a, 0x71, 0x56, 0x4a, 0x77, + 0x79, 0x37, 0x58, 0x51, 0x59, 0x63, 0x69, 0x34, 0x45, 0x2b, 0x47, 0x79, + 0x6d, 0x43, 0x31, 0x36, 0x71, 0x46, 0x6a, 0x77, 0x41, 0x47, 0x58, 0x45, + 0x48, 0x6d, 0x39, 0x41, 0x44, 0x77, 0x53, 0x62, 0x53, 0x73, 0x56, 0x73, + 0x61, 0x78, 0x4c, 0x0a, 0x73, 0x65, 0x34, 0x59, 0x75, 0x55, 0x36, 0x57, + 0x33, 0x4e, 0x78, 0x32, 0x2f, 0x7a, 0x75, 0x2b, 0x7a, 0x31, 0x38, 0x44, + 0x77, 0x50, 0x77, 0x37, 0x36, 0x4c, 0x35, 0x47, 0x47, 0x2f, 0x2f, 0x61, + 0x51, 0x4d, 0x4a, 0x53, 0x39, 0x2f, 0x37, 0x6a, 0x4f, 0x76, 0x64, 0x71, + 0x64, 0x7a, 0x58, 0x51, 0x32, 0x6f, 0x33, 0x72, 0x58, 0x68, 0x68, 0x71, + 0x4d, 0x63, 0x63, 0x65, 0x75, 0x6a, 0x77, 0x62, 0x0a, 0x4b, 0x4e, 0x5a, + 0x72, 0x56, 0x4d, 0x61, 0x71, 0x57, 0x39, 0x65, 0x69, 0x4c, 0x42, 0x73, + 0x5a, 0x7a, 0x4b, 0x49, 0x43, 0x39, 0x70, 0x74, 0x5a, 0x76, 0x54, 0x64, + 0x72, 0x68, 0x72, 0x56, 0x74, 0x67, 0x72, 0x72, 0x59, 0x36, 0x73, 0x6c, + 0x57, 0x76, 0x4b, 0x6b, 0x32, 0x57, 0x50, 0x30, 0x2b, 0x47, 0x66, 0x50, + 0x74, 0x44, 0x43, 0x61, 0x70, 0x6b, 0x7a, 0x6a, 0x34, 0x54, 0x38, 0x46, + 0x64, 0x0a, 0x49, 0x67, 0x62, 0x51, 0x6c, 0x2b, 0x72, 0x68, 0x72, 0x63, + 0x5a, 0x56, 0x34, 0x49, 0x45, 0x72, 0x4b, 0x49, 0x4d, 0x36, 0x2b, 0x76, + 0x52, 0x37, 0x49, 0x56, 0x45, 0x41, 0x76, 0x6c, 0x49, 0x34, 0x7a, 0x73, + 0x31, 0x6d, 0x65, 0x61, 0x6a, 0x30, 0x67, 0x56, 0x62, 0x69, 0x30, 0x49, + 0x4d, 0x4a, 0x52, 0x31, 0x46, 0x62, 0x55, 0x47, 0x72, 0x50, 0x32, 0x30, + 0x67, 0x61, 0x58, 0x54, 0x37, 0x33, 0x0a, 0x79, 0x2f, 0x5a, 0x6c, 0x39, + 0x32, 0x7a, 0x78, 0x6c, 0x66, 0x67, 0x43, 0x4f, 0x7a, 0x4a, 0x57, 0x67, + 0x6a, 0x6c, 0x36, 0x57, 0x37, 0x30, 0x76, 0x69, 0x52, 0x75, 0x2f, 0x6f, + 0x62, 0x54, 0x6f, 0x2f, 0x33, 0x2b, 0x4e, 0x6a, 0x4e, 0x38, 0x44, 0x38, + 0x57, 0x42, 0x4f, 0x57, 0x42, 0x46, 0x4d, 0x36, 0x36, 0x4d, 0x2f, 0x45, + 0x43, 0x75, 0x44, 0x6d, 0x67, 0x46, 0x7a, 0x32, 0x5a, 0x52, 0x74, 0x0a, + 0x68, 0x41, 0x41, 0x6e, 0x5a, 0x71, 0x7a, 0x77, 0x63, 0x45, 0x41, 0x4a, + 0x51, 0x70, 0x4b, 0x74, 0x54, 0x35, 0x4d, 0x4e, 0x59, 0x51, 0x6c, 0x52, + 0x4a, 0x4e, 0x69, 0x53, 0x31, 0x51, 0x75, 0x55, 0x59, 0x62, 0x4b, 0x48, + 0x73, 0x75, 0x33, 0x2f, 0x6d, 0x6a, 0x58, 0x2f, 0x68, 0x56, 0x54, 0x4b, + 0x37, 0x55, 0x52, 0x44, 0x72, 0x42, 0x73, 0x38, 0x46, 0x6d, 0x74, 0x49, + 0x53, 0x67, 0x6f, 0x63, 0x0a, 0x51, 0x49, 0x67, 0x66, 0x6b, 0x73, 0x49, + 0x4c, 0x41, 0x41, 0x58, 0x2f, 0x38, 0x73, 0x67, 0x43, 0x53, 0x71, 0x53, + 0x71, 0x71, 0x63, 0x79, 0x5a, 0x6c, 0x70, 0x77, 0x76, 0x57, 0x4f, 0x42, + 0x39, 0x34, 0x62, 0x36, 0x37, 0x42, 0x39, 0x78, 0x66, 0x42, 0x48, 0x4a, + 0x63, 0x4d, 0x54, 0x54, 0x44, 0x37, 0x46, 0x38, 0x74, 0x34, 0x44, 0x31, + 0x6b, 0x6b, 0x43, 0x4c, 0x6d, 0x30, 0x65, 0x79, 0x34, 0x0a, 0x4c, 0x74, + 0x31, 0x5a, 0x72, 0x74, 0x6d, 0x68, 0x4e, 0x37, 0x39, 0x55, 0x4e, 0x64, + 0x78, 0x7a, 0x4d, 0x6b, 0x2b, 0x4d, 0x42, 0x42, 0x34, 0x7a, 0x73, 0x73, + 0x6c, 0x47, 0x38, 0x64, 0x68, 0x63, 0x79, 0x46, 0x56, 0x51, 0x79, 0x57, + 0x69, 0x39, 0x71, 0x4c, 0x6f, 0x32, 0x43, 0x51, 0x49, 0x44, 0x41, 0x51, + 0x41, 0x42, 0x6f, 0x32, 0x4d, 0x77, 0x59, 0x54, 0x41, 0x50, 0x42, 0x67, + 0x4e, 0x56, 0x0a, 0x48, 0x52, 0x4d, 0x42, 0x41, 0x66, 0x38, 0x45, 0x42, + 0x54, 0x41, 0x44, 0x41, 0x51, 0x48, 0x2f, 0x4d, 0x42, 0x30, 0x47, 0x41, + 0x31, 0x55, 0x64, 0x44, 0x67, 0x51, 0x57, 0x42, 0x42, 0x52, 0x32, 0x38, + 0x31, 0x58, 0x68, 0x2b, 0x71, 0x51, 0x32, 0x2b, 0x2f, 0x43, 0x66, 0x58, + 0x47, 0x4a, 0x78, 0x37, 0x54, 0x7a, 0x30, 0x52, 0x7a, 0x67, 0x51, 0x4b, + 0x7a, 0x41, 0x66, 0x42, 0x67, 0x4e, 0x56, 0x0a, 0x48, 0x53, 0x4d, 0x45, + 0x47, 0x44, 0x41, 0x57, 0x67, 0x42, 0x52, 0x32, 0x38, 0x31, 0x58, 0x68, + 0x2b, 0x71, 0x51, 0x32, 0x2b, 0x2f, 0x43, 0x66, 0x58, 0x47, 0x4a, 0x78, + 0x37, 0x54, 0x7a, 0x30, 0x52, 0x7a, 0x67, 0x51, 0x4b, 0x7a, 0x41, 0x4f, + 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x38, 0x42, 0x41, 0x66, 0x38, 0x45, + 0x42, 0x41, 0x4d, 0x43, 0x41, 0x59, 0x59, 0x77, 0x44, 0x51, 0x59, 0x4a, + 0x0a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, + 0x46, 0x42, 0x51, 0x41, 0x44, 0x67, 0x67, 0x49, 0x42, 0x41, 0x47, 0x62, + 0x42, 0x78, 0x69, 0x50, 0x7a, 0x32, 0x65, 0x41, 0x75, 0x62, 0x6c, 0x2f, + 0x6f, 0x7a, 0x36, 0x36, 0x77, 0x73, 0x43, 0x56, 0x4e, 0x4b, 0x2f, 0x67, + 0x37, 0x57, 0x4a, 0x74, 0x41, 0x4a, 0x44, 0x64, 0x61, 0x79, 0x36, 0x73, + 0x57, 0x53, 0x66, 0x2b, 0x7a, 0x0a, 0x64, 0x58, 0x6b, 0x7a, 0x6f, 0x53, + 0x39, 0x74, 0x63, 0x42, 0x63, 0x30, 0x6b, 0x66, 0x35, 0x6e, 0x66, 0x6f, + 0x2f, 0x73, 0x6d, 0x2b, 0x56, 0x65, 0x67, 0x71, 0x6c, 0x56, 0x48, 0x79, + 0x2f, 0x63, 0x31, 0x46, 0x45, 0x48, 0x45, 0x76, 0x36, 0x73, 0x46, 0x6a, + 0x34, 0x73, 0x4e, 0x63, 0x5a, 0x6a, 0x2f, 0x4e, 0x77, 0x51, 0x36, 0x77, + 0x32, 0x6a, 0x71, 0x74, 0x42, 0x38, 0x7a, 0x4e, 0x48, 0x51, 0x0a, 0x4c, + 0x31, 0x45, 0x75, 0x78, 0x42, 0x52, 0x61, 0x33, 0x75, 0x67, 0x5a, 0x34, + 0x54, 0x37, 0x47, 0x7a, 0x4b, 0x51, 0x70, 0x35, 0x79, 0x36, 0x45, 0x71, + 0x67, 0x59, 0x77, 0x65, 0x48, 0x5a, 0x55, 0x63, 0x79, 0x69, 0x59, 0x57, + 0x54, 0x6a, 0x67, 0x41, 0x41, 0x31, 0x69, 0x30, 0x30, 0x4a, 0x39, 0x49, + 0x5a, 0x2b, 0x75, 0x50, 0x54, 0x71, 0x4d, 0x31, 0x66, 0x70, 0x33, 0x44, + 0x52, 0x67, 0x72, 0x0a, 0x46, 0x67, 0x35, 0x66, 0x4e, 0x75, 0x48, 0x38, + 0x4b, 0x72, 0x55, 0x77, 0x4a, 0x4d, 0x2f, 0x67, 0x59, 0x77, 0x78, 0x37, + 0x57, 0x42, 0x72, 0x2b, 0x6d, 0x62, 0x70, 0x43, 0x45, 0x72, 0x47, 0x52, + 0x39, 0x48, 0x78, 0x6f, 0x34, 0x73, 0x6a, 0x6f, 0x72, 0x79, 0x7a, 0x71, + 0x79, 0x58, 0x36, 0x75, 0x75, 0x79, 0x6f, 0x39, 0x44, 0x52, 0x58, 0x63, + 0x4e, 0x4a, 0x57, 0x32, 0x47, 0x48, 0x53, 0x6f, 0x0a, 0x61, 0x67, 0x2f, + 0x48, 0x74, 0x50, 0x51, 0x54, 0x78, 0x4f, 0x52, 0x62, 0x37, 0x51, 0x72, + 0x53, 0x70, 0x4a, 0x64, 0x4d, 0x4b, 0x75, 0x30, 0x76, 0x62, 0x42, 0x4b, + 0x4a, 0x50, 0x66, 0x45, 0x6e, 0x63, 0x4b, 0x70, 0x71, 0x41, 0x31, 0x49, + 0x68, 0x6e, 0x30, 0x43, 0x6f, 0x5a, 0x31, 0x44, 0x79, 0x38, 0x31, 0x6f, + 0x66, 0x33, 0x39, 0x38, 0x6a, 0x39, 0x74, 0x78, 0x34, 0x54, 0x75, 0x61, + 0x59, 0x0a, 0x54, 0x31, 0x55, 0x36, 0x55, 0x2b, 0x50, 0x76, 0x38, 0x76, + 0x53, 0x66, 0x78, 0x33, 0x7a, 0x59, 0x57, 0x4b, 0x38, 0x70, 0x49, 0x70, + 0x65, 0x34, 0x34, 0x4c, 0x32, 0x52, 0x4c, 0x72, 0x42, 0x32, 0x37, 0x46, + 0x63, 0x52, 0x7a, 0x2b, 0x38, 0x70, 0x52, 0x50, 0x50, 0x70, 0x68, 0x58, + 0x70, 0x67, 0x59, 0x2b, 0x52, 0x64, 0x4d, 0x34, 0x6b, 0x58, 0x32, 0x54, + 0x47, 0x71, 0x32, 0x74, 0x62, 0x7a, 0x0a, 0x47, 0x44, 0x56, 0x79, 0x7a, + 0x34, 0x63, 0x72, 0x4c, 0x32, 0x4d, 0x6a, 0x68, 0x46, 0x32, 0x45, 0x6a, + 0x44, 0x39, 0x58, 0x6f, 0x49, 0x6a, 0x38, 0x6d, 0x5a, 0x45, 0x6f, 0x4a, + 0x6d, 0x6d, 0x5a, 0x31, 0x49, 0x2b, 0x58, 0x52, 0x4c, 0x36, 0x4f, 0x31, + 0x55, 0x69, 0x78, 0x70, 0x43, 0x67, 0x70, 0x38, 0x52, 0x57, 0x30, 0x34, + 0x65, 0x57, 0x65, 0x33, 0x66, 0x69, 0x50, 0x70, 0x6d, 0x38, 0x6d, 0x0a, + 0x31, 0x77, 0x6b, 0x38, 0x4f, 0x68, 0x77, 0x52, 0x44, 0x71, 0x5a, 0x73, + 0x4e, 0x2f, 0x65, 0x74, 0x52, 0x49, 0x63, 0x73, 0x4b, 0x4d, 0x66, 0x59, + 0x64, 0x49, 0x4b, 0x7a, 0x30, 0x47, 0x39, 0x4b, 0x56, 0x37, 0x73, 0x31, + 0x4b, 0x53, 0x65, 0x67, 0x69, 0x2b, 0x67, 0x68, 0x70, 0x34, 0x64, 0x6b, + 0x4e, 0x6c, 0x33, 0x4d, 0x32, 0x42, 0x61, 0x73, 0x78, 0x37, 0x49, 0x6e, + 0x51, 0x4a, 0x4a, 0x56, 0x0a, 0x4f, 0x43, 0x69, 0x4e, 0x55, 0x57, 0x37, + 0x64, 0x46, 0x47, 0x64, 0x54, 0x62, 0x48, 0x46, 0x63, 0x4a, 0x6f, 0x52, + 0x4e, 0x64, 0x56, 0x71, 0x32, 0x66, 0x6d, 0x42, 0x57, 0x71, 0x55, 0x32, + 0x74, 0x2b, 0x35, 0x73, 0x65, 0x6c, 0x2f, 0x4d, 0x4e, 0x32, 0x64, 0x4b, + 0x58, 0x56, 0x48, 0x66, 0x61, 0x50, 0x52, 0x4b, 0x33, 0x34, 0x42, 0x37, + 0x76, 0x43, 0x41, 0x61, 0x73, 0x2b, 0x59, 0x57, 0x48, 0x0a, 0x36, 0x61, + 0x4c, 0x63, 0x72, 0x33, 0x34, 0x59, 0x45, 0x6f, 0x50, 0x39, 0x56, 0x68, + 0x64, 0x42, 0x4c, 0x74, 0x55, 0x70, 0x67, 0x6e, 0x32, 0x5a, 0x39, 0x44, + 0x48, 0x32, 0x63, 0x61, 0x6e, 0x50, 0x4c, 0x41, 0x45, 0x6e, 0x70, 0x51, + 0x57, 0x35, 0x71, 0x72, 0x4a, 0x49, 0x54, 0x69, 0x72, 0x76, 0x6e, 0x35, + 0x4e, 0x53, 0x55, 0x5a, 0x55, 0x38, 0x55, 0x6e, 0x4f, 0x4f, 0x56, 0x6b, + 0x77, 0x58, 0x0a, 0x51, 0x4d, 0x41, 0x4a, 0x4b, 0x4f, 0x53, 0x4c, 0x61, + 0x6b, 0x68, 0x54, 0x32, 0x2b, 0x7a, 0x4e, 0x56, 0x56, 0x58, 0x78, 0x78, + 0x76, 0x6a, 0x70, 0x6f, 0x69, 0x78, 0x4d, 0x70, 0x74, 0x45, 0x6d, 0x58, + 0x33, 0x36, 0x76, 0x57, 0x6b, 0x7a, 0x61, 0x48, 0x36, 0x62, 0x79, 0x48, + 0x43, 0x78, 0x2b, 0x72, 0x67, 0x49, 0x57, 0x30, 0x6c, 0x62, 0x51, 0x4c, + 0x31, 0x64, 0x54, 0x52, 0x2b, 0x69, 0x53, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, + 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, + 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, + 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x20, 0x4f, 0x6e, 0x6c, 0x69, + 0x6e, 0x65, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x31, 0x20, 0x4f, 0x3d, + 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x20, 0x4f, 0x6e, 0x6c, 0x69, + 0x6e, 0x65, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x53, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x41, 0x6d, + 0x65, 0x72, 0x69, 0x63, 0x61, 0x20, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, + 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x31, 0x20, 0x4f, 0x3d, 0x41, 0x6d, + 0x65, 0x72, 0x69, 0x63, 0x61, 0x20, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, + 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x20, + 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, + 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, + 0x31, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, + 0x20, 0x31, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, + 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x31, 0x34, + 0x3a, 0x66, 0x31, 0x3a, 0x30, 0x38, 0x3a, 0x61, 0x64, 0x3a, 0x39, 0x64, + 0x3a, 0x66, 0x61, 0x3a, 0x36, 0x34, 0x3a, 0x65, 0x32, 0x3a, 0x38, 0x39, + 0x3a, 0x65, 0x37, 0x3a, 0x31, 0x63, 0x3a, 0x63, 0x66, 0x3a, 0x61, 0x38, + 0x3a, 0x61, 0x64, 0x3a, 0x37, 0x64, 0x3a, 0x35, 0x65, 0x0a, 0x23, 0x20, + 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, + 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x33, 0x39, 0x3a, 0x32, 0x31, 0x3a, + 0x63, 0x31, 0x3a, 0x31, 0x35, 0x3a, 0x63, 0x31, 0x3a, 0x35, 0x64, 0x3a, + 0x30, 0x65, 0x3a, 0x63, 0x61, 0x3a, 0x35, 0x63, 0x3a, 0x63, 0x62, 0x3a, + 0x35, 0x62, 0x3a, 0x63, 0x34, 0x3a, 0x66, 0x30, 0x3a, 0x37, 0x64, 0x3a, + 0x32, 0x31, 0x3a, 0x64, 0x38, 0x3a, 0x30, 0x35, 0x3a, 0x30, 0x62, 0x3a, + 0x35, 0x36, 0x3a, 0x36, 0x61, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, + 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x3a, 0x20, 0x37, 0x37, 0x3a, 0x34, 0x30, 0x3a, 0x37, 0x33, + 0x3a, 0x31, 0x32, 0x3a, 0x63, 0x36, 0x3a, 0x33, 0x61, 0x3a, 0x31, 0x35, + 0x3a, 0x33, 0x64, 0x3a, 0x35, 0x62, 0x3a, 0x63, 0x30, 0x3a, 0x30, 0x62, + 0x3a, 0x34, 0x65, 0x3a, 0x35, 0x31, 0x3a, 0x37, 0x35, 0x3a, 0x39, 0x63, + 0x3a, 0x64, 0x66, 0x3a, 0x64, 0x61, 0x3a, 0x63, 0x32, 0x3a, 0x33, 0x37, + 0x3a, 0x64, 0x63, 0x3a, 0x32, 0x61, 0x3a, 0x33, 0x33, 0x3a, 0x62, 0x36, + 0x3a, 0x37, 0x39, 0x3a, 0x34, 0x36, 0x3a, 0x65, 0x39, 0x3a, 0x38, 0x65, + 0x3a, 0x39, 0x62, 0x3a, 0x66, 0x61, 0x3a, 0x36, 0x38, 0x3a, 0x30, 0x61, + 0x3a, 0x65, 0x33, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, + 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, + 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x44, + 0x70, 0x44, 0x43, 0x43, 0x41, 0x6f, 0x79, 0x67, 0x41, 0x77, 0x49, 0x42, + 0x41, 0x67, 0x49, 0x42, 0x41, 0x54, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, + 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, + 0x41, 0x44, 0x42, 0x6a, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, + 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, 0x63, + 0x0a, 0x4d, 0x42, 0x6f, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, + 0x54, 0x51, 0x57, 0x31, 0x6c, 0x63, 0x6d, 0x6c, 0x6a, 0x59, 0x53, 0x42, + 0x50, 0x62, 0x6d, 0x78, 0x70, 0x62, 0x6d, 0x55, 0x67, 0x53, 0x57, 0x35, + 0x6a, 0x4c, 0x6a, 0x45, 0x32, 0x4d, 0x44, 0x51, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x41, 0x78, 0x4d, 0x74, 0x51, 0x57, 0x31, 0x6c, 0x63, 0x6d, 0x6c, + 0x6a, 0x59, 0x53, 0x42, 0x50, 0x0a, 0x62, 0x6d, 0x78, 0x70, 0x62, 0x6d, + 0x55, 0x67, 0x55, 0x6d, 0x39, 0x76, 0x64, 0x43, 0x42, 0x44, 0x5a, 0x58, + 0x4a, 0x30, 0x61, 0x57, 0x5a, 0x70, 0x59, 0x32, 0x46, 0x30, 0x61, 0x57, + 0x39, 0x75, 0x49, 0x45, 0x46, 0x31, 0x64, 0x47, 0x68, 0x76, 0x63, 0x6d, + 0x6c, 0x30, 0x65, 0x53, 0x41, 0x78, 0x4d, 0x42, 0x34, 0x58, 0x44, 0x54, + 0x41, 0x79, 0x4d, 0x44, 0x55, 0x79, 0x4f, 0x44, 0x41, 0x32, 0x0a, 0x4d, + 0x44, 0x41, 0x77, 0x4d, 0x46, 0x6f, 0x58, 0x44, 0x54, 0x4d, 0x33, 0x4d, + 0x54, 0x45, 0x78, 0x4f, 0x54, 0x49, 0x77, 0x4e, 0x44, 0x4d, 0x77, 0x4d, + 0x46, 0x6f, 0x77, 0x59, 0x7a, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, + 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, 0x48, + 0x44, 0x41, 0x61, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, 0x45, + 0x30, 0x46, 0x74, 0x0a, 0x5a, 0x58, 0x4a, 0x70, 0x59, 0x32, 0x45, 0x67, + 0x54, 0x32, 0x35, 0x73, 0x61, 0x57, 0x35, 0x6c, 0x49, 0x45, 0x6c, 0x75, + 0x59, 0x79, 0x34, 0x78, 0x4e, 0x6a, 0x41, 0x30, 0x42, 0x67, 0x4e, 0x56, + 0x42, 0x41, 0x4d, 0x54, 0x4c, 0x55, 0x46, 0x74, 0x5a, 0x58, 0x4a, 0x70, + 0x59, 0x32, 0x45, 0x67, 0x54, 0x32, 0x35, 0x73, 0x61, 0x57, 0x35, 0x6c, + 0x49, 0x46, 0x4a, 0x76, 0x62, 0x33, 0x51, 0x67, 0x0a, 0x51, 0x32, 0x56, + 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, 0x6c, + 0x76, 0x62, 0x69, 0x42, 0x42, 0x64, 0x58, 0x52, 0x6f, 0x62, 0x33, 0x4a, + 0x70, 0x64, 0x48, 0x6b, 0x67, 0x4d, 0x54, 0x43, 0x43, 0x41, 0x53, 0x49, + 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, + 0x4e, 0x41, 0x51, 0x45, 0x42, 0x42, 0x51, 0x41, 0x44, 0x67, 0x67, 0x45, + 0x50, 0x0a, 0x41, 0x44, 0x43, 0x43, 0x41, 0x51, 0x6f, 0x43, 0x67, 0x67, + 0x45, 0x42, 0x41, 0x4b, 0x67, 0x76, 0x36, 0x4b, 0x52, 0x70, 0x42, 0x67, + 0x4e, 0x48, 0x77, 0x2b, 0x6b, 0x71, 0x6d, 0x50, 0x38, 0x5a, 0x6f, 0x6e, + 0x43, 0x61, 0x78, 0x6c, 0x43, 0x79, 0x66, 0x71, 0x58, 0x66, 0x61, 0x45, + 0x30, 0x62, 0x66, 0x41, 0x2b, 0x32, 0x6c, 0x32, 0x68, 0x39, 0x4c, 0x61, + 0x61, 0x4c, 0x6c, 0x2b, 0x6c, 0x6b, 0x0a, 0x68, 0x73, 0x6d, 0x6a, 0x37, + 0x36, 0x43, 0x47, 0x76, 0x32, 0x42, 0x6c, 0x6e, 0x45, 0x74, 0x55, 0x69, + 0x4d, 0x4a, 0x49, 0x78, 0x55, 0x6f, 0x35, 0x76, 0x78, 0x54, 0x6a, 0x57, + 0x56, 0x58, 0x6c, 0x47, 0x62, 0x52, 0x30, 0x79, 0x4c, 0x51, 0x46, 0x4f, + 0x56, 0x77, 0x57, 0x70, 0x65, 0x4b, 0x56, 0x42, 0x65, 0x41, 0x53, 0x72, + 0x6c, 0x6d, 0x4c, 0x6f, 0x6a, 0x4e, 0x6f, 0x57, 0x42, 0x79, 0x6d, 0x0a, + 0x31, 0x42, 0x57, 0x33, 0x32, 0x4a, 0x2f, 0x58, 0x33, 0x48, 0x47, 0x72, + 0x66, 0x70, 0x71, 0x2f, 0x6d, 0x34, 0x34, 0x7a, 0x44, 0x79, 0x4c, 0x39, + 0x48, 0x79, 0x37, 0x6e, 0x42, 0x7a, 0x62, 0x76, 0x59, 0x6a, 0x6e, 0x46, + 0x33, 0x63, 0x75, 0x36, 0x4a, 0x52, 0x51, 0x6a, 0x33, 0x67, 0x7a, 0x47, + 0x50, 0x54, 0x7a, 0x4f, 0x67, 0x67, 0x6a, 0x6d, 0x5a, 0x6a, 0x37, 0x61, + 0x55, 0x54, 0x73, 0x57, 0x0a, 0x4f, 0x71, 0x4d, 0x46, 0x66, 0x36, 0x44, + 0x63, 0x68, 0x39, 0x57, 0x63, 0x2f, 0x48, 0x4b, 0x70, 0x6f, 0x48, 0x31, + 0x34, 0x35, 0x4c, 0x63, 0x78, 0x56, 0x52, 0x35, 0x6c, 0x75, 0x39, 0x52, + 0x68, 0x73, 0x43, 0x46, 0x67, 0x37, 0x52, 0x41, 0x79, 0x63, 0x73, 0x57, + 0x53, 0x4a, 0x52, 0x37, 0x34, 0x6b, 0x45, 0x6f, 0x59, 0x65, 0x45, 0x66, + 0x66, 0x66, 0x6a, 0x41, 0x33, 0x50, 0x6c, 0x41, 0x62, 0x0a, 0x32, 0x78, + 0x7a, 0x54, 0x61, 0x35, 0x71, 0x47, 0x55, 0x77, 0x65, 0x77, 0x37, 0x36, + 0x77, 0x47, 0x65, 0x50, 0x69, 0x45, 0x6d, 0x66, 0x34, 0x68, 0x6a, 0x55, + 0x79, 0x41, 0x74, 0x67, 0x79, 0x43, 0x39, 0x6d, 0x5a, 0x77, 0x65, 0x52, + 0x72, 0x54, 0x54, 0x36, 0x50, 0x50, 0x38, 0x63, 0x39, 0x47, 0x73, 0x45, + 0x73, 0x50, 0x50, 0x74, 0x32, 0x49, 0x59, 0x72, 0x69, 0x4d, 0x71, 0x51, + 0x6b, 0x6f, 0x0a, 0x4f, 0x33, 0x72, 0x48, 0x6c, 0x2b, 0x45, 0x65, 0x35, + 0x66, 0x53, 0x66, 0x77, 0x4d, 0x43, 0x75, 0x4a, 0x4b, 0x44, 0x49, 0x6f, + 0x64, 0x6b, 0x50, 0x31, 0x6e, 0x73, 0x6d, 0x67, 0x6d, 0x6b, 0x79, 0x50, + 0x61, 0x63, 0x43, 0x41, 0x77, 0x45, 0x41, 0x41, 0x61, 0x4e, 0x6a, 0x4d, + 0x47, 0x45, 0x77, 0x44, 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, 0x54, 0x41, + 0x51, 0x48, 0x2f, 0x42, 0x41, 0x55, 0x77, 0x0a, 0x41, 0x77, 0x45, 0x42, + 0x2f, 0x7a, 0x41, 0x64, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x34, 0x45, + 0x46, 0x67, 0x51, 0x55, 0x41, 0x4b, 0x33, 0x5a, 0x6f, 0x2f, 0x5a, 0x35, + 0x39, 0x6d, 0x35, 0x30, 0x71, 0x58, 0x38, 0x7a, 0x50, 0x59, 0x45, 0x58, + 0x31, 0x30, 0x7a, 0x50, 0x4d, 0x39, 0x34, 0x77, 0x48, 0x77, 0x59, 0x44, + 0x56, 0x52, 0x30, 0x6a, 0x42, 0x42, 0x67, 0x77, 0x46, 0x6f, 0x41, 0x55, + 0x0a, 0x41, 0x4b, 0x33, 0x5a, 0x6f, 0x2f, 0x5a, 0x35, 0x39, 0x6d, 0x35, + 0x30, 0x71, 0x58, 0x38, 0x7a, 0x50, 0x59, 0x45, 0x58, 0x31, 0x30, 0x7a, + 0x50, 0x4d, 0x39, 0x34, 0x77, 0x44, 0x67, 0x59, 0x44, 0x56, 0x52, 0x30, + 0x50, 0x41, 0x51, 0x48, 0x2f, 0x42, 0x41, 0x51, 0x44, 0x41, 0x67, 0x47, + 0x47, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, + 0x33, 0x44, 0x51, 0x45, 0x42, 0x0a, 0x42, 0x51, 0x55, 0x41, 0x41, 0x34, + 0x49, 0x42, 0x41, 0x51, 0x42, 0x38, 0x69, 0x74, 0x45, 0x66, 0x47, 0x44, + 0x65, 0x43, 0x34, 0x4c, 0x69, 0x77, 0x6f, 0x2b, 0x31, 0x57, 0x6c, 0x63, + 0x68, 0x69, 0x59, 0x5a, 0x77, 0x46, 0x6f, 0x73, 0x33, 0x43, 0x59, 0x69, + 0x5a, 0x68, 0x7a, 0x52, 0x41, 0x57, 0x31, 0x38, 0x79, 0x30, 0x5a, 0x54, + 0x54, 0x51, 0x45, 0x59, 0x71, 0x74, 0x71, 0x4b, 0x6b, 0x46, 0x0a, 0x5a, + 0x75, 0x39, 0x30, 0x38, 0x32, 0x31, 0x66, 0x6e, 0x5a, 0x6d, 0x76, 0x39, + 0x6f, 0x76, 0x37, 0x36, 0x31, 0x4b, 0x79, 0x42, 0x5a, 0x69, 0x69, 0x62, + 0x79, 0x72, 0x46, 0x56, 0x4c, 0x30, 0x6c, 0x76, 0x56, 0x2b, 0x75, 0x79, + 0x49, 0x62, 0x71, 0x52, 0x69, 0x7a, 0x42, 0x73, 0x37, 0x33, 0x42, 0x36, + 0x55, 0x6c, 0x77, 0x47, 0x42, 0x61, 0x58, 0x43, 0x42, 0x4f, 0x4d, 0x49, + 0x4f, 0x41, 0x62, 0x0a, 0x4c, 0x6a, 0x70, 0x48, 0x79, 0x78, 0x37, 0x6b, + 0x41, 0x44, 0x43, 0x56, 0x57, 0x2f, 0x52, 0x46, 0x6f, 0x38, 0x41, 0x61, + 0x73, 0x41, 0x46, 0x4f, 0x71, 0x37, 0x33, 0x41, 0x49, 0x32, 0x35, 0x6a, + 0x50, 0x34, 0x42, 0x4b, 0x78, 0x51, 0x66, 0x74, 0x33, 0x4f, 0x4a, 0x76, + 0x78, 0x38, 0x46, 0x69, 0x38, 0x65, 0x4e, 0x79, 0x31, 0x67, 0x54, 0x49, + 0x64, 0x47, 0x63, 0x4c, 0x2b, 0x6f, 0x69, 0x72, 0x0a, 0x6f, 0x51, 0x48, + 0x49, 0x62, 0x2f, 0x41, 0x55, 0x72, 0x39, 0x4b, 0x5a, 0x7a, 0x56, 0x47, + 0x54, 0x66, 0x75, 0x30, 0x75, 0x4f, 0x4d, 0x65, 0x39, 0x7a, 0x6b, 0x5a, + 0x51, 0x50, 0x58, 0x4c, 0x6a, 0x65, 0x53, 0x57, 0x64, 0x6d, 0x34, 0x67, + 0x72, 0x45, 0x43, 0x44, 0x64, 0x70, 0x62, 0x67, 0x79, 0x6e, 0x34, 0x33, + 0x67, 0x4b, 0x64, 0x38, 0x68, 0x64, 0x49, 0x61, 0x43, 0x32, 0x79, 0x2b, + 0x43, 0x0a, 0x4d, 0x4d, 0x62, 0x48, 0x4e, 0x59, 0x61, 0x7a, 0x2b, 0x5a, + 0x5a, 0x66, 0x52, 0x74, 0x73, 0x4d, 0x52, 0x66, 0x33, 0x7a, 0x55, 0x4d, + 0x4e, 0x76, 0x78, 0x73, 0x4e, 0x49, 0x72, 0x55, 0x61, 0x6d, 0x34, 0x53, + 0x64, 0x48, 0x43, 0x68, 0x30, 0x4f, 0x6d, 0x37, 0x62, 0x43, 0x64, 0x33, + 0x39, 0x6a, 0x38, 0x75, 0x42, 0x39, 0x47, 0x72, 0x37, 0x38, 0x34, 0x4e, + 0x2f, 0x58, 0x78, 0x36, 0x64, 0x73, 0x0a, 0x73, 0x50, 0x6d, 0x75, 0x75, + 0x6a, 0x7a, 0x39, 0x64, 0x4c, 0x51, 0x52, 0x36, 0x46, 0x67, 0x4e, 0x67, + 0x4c, 0x7a, 0x54, 0x71, 0x49, 0x41, 0x36, 0x6d, 0x65, 0x31, 0x31, 0x7a, + 0x45, 0x5a, 0x37, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, + 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, + 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x41, 0x6d, 0x65, 0x72, + 0x69, 0x63, 0x61, 0x20, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x52, + 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x74, 0x79, 0x20, 0x32, 0x20, 0x4f, 0x3d, 0x41, 0x6d, 0x65, 0x72, + 0x69, 0x63, 0x61, 0x20, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x49, + 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, + 0x61, 0x20, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x52, 0x6f, 0x6f, + 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, + 0x79, 0x20, 0x32, 0x20, 0x4f, 0x3d, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, + 0x61, 0x20, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x49, 0x6e, 0x63, + 0x2e, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, + 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x20, 0x4f, 0x6e, 0x6c, 0x69, + 0x6e, 0x65, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x32, 0x22, 0x0a, 0x23, + 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x31, 0x0a, 0x23, + 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, + 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x64, 0x36, 0x3a, 0x65, 0x64, 0x3a, + 0x33, 0x63, 0x3a, 0x63, 0x61, 0x3a, 0x65, 0x32, 0x3a, 0x36, 0x36, 0x3a, + 0x30, 0x66, 0x3a, 0x61, 0x66, 0x3a, 0x31, 0x30, 0x3a, 0x34, 0x33, 0x3a, + 0x30, 0x64, 0x3a, 0x37, 0x37, 0x3a, 0x39, 0x62, 0x3a, 0x30, 0x34, 0x3a, + 0x30, 0x39, 0x3a, 0x62, 0x66, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, + 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, + 0x3a, 0x20, 0x38, 0x35, 0x3a, 0x62, 0x35, 0x3a, 0x66, 0x66, 0x3a, 0x36, + 0x37, 0x3a, 0x39, 0x62, 0x3a, 0x30, 0x63, 0x3a, 0x37, 0x39, 0x3a, 0x39, + 0x36, 0x3a, 0x31, 0x66, 0x3a, 0x63, 0x38, 0x3a, 0x36, 0x65, 0x3a, 0x34, + 0x34, 0x3a, 0x32, 0x32, 0x3a, 0x30, 0x30, 0x3a, 0x34, 0x36, 0x3a, 0x31, + 0x33, 0x3a, 0x64, 0x62, 0x3a, 0x31, 0x37, 0x3a, 0x39, 0x32, 0x3a, 0x38, + 0x34, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x37, 0x64, 0x3a, 0x33, 0x62, 0x3a, 0x34, 0x36, 0x3a, 0x35, 0x61, 0x3a, + 0x36, 0x30, 0x3a, 0x31, 0x34, 0x3a, 0x65, 0x35, 0x3a, 0x32, 0x36, 0x3a, + 0x63, 0x30, 0x3a, 0x61, 0x66, 0x3a, 0x66, 0x63, 0x3a, 0x65, 0x65, 0x3a, + 0x32, 0x31, 0x3a, 0x32, 0x37, 0x3a, 0x64, 0x32, 0x3a, 0x33, 0x31, 0x3a, + 0x31, 0x37, 0x3a, 0x32, 0x37, 0x3a, 0x61, 0x64, 0x3a, 0x38, 0x31, 0x3a, + 0x31, 0x63, 0x3a, 0x32, 0x36, 0x3a, 0x38, 0x34, 0x3a, 0x32, 0x64, 0x3a, + 0x30, 0x30, 0x3a, 0x36, 0x61, 0x3a, 0x66, 0x33, 0x3a, 0x37, 0x33, 0x3a, + 0x30, 0x36, 0x3a, 0x63, 0x63, 0x3a, 0x38, 0x30, 0x3a, 0x62, 0x64, 0x0a, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, + 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x46, 0x70, 0x44, 0x43, 0x43, + 0x41, 0x34, 0x79, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x42, + 0x41, 0x54, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, + 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x42, 0x6a, + 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, + 0x45, 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, 0x63, 0x0a, 0x4d, 0x42, 0x6f, + 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x54, 0x51, 0x57, 0x31, + 0x6c, 0x63, 0x6d, 0x6c, 0x6a, 0x59, 0x53, 0x42, 0x50, 0x62, 0x6d, 0x78, + 0x70, 0x62, 0x6d, 0x55, 0x67, 0x53, 0x57, 0x35, 0x6a, 0x4c, 0x6a, 0x45, + 0x32, 0x4d, 0x44, 0x51, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, + 0x74, 0x51, 0x57, 0x31, 0x6c, 0x63, 0x6d, 0x6c, 0x6a, 0x59, 0x53, 0x42, + 0x50, 0x0a, 0x62, 0x6d, 0x78, 0x70, 0x62, 0x6d, 0x55, 0x67, 0x55, 0x6d, + 0x39, 0x76, 0x64, 0x43, 0x42, 0x44, 0x5a, 0x58, 0x4a, 0x30, 0x61, 0x57, + 0x5a, 0x70, 0x59, 0x32, 0x46, 0x30, 0x61, 0x57, 0x39, 0x75, 0x49, 0x45, + 0x46, 0x31, 0x64, 0x47, 0x68, 0x76, 0x63, 0x6d, 0x6c, 0x30, 0x65, 0x53, + 0x41, 0x79, 0x4d, 0x42, 0x34, 0x58, 0x44, 0x54, 0x41, 0x79, 0x4d, 0x44, + 0x55, 0x79, 0x4f, 0x44, 0x41, 0x32, 0x0a, 0x4d, 0x44, 0x41, 0x77, 0x4d, + 0x46, 0x6f, 0x58, 0x44, 0x54, 0x4d, 0x33, 0x4d, 0x44, 0x6b, 0x79, 0x4f, + 0x54, 0x45, 0x30, 0x4d, 0x44, 0x67, 0x77, 0x4d, 0x46, 0x6f, 0x77, 0x59, + 0x7a, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, + 0x68, 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, 0x48, 0x44, 0x41, 0x61, 0x42, + 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, 0x45, 0x30, 0x46, 0x74, 0x0a, + 0x5a, 0x58, 0x4a, 0x70, 0x59, 0x32, 0x45, 0x67, 0x54, 0x32, 0x35, 0x73, + 0x61, 0x57, 0x35, 0x6c, 0x49, 0x45, 0x6c, 0x75, 0x59, 0x79, 0x34, 0x78, + 0x4e, 0x6a, 0x41, 0x30, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x4d, 0x54, + 0x4c, 0x55, 0x46, 0x74, 0x5a, 0x58, 0x4a, 0x70, 0x59, 0x32, 0x45, 0x67, + 0x54, 0x32, 0x35, 0x73, 0x61, 0x57, 0x35, 0x6c, 0x49, 0x46, 0x4a, 0x76, + 0x62, 0x33, 0x51, 0x67, 0x0a, 0x51, 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, + 0x6d, 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, 0x6c, 0x76, 0x62, 0x69, 0x42, + 0x42, 0x64, 0x58, 0x52, 0x6f, 0x62, 0x33, 0x4a, 0x70, 0x64, 0x48, 0x6b, + 0x67, 0x4d, 0x6a, 0x43, 0x43, 0x41, 0x69, 0x49, 0x77, 0x44, 0x51, 0x59, + 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, + 0x42, 0x42, 0x51, 0x41, 0x44, 0x67, 0x67, 0x49, 0x50, 0x0a, 0x41, 0x44, + 0x43, 0x43, 0x41, 0x67, 0x6f, 0x43, 0x67, 0x67, 0x49, 0x42, 0x41, 0x4d, + 0x78, 0x42, 0x52, 0x52, 0x33, 0x70, 0x50, 0x55, 0x30, 0x51, 0x39, 0x6f, + 0x79, 0x78, 0x51, 0x63, 0x6e, 0x67, 0x58, 0x73, 0x73, 0x4e, 0x74, 0x37, + 0x39, 0x48, 0x63, 0x39, 0x50, 0x77, 0x56, 0x55, 0x33, 0x64, 0x78, 0x67, + 0x7a, 0x36, 0x73, 0x57, 0x59, 0x46, 0x61, 0x73, 0x31, 0x34, 0x74, 0x4e, + 0x77, 0x43, 0x0a, 0x32, 0x30, 0x36, 0x42, 0x38, 0x39, 0x65, 0x6e, 0x66, + 0x48, 0x47, 0x38, 0x64, 0x57, 0x4f, 0x67, 0x58, 0x65, 0x4d, 0x48, 0x44, + 0x45, 0x6a, 0x73, 0x4a, 0x63, 0x51, 0x44, 0x49, 0x50, 0x54, 0x2f, 0x44, + 0x6a, 0x73, 0x53, 0x2f, 0x35, 0x75, 0x4e, 0x34, 0x63, 0x62, 0x56, 0x47, + 0x37, 0x52, 0x74, 0x49, 0x75, 0x4f, 0x78, 0x32, 0x33, 0x38, 0x68, 0x5a, + 0x4b, 0x2b, 0x47, 0x76, 0x46, 0x63, 0x69, 0x0a, 0x4b, 0x74, 0x5a, 0x48, + 0x67, 0x56, 0x64, 0x45, 0x67, 0x6c, 0x5a, 0x54, 0x76, 0x59, 0x59, 0x55, + 0x41, 0x51, 0x76, 0x38, 0x66, 0x33, 0x53, 0x6b, 0x57, 0x71, 0x37, 0x78, + 0x75, 0x68, 0x47, 0x31, 0x6d, 0x31, 0x68, 0x61, 0x67, 0x4c, 0x51, 0x33, + 0x65, 0x41, 0x6b, 0x7a, 0x66, 0x44, 0x4a, 0x48, 0x41, 0x31, 0x7a, 0x45, + 0x70, 0x59, 0x4e, 0x49, 0x39, 0x46, 0x64, 0x57, 0x62, 0x6f, 0x45, 0x32, + 0x0a, 0x4a, 0x78, 0x68, 0x50, 0x37, 0x4a, 0x73, 0x6f, 0x77, 0x74, 0x53, + 0x30, 0x31, 0x33, 0x77, 0x4d, 0x50, 0x67, 0x77, 0x72, 0x33, 0x38, 0x6f, + 0x45, 0x31, 0x38, 0x61, 0x4f, 0x36, 0x6c, 0x68, 0x4f, 0x71, 0x4b, 0x53, + 0x6c, 0x47, 0x42, 0x78, 0x73, 0x52, 0x5a, 0x69, 0x6a, 0x51, 0x64, 0x45, + 0x74, 0x30, 0x73, 0x64, 0x74, 0x6a, 0x52, 0x6e, 0x78, 0x72, 0x58, 0x6d, + 0x33, 0x67, 0x54, 0x2b, 0x39, 0x0a, 0x42, 0x6f, 0x49, 0x6e, 0x4c, 0x52, + 0x42, 0x59, 0x42, 0x62, 0x56, 0x34, 0x42, 0x62, 0x6b, 0x76, 0x32, 0x77, + 0x78, 0x72, 0x6b, 0x4a, 0x42, 0x2b, 0x46, 0x46, 0x6b, 0x34, 0x75, 0x35, + 0x51, 0x6b, 0x45, 0x2b, 0x58, 0x52, 0x6e, 0x52, 0x54, 0x66, 0x30, 0x34, + 0x4a, 0x4e, 0x52, 0x76, 0x43, 0x41, 0x4f, 0x56, 0x49, 0x79, 0x44, 0x2b, + 0x4f, 0x45, 0x73, 0x6e, 0x70, 0x44, 0x38, 0x6c, 0x37, 0x65, 0x0a, 0x58, + 0x7a, 0x38, 0x64, 0x33, 0x65, 0x4f, 0x79, 0x47, 0x36, 0x43, 0x68, 0x4b, + 0x69, 0x4d, 0x44, 0x62, 0x69, 0x34, 0x42, 0x46, 0x59, 0x64, 0x63, 0x70, + 0x6e, 0x56, 0x31, 0x78, 0x35, 0x64, 0x68, 0x76, 0x74, 0x36, 0x47, 0x33, + 0x4e, 0x52, 0x49, 0x32, 0x37, 0x30, 0x71, 0x76, 0x30, 0x70, 0x56, 0x32, + 0x75, 0x68, 0x39, 0x55, 0x50, 0x75, 0x30, 0x67, 0x42, 0x65, 0x34, 0x6c, + 0x4c, 0x38, 0x42, 0x0a, 0x50, 0x65, 0x72, 0x61, 0x75, 0x6e, 0x7a, 0x67, + 0x57, 0x47, 0x63, 0x58, 0x75, 0x56, 0x6a, 0x67, 0x69, 0x49, 0x5a, 0x47, + 0x5a, 0x32, 0x79, 0x64, 0x45, 0x45, 0x64, 0x59, 0x4d, 0x74, 0x41, 0x31, + 0x66, 0x48, 0x6b, 0x71, 0x6b, 0x4b, 0x4a, 0x61, 0x45, 0x42, 0x45, 0x6a, + 0x4e, 0x61, 0x30, 0x76, 0x7a, 0x4f, 0x52, 0x4b, 0x57, 0x36, 0x66, 0x49, + 0x4a, 0x2f, 0x4b, 0x44, 0x33, 0x6c, 0x36, 0x37, 0x0a, 0x58, 0x6e, 0x66, + 0x6e, 0x36, 0x4b, 0x56, 0x75, 0x59, 0x38, 0x49, 0x4e, 0x58, 0x57, 0x48, + 0x51, 0x6a, 0x4e, 0x4a, 0x73, 0x57, 0x69, 0x45, 0x4f, 0x79, 0x69, 0x69, + 0x6a, 0x7a, 0x69, 0x72, 0x70, 0x6c, 0x63, 0x64, 0x49, 0x7a, 0x35, 0x5a, + 0x76, 0x48, 0x5a, 0x49, 0x6c, 0x79, 0x4d, 0x62, 0x47, 0x77, 0x63, 0x45, + 0x4d, 0x42, 0x61, 0x77, 0x6d, 0x78, 0x4e, 0x4a, 0x31, 0x30, 0x75, 0x45, + 0x71, 0x0a, 0x5a, 0x38, 0x41, 0x39, 0x57, 0x36, 0x57, 0x61, 0x36, 0x38, + 0x39, 0x37, 0x47, 0x71, 0x69, 0x64, 0x46, 0x45, 0x58, 0x6c, 0x44, 0x36, + 0x43, 0x61, 0x5a, 0x64, 0x34, 0x76, 0x4b, 0x4c, 0x33, 0x4f, 0x62, 0x35, + 0x52, 0x6d, 0x67, 0x30, 0x67, 0x70, 0x32, 0x4f, 0x70, 0x6c, 0x6a, 0x4b, + 0x2b, 0x54, 0x32, 0x57, 0x53, 0x66, 0x56, 0x56, 0x63, 0x6d, 0x76, 0x32, + 0x2f, 0x4c, 0x4e, 0x7a, 0x47, 0x5a, 0x0a, 0x6f, 0x32, 0x43, 0x37, 0x48, + 0x4b, 0x32, 0x4a, 0x4e, 0x44, 0x4a, 0x69, 0x75, 0x45, 0x4d, 0x68, 0x42, + 0x6e, 0x49, 0x4d, 0x6f, 0x56, 0x78, 0x74, 0x52, 0x73, 0x58, 0x36, 0x4b, + 0x63, 0x38, 0x77, 0x33, 0x6f, 0x6e, 0x63, 0x63, 0x56, 0x76, 0x64, 0x74, + 0x6a, 0x63, 0x2b, 0x33, 0x31, 0x44, 0x31, 0x75, 0x41, 0x63, 0x6c, 0x4a, + 0x75, 0x57, 0x38, 0x74, 0x66, 0x34, 0x38, 0x41, 0x72, 0x4f, 0x33, 0x0a, + 0x2b, 0x4c, 0x35, 0x44, 0x77, 0x59, 0x63, 0x52, 0x6c, 0x4a, 0x34, 0x6a, + 0x62, 0x42, 0x65, 0x4b, 0x75, 0x49, 0x6f, 0x6e, 0x44, 0x46, 0x52, 0x48, + 0x38, 0x4b, 0x6d, 0x7a, 0x77, 0x49, 0x43, 0x4d, 0x6f, 0x43, 0x66, 0x72, + 0x48, 0x52, 0x6e, 0x6a, 0x42, 0x34, 0x35, 0x33, 0x63, 0x4d, 0x6f, 0x72, + 0x39, 0x48, 0x31, 0x32, 0x34, 0x48, 0x68, 0x6e, 0x41, 0x67, 0x4d, 0x42, + 0x41, 0x41, 0x47, 0x6a, 0x0a, 0x59, 0x7a, 0x42, 0x68, 0x4d, 0x41, 0x38, + 0x47, 0x41, 0x31, 0x55, 0x64, 0x45, 0x77, 0x45, 0x42, 0x2f, 0x77, 0x51, + 0x46, 0x4d, 0x41, 0x4d, 0x42, 0x41, 0x66, 0x38, 0x77, 0x48, 0x51, 0x59, + 0x44, 0x56, 0x52, 0x30, 0x4f, 0x42, 0x42, 0x59, 0x45, 0x46, 0x45, 0x31, + 0x46, 0x77, 0x57, 0x67, 0x34, 0x75, 0x33, 0x4f, 0x70, 0x61, 0x61, 0x45, + 0x67, 0x35, 0x2b, 0x33, 0x31, 0x49, 0x71, 0x45, 0x6a, 0x0a, 0x46, 0x4e, + 0x65, 0x65, 0x4d, 0x42, 0x38, 0x47, 0x41, 0x31, 0x55, 0x64, 0x49, 0x77, + 0x51, 0x59, 0x4d, 0x42, 0x61, 0x41, 0x46, 0x45, 0x31, 0x46, 0x77, 0x57, + 0x67, 0x34, 0x75, 0x33, 0x4f, 0x70, 0x61, 0x61, 0x45, 0x67, 0x35, 0x2b, + 0x33, 0x31, 0x49, 0x71, 0x45, 0x6a, 0x46, 0x4e, 0x65, 0x65, 0x4d, 0x41, + 0x34, 0x47, 0x41, 0x31, 0x55, 0x64, 0x44, 0x77, 0x45, 0x42, 0x2f, 0x77, + 0x51, 0x45, 0x0a, 0x41, 0x77, 0x49, 0x42, 0x68, 0x6a, 0x41, 0x4e, 0x42, + 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, + 0x51, 0x55, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x67, 0x45, 0x41, 0x5a, + 0x32, 0x73, 0x47, 0x75, 0x56, 0x39, 0x46, 0x4f, 0x79, 0x70, 0x4c, 0x4d, + 0x37, 0x50, 0x6d, 0x47, 0x32, 0x74, 0x5a, 0x54, 0x69, 0x4c, 0x4d, 0x75, + 0x62, 0x65, 0x6b, 0x4a, 0x63, 0x6d, 0x6e, 0x0a, 0x78, 0x50, 0x42, 0x55, + 0x6c, 0x67, 0x74, 0x6b, 0x38, 0x37, 0x46, 0x59, 0x54, 0x31, 0x35, 0x52, + 0x2f, 0x4c, 0x4b, 0x58, 0x65, 0x79, 0x64, 0x6c, 0x77, 0x75, 0x58, 0x4b, + 0x35, 0x77, 0x30, 0x4d, 0x4a, 0x58, 0x74, 0x69, 0x34, 0x2f, 0x71, 0x66, + 0x74, 0x49, 0x65, 0x33, 0x52, 0x55, 0x61, 0x76, 0x67, 0x36, 0x57, 0x58, + 0x53, 0x49, 0x79, 0x6c, 0x76, 0x66, 0x45, 0x57, 0x4b, 0x35, 0x74, 0x32, + 0x0a, 0x4c, 0x48, 0x6f, 0x31, 0x59, 0x47, 0x77, 0x52, 0x67, 0x4a, 0x66, + 0x4d, 0x71, 0x5a, 0x4a, 0x53, 0x35, 0x69, 0x76, 0x6d, 0x61, 0x65, 0x32, + 0x70, 0x2b, 0x44, 0x59, 0x74, 0x4c, 0x48, 0x65, 0x2f, 0x59, 0x55, 0x6a, + 0x52, 0x59, 0x77, 0x75, 0x35, 0x57, 0x31, 0x4c, 0x74, 0x47, 0x4c, 0x42, + 0x44, 0x51, 0x69, 0x4b, 0x6d, 0x73, 0x58, 0x65, 0x75, 0x33, 0x6d, 0x6e, + 0x46, 0x7a, 0x63, 0x63, 0x63, 0x0a, 0x6f, 0x62, 0x47, 0x6c, 0x48, 0x42, + 0x44, 0x37, 0x47, 0x4c, 0x34, 0x61, 0x63, 0x4e, 0x33, 0x42, 0x6b, 0x6b, + 0x75, 0x2b, 0x4b, 0x56, 0x71, 0x64, 0x50, 0x7a, 0x57, 0x2b, 0x35, 0x58, + 0x31, 0x52, 0x2b, 0x46, 0x58, 0x67, 0x4a, 0x58, 0x55, 0x6a, 0x68, 0x78, + 0x35, 0x63, 0x33, 0x4c, 0x71, 0x64, 0x73, 0x4b, 0x79, 0x7a, 0x61, 0x64, + 0x73, 0x58, 0x67, 0x38, 0x6e, 0x33, 0x33, 0x67, 0x79, 0x38, 0x0a, 0x43, + 0x4e, 0x79, 0x52, 0x6e, 0x71, 0x6a, 0x51, 0x31, 0x78, 0x55, 0x33, 0x63, + 0x36, 0x55, 0x31, 0x75, 0x50, 0x78, 0x2b, 0x78, 0x55, 0x52, 0x41, 0x42, + 0x73, 0x50, 0x72, 0x2b, 0x43, 0x4b, 0x41, 0x58, 0x45, 0x66, 0x4f, 0x41, + 0x75, 0x4d, 0x52, 0x6e, 0x30, 0x54, 0x2f, 0x2f, 0x5a, 0x6f, 0x79, 0x7a, + 0x48, 0x31, 0x6b, 0x55, 0x51, 0x37, 0x72, 0x56, 0x79, 0x5a, 0x32, 0x4f, + 0x75, 0x4d, 0x65, 0x0a, 0x49, 0x6a, 0x7a, 0x43, 0x70, 0x6a, 0x62, 0x64, + 0x47, 0x65, 0x2b, 0x6e, 0x2f, 0x42, 0x4c, 0x7a, 0x4a, 0x73, 0x42, 0x5a, + 0x4d, 0x59, 0x56, 0x4d, 0x6e, 0x4e, 0x6a, 0x50, 0x33, 0x36, 0x54, 0x4d, + 0x7a, 0x43, 0x6d, 0x54, 0x2f, 0x35, 0x52, 0x74, 0x64, 0x6c, 0x77, 0x54, + 0x43, 0x4a, 0x66, 0x79, 0x37, 0x61, 0x55, 0x4c, 0x54, 0x64, 0x33, 0x6f, + 0x79, 0x57, 0x67, 0x4f, 0x5a, 0x74, 0x4d, 0x41, 0x0a, 0x44, 0x6a, 0x4d, + 0x53, 0x57, 0x37, 0x79, 0x56, 0x35, 0x54, 0x4b, 0x51, 0x71, 0x4c, 0x50, + 0x47, 0x62, 0x49, 0x4f, 0x74, 0x64, 0x2b, 0x36, 0x4c, 0x66, 0x6e, 0x36, + 0x78, 0x71, 0x61, 0x76, 0x54, 0x34, 0x66, 0x47, 0x32, 0x77, 0x4c, 0x48, + 0x71, 0x69, 0x4d, 0x44, 0x6e, 0x30, 0x35, 0x44, 0x70, 0x4b, 0x4a, 0x4b, + 0x55, 0x65, 0x32, 0x68, 0x37, 0x6c, 0x79, 0x6f, 0x4b, 0x5a, 0x79, 0x32, + 0x46, 0x0a, 0x41, 0x6a, 0x67, 0x51, 0x35, 0x41, 0x4e, 0x68, 0x31, 0x4e, + 0x6f, 0x6c, 0x4e, 0x73, 0x63, 0x49, 0x57, 0x43, 0x32, 0x68, 0x70, 0x31, + 0x47, 0x76, 0x4d, 0x41, 0x70, 0x4a, 0x39, 0x61, 0x5a, 0x70, 0x68, 0x77, + 0x63, 0x74, 0x52, 0x45, 0x5a, 0x32, 0x6a, 0x69, 0x72, 0x6c, 0x6d, 0x6a, + 0x76, 0x58, 0x47, 0x4b, 0x4c, 0x38, 0x6e, 0x44, 0x67, 0x51, 0x7a, 0x4d, + 0x59, 0x37, 0x30, 0x72, 0x55, 0x58, 0x0a, 0x4f, 0x6d, 0x2f, 0x39, 0x72, + 0x69, 0x57, 0x39, 0x39, 0x58, 0x4a, 0x5a, 0x5a, 0x4c, 0x46, 0x30, 0x4b, + 0x6a, 0x68, 0x66, 0x47, 0x45, 0x7a, 0x66, 0x7a, 0x33, 0x45, 0x45, 0x57, + 0x6a, 0x62, 0x55, 0x76, 0x79, 0x2b, 0x5a, 0x6e, 0x4f, 0x6a, 0x5a, 0x75, + 0x72, 0x47, 0x56, 0x35, 0x67, 0x4a, 0x4c, 0x49, 0x61, 0x46, 0x62, 0x31, + 0x63, 0x46, 0x50, 0x6a, 0x36, 0x35, 0x70, 0x62, 0x56, 0x50, 0x62, 0x0a, + 0x41, 0x5a, 0x4f, 0x31, 0x58, 0x42, 0x34, 0x59, 0x33, 0x57, 0x52, 0x61, + 0x79, 0x68, 0x67, 0x6f, 0x50, 0x6d, 0x4d, 0x45, 0x45, 0x66, 0x30, 0x63, + 0x6a, 0x51, 0x41, 0x50, 0x75, 0x44, 0x66, 0x66, 0x5a, 0x34, 0x71, 0x64, + 0x5a, 0x71, 0x6b, 0x43, 0x61, 0x70, 0x48, 0x2f, 0x45, 0x38, 0x6f, 0x76, + 0x58, 0x59, 0x4f, 0x38, 0x68, 0x35, 0x4e, 0x73, 0x33, 0x43, 0x52, 0x52, + 0x46, 0x67, 0x51, 0x6c, 0x0a, 0x5a, 0x76, 0x71, 0x7a, 0x32, 0x63, 0x4b, + 0x36, 0x4b, 0x62, 0x36, 0x61, 0x53, 0x44, 0x69, 0x43, 0x6d, 0x66, 0x53, + 0x2f, 0x4f, 0x30, 0x6f, 0x78, 0x47, 0x66, 0x6d, 0x2f, 0x6a, 0x69, 0x45, + 0x7a, 0x46, 0x4d, 0x70, 0x50, 0x56, 0x46, 0x2f, 0x37, 0x7a, 0x76, 0x75, + 0x50, 0x63, 0x58, 0x2f, 0x39, 0x58, 0x68, 0x6d, 0x67, 0x44, 0x30, 0x75, + 0x52, 0x75, 0x4d, 0x52, 0x55, 0x76, 0x41, 0x61, 0x77, 0x0a, 0x52, 0x59, + 0x38, 0x6d, 0x6b, 0x61, 0x4b, 0x4f, 0x2f, 0x71, 0x6b, 0x3d, 0x0a, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, + 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, + 0x43, 0x4e, 0x3d, 0x41, 0x41, 0x41, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x20, 0x4f, 0x3d, 0x43, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, + 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x0a, + 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, + 0x4e, 0x3d, 0x41, 0x41, 0x41, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x20, 0x4f, 0x3d, 0x43, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x20, + 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x0a, 0x23, + 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6d, + 0x6f, 0x64, 0x6f, 0x20, 0x41, 0x41, 0x41, 0x20, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x20, 0x72, 0x6f, 0x6f, 0x74, 0x22, 0x0a, 0x23, + 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x31, 0x0a, 0x23, + 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, + 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x34, 0x39, 0x3a, 0x37, 0x39, 0x3a, + 0x30, 0x34, 0x3a, 0x62, 0x30, 0x3a, 0x65, 0x62, 0x3a, 0x38, 0x37, 0x3a, + 0x31, 0x39, 0x3a, 0x61, 0x63, 0x3a, 0x34, 0x37, 0x3a, 0x62, 0x30, 0x3a, + 0x62, 0x63, 0x3a, 0x31, 0x31, 0x3a, 0x35, 0x31, 0x3a, 0x39, 0x62, 0x3a, + 0x37, 0x34, 0x3a, 0x64, 0x30, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, + 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, + 0x3a, 0x20, 0x64, 0x31, 0x3a, 0x65, 0x62, 0x3a, 0x32, 0x33, 0x3a, 0x61, + 0x34, 0x3a, 0x36, 0x64, 0x3a, 0x31, 0x37, 0x3a, 0x64, 0x36, 0x3a, 0x38, + 0x66, 0x3a, 0x64, 0x39, 0x3a, 0x32, 0x35, 0x3a, 0x36, 0x34, 0x3a, 0x63, + 0x32, 0x3a, 0x66, 0x31, 0x3a, 0x66, 0x31, 0x3a, 0x36, 0x30, 0x3a, 0x31, + 0x37, 0x3a, 0x36, 0x34, 0x3a, 0x64, 0x38, 0x3a, 0x65, 0x33, 0x3a, 0x34, + 0x39, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x64, 0x37, 0x3a, 0x61, 0x37, 0x3a, 0x61, 0x30, 0x3a, 0x66, 0x62, 0x3a, + 0x35, 0x64, 0x3a, 0x37, 0x65, 0x3a, 0x32, 0x37, 0x3a, 0x33, 0x31, 0x3a, + 0x64, 0x37, 0x3a, 0x37, 0x31, 0x3a, 0x65, 0x39, 0x3a, 0x34, 0x38, 0x3a, + 0x34, 0x65, 0x3a, 0x62, 0x63, 0x3a, 0x64, 0x65, 0x3a, 0x66, 0x37, 0x3a, + 0x31, 0x64, 0x3a, 0x35, 0x66, 0x3a, 0x30, 0x63, 0x3a, 0x33, 0x65, 0x3a, + 0x30, 0x61, 0x3a, 0x32, 0x39, 0x3a, 0x34, 0x38, 0x3a, 0x37, 0x38, 0x3a, + 0x32, 0x62, 0x3a, 0x63, 0x38, 0x3a, 0x33, 0x65, 0x3a, 0x65, 0x30, 0x3a, + 0x65, 0x61, 0x3a, 0x36, 0x39, 0x3a, 0x39, 0x65, 0x3a, 0x66, 0x34, 0x0a, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, + 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x45, 0x4d, 0x6a, 0x43, 0x43, + 0x41, 0x78, 0x71, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x42, + 0x41, 0x54, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, + 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x42, 0x37, + 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, + 0x45, 0x77, 0x4a, 0x48, 0x51, 0x6a, 0x45, 0x62, 0x0a, 0x4d, 0x42, 0x6b, + 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x41, 0x77, 0x53, 0x52, 0x33, 0x4a, + 0x6c, 0x59, 0x58, 0x52, 0x6c, 0x63, 0x69, 0x42, 0x4e, 0x59, 0x57, 0x35, + 0x6a, 0x61, 0x47, 0x56, 0x7a, 0x64, 0x47, 0x56, 0x79, 0x4d, 0x52, 0x41, + 0x77, 0x44, 0x67, 0x59, 0x44, 0x56, 0x51, 0x51, 0x48, 0x44, 0x41, 0x64, + 0x54, 0x59, 0x57, 0x78, 0x6d, 0x62, 0x33, 0x4a, 0x6b, 0x4d, 0x52, 0x6f, + 0x77, 0x0a, 0x47, 0x41, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4b, 0x44, 0x42, + 0x46, 0x44, 0x62, 0x32, 0x31, 0x76, 0x5a, 0x47, 0x38, 0x67, 0x51, 0x30, + 0x45, 0x67, 0x54, 0x47, 0x6c, 0x74, 0x61, 0x58, 0x52, 0x6c, 0x5a, 0x44, + 0x45, 0x68, 0x4d, 0x42, 0x38, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x77, + 0x77, 0x59, 0x51, 0x55, 0x46, 0x42, 0x49, 0x45, 0x4e, 0x6c, 0x63, 0x6e, + 0x52, 0x70, 0x5a, 0x6d, 0x6c, 0x6a, 0x0a, 0x59, 0x58, 0x52, 0x6c, 0x49, + 0x46, 0x4e, 0x6c, 0x63, 0x6e, 0x5a, 0x70, 0x59, 0x32, 0x56, 0x7a, 0x4d, + 0x42, 0x34, 0x58, 0x44, 0x54, 0x41, 0x30, 0x4d, 0x44, 0x45, 0x77, 0x4d, + 0x54, 0x41, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x46, 0x6f, 0x58, 0x44, + 0x54, 0x49, 0x34, 0x4d, 0x54, 0x49, 0x7a, 0x4d, 0x54, 0x49, 0x7a, 0x4e, + 0x54, 0x6b, 0x31, 0x4f, 0x56, 0x6f, 0x77, 0x65, 0x7a, 0x45, 0x4c, 0x0a, + 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, + 0x52, 0x30, 0x49, 0x78, 0x47, 0x7a, 0x41, 0x5a, 0x42, 0x67, 0x4e, 0x56, + 0x42, 0x41, 0x67, 0x4d, 0x45, 0x6b, 0x64, 0x79, 0x5a, 0x57, 0x46, 0x30, + 0x5a, 0x58, 0x49, 0x67, 0x54, 0x57, 0x46, 0x75, 0x59, 0x32, 0x68, 0x6c, + 0x63, 0x33, 0x52, 0x6c, 0x63, 0x6a, 0x45, 0x51, 0x4d, 0x41, 0x34, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x0a, 0x42, 0x77, 0x77, 0x48, 0x55, 0x32, 0x46, + 0x73, 0x5a, 0x6d, 0x39, 0x79, 0x5a, 0x44, 0x45, 0x61, 0x4d, 0x42, 0x67, + 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x67, 0x77, 0x52, 0x51, 0x32, 0x39, + 0x74, 0x62, 0x32, 0x52, 0x76, 0x49, 0x45, 0x4e, 0x42, 0x49, 0x45, 0x78, + 0x70, 0x62, 0x57, 0x6c, 0x30, 0x5a, 0x57, 0x51, 0x78, 0x49, 0x54, 0x41, + 0x66, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x4d, 0x4d, 0x0a, 0x47, 0x45, + 0x46, 0x42, 0x51, 0x53, 0x42, 0x44, 0x5a, 0x58, 0x4a, 0x30, 0x61, 0x57, + 0x5a, 0x70, 0x59, 0x32, 0x46, 0x30, 0x5a, 0x53, 0x42, 0x54, 0x5a, 0x58, + 0x4a, 0x32, 0x61, 0x57, 0x4e, 0x6c, 0x63, 0x7a, 0x43, 0x43, 0x41, 0x53, + 0x49, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, + 0x63, 0x4e, 0x41, 0x51, 0x45, 0x42, 0x42, 0x51, 0x41, 0x44, 0x67, 0x67, + 0x45, 0x50, 0x0a, 0x41, 0x44, 0x43, 0x43, 0x41, 0x51, 0x6f, 0x43, 0x67, + 0x67, 0x45, 0x42, 0x41, 0x4c, 0x35, 0x41, 0x6e, 0x66, 0x52, 0x75, 0x34, + 0x65, 0x70, 0x32, 0x68, 0x78, 0x78, 0x4e, 0x52, 0x55, 0x53, 0x4f, 0x76, + 0x6b, 0x62, 0x49, 0x67, 0x77, 0x61, 0x64, 0x77, 0x53, 0x72, 0x2b, 0x47, + 0x42, 0x2b, 0x4f, 0x35, 0x41, 0x4c, 0x36, 0x38, 0x36, 0x74, 0x64, 0x55, + 0x49, 0x6f, 0x57, 0x4d, 0x51, 0x75, 0x61, 0x0a, 0x42, 0x74, 0x44, 0x46, + 0x63, 0x43, 0x4c, 0x4e, 0x53, 0x53, 0x31, 0x55, 0x59, 0x38, 0x79, 0x32, + 0x62, 0x6d, 0x68, 0x47, 0x43, 0x31, 0x50, 0x71, 0x79, 0x30, 0x77, 0x6b, + 0x77, 0x4c, 0x78, 0x79, 0x54, 0x75, 0x72, 0x78, 0x46, 0x61, 0x37, 0x30, + 0x56, 0x4a, 0x6f, 0x53, 0x43, 0x73, 0x4e, 0x36, 0x73, 0x6a, 0x4e, 0x67, + 0x34, 0x74, 0x71, 0x4a, 0x56, 0x66, 0x4d, 0x69, 0x57, 0x50, 0x50, 0x65, + 0x0a, 0x33, 0x4d, 0x2f, 0x76, 0x67, 0x34, 0x61, 0x69, 0x6a, 0x4a, 0x52, + 0x50, 0x6e, 0x32, 0x6a, 0x79, 0x6d, 0x4a, 0x42, 0x47, 0x68, 0x43, 0x66, + 0x48, 0x64, 0x72, 0x2f, 0x6a, 0x7a, 0x44, 0x55, 0x73, 0x69, 0x31, 0x34, + 0x48, 0x5a, 0x47, 0x57, 0x43, 0x77, 0x45, 0x69, 0x77, 0x71, 0x4a, 0x48, + 0x35, 0x59, 0x5a, 0x39, 0x32, 0x49, 0x46, 0x43, 0x6f, 0x6b, 0x63, 0x64, + 0x6d, 0x74, 0x65, 0x74, 0x34, 0x0a, 0x59, 0x67, 0x4e, 0x57, 0x38, 0x49, + 0x6f, 0x61, 0x45, 0x2b, 0x6f, 0x78, 0x6f, 0x78, 0x36, 0x67, 0x6d, 0x66, + 0x30, 0x34, 0x39, 0x76, 0x59, 0x6e, 0x4d, 0x6c, 0x68, 0x76, 0x42, 0x2f, + 0x56, 0x72, 0x75, 0x50, 0x73, 0x55, 0x4b, 0x36, 0x2b, 0x33, 0x71, 0x73, + 0x7a, 0x57, 0x59, 0x31, 0x39, 0x7a, 0x6a, 0x4e, 0x6f, 0x46, 0x6d, 0x61, + 0x67, 0x34, 0x71, 0x4d, 0x73, 0x58, 0x65, 0x44, 0x5a, 0x52, 0x0a, 0x72, + 0x4f, 0x6d, 0x65, 0x39, 0x48, 0x67, 0x36, 0x6a, 0x63, 0x38, 0x50, 0x32, + 0x55, 0x4c, 0x69, 0x6d, 0x41, 0x79, 0x72, 0x4c, 0x35, 0x38, 0x4f, 0x41, + 0x64, 0x37, 0x76, 0x6e, 0x35, 0x6c, 0x4a, 0x38, 0x53, 0x33, 0x66, 0x72, + 0x48, 0x52, 0x4e, 0x47, 0x35, 0x69, 0x31, 0x52, 0x38, 0x58, 0x6c, 0x4b, + 0x64, 0x48, 0x35, 0x6b, 0x42, 0x6a, 0x48, 0x59, 0x70, 0x79, 0x2b, 0x67, + 0x38, 0x63, 0x6d, 0x0a, 0x65, 0x7a, 0x36, 0x4b, 0x4a, 0x63, 0x66, 0x41, + 0x33, 0x5a, 0x33, 0x6d, 0x4e, 0x57, 0x67, 0x51, 0x49, 0x4a, 0x32, 0x50, + 0x32, 0x4e, 0x37, 0x53, 0x77, 0x34, 0x53, 0x63, 0x44, 0x56, 0x37, 0x6f, + 0x4c, 0x38, 0x6b, 0x43, 0x41, 0x77, 0x45, 0x41, 0x41, 0x61, 0x4f, 0x42, + 0x77, 0x44, 0x43, 0x42, 0x76, 0x54, 0x41, 0x64, 0x42, 0x67, 0x4e, 0x56, + 0x48, 0x51, 0x34, 0x45, 0x46, 0x67, 0x51, 0x55, 0x0a, 0x6f, 0x42, 0x45, + 0x4b, 0x49, 0x7a, 0x36, 0x57, 0x38, 0x51, 0x66, 0x73, 0x34, 0x71, 0x38, + 0x70, 0x37, 0x34, 0x4b, 0x6c, 0x66, 0x39, 0x41, 0x77, 0x70, 0x4c, 0x51, + 0x77, 0x44, 0x67, 0x59, 0x44, 0x56, 0x52, 0x30, 0x50, 0x41, 0x51, 0x48, + 0x2f, 0x42, 0x41, 0x51, 0x44, 0x41, 0x67, 0x45, 0x47, 0x4d, 0x41, 0x38, + 0x47, 0x41, 0x31, 0x55, 0x64, 0x45, 0x77, 0x45, 0x42, 0x2f, 0x77, 0x51, + 0x46, 0x0a, 0x4d, 0x41, 0x4d, 0x42, 0x41, 0x66, 0x38, 0x77, 0x65, 0x77, + 0x59, 0x44, 0x56, 0x52, 0x30, 0x66, 0x42, 0x48, 0x51, 0x77, 0x63, 0x6a, + 0x41, 0x34, 0x6f, 0x44, 0x61, 0x67, 0x4e, 0x49, 0x59, 0x79, 0x61, 0x48, + 0x52, 0x30, 0x63, 0x44, 0x6f, 0x76, 0x4c, 0x32, 0x4e, 0x79, 0x62, 0x43, + 0x35, 0x6a, 0x62, 0x32, 0x31, 0x76, 0x5a, 0x47, 0x39, 0x6a, 0x59, 0x53, + 0x35, 0x6a, 0x62, 0x32, 0x30, 0x76, 0x0a, 0x51, 0x55, 0x46, 0x42, 0x51, + 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, 0x64, + 0x47, 0x56, 0x54, 0x5a, 0x58, 0x4a, 0x32, 0x61, 0x57, 0x4e, 0x6c, 0x63, + 0x79, 0x35, 0x6a, 0x63, 0x6d, 0x77, 0x77, 0x4e, 0x71, 0x41, 0x30, 0x6f, + 0x44, 0x4b, 0x47, 0x4d, 0x47, 0x68, 0x30, 0x64, 0x48, 0x41, 0x36, 0x4c, + 0x79, 0x39, 0x6a, 0x63, 0x6d, 0x77, 0x75, 0x59, 0x32, 0x39, 0x74, 0x0a, + 0x62, 0x32, 0x52, 0x76, 0x4c, 0x6d, 0x35, 0x6c, 0x64, 0x43, 0x39, 0x42, + 0x51, 0x55, 0x46, 0x44, 0x5a, 0x58, 0x4a, 0x30, 0x61, 0x57, 0x5a, 0x70, + 0x59, 0x32, 0x46, 0x30, 0x5a, 0x56, 0x4e, 0x6c, 0x63, 0x6e, 0x5a, 0x70, + 0x59, 0x32, 0x56, 0x7a, 0x4c, 0x6d, 0x4e, 0x79, 0x62, 0x44, 0x41, 0x4e, + 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, + 0x41, 0x51, 0x55, 0x46, 0x0a, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x51, 0x45, + 0x41, 0x43, 0x46, 0x62, 0x38, 0x41, 0x76, 0x43, 0x62, 0x36, 0x50, 0x2b, + 0x6b, 0x2b, 0x74, 0x5a, 0x37, 0x78, 0x6b, 0x53, 0x41, 0x7a, 0x6b, 0x2f, + 0x45, 0x78, 0x66, 0x59, 0x41, 0x57, 0x4d, 0x79, 0x6d, 0x74, 0x72, 0x77, + 0x55, 0x53, 0x57, 0x67, 0x45, 0x64, 0x75, 0x6a, 0x6d, 0x37, 0x6c, 0x33, + 0x73, 0x41, 0x67, 0x39, 0x67, 0x31, 0x6f, 0x31, 0x51, 0x0a, 0x47, 0x45, + 0x38, 0x6d, 0x54, 0x67, 0x48, 0x6a, 0x35, 0x72, 0x43, 0x6c, 0x37, 0x72, + 0x2b, 0x38, 0x64, 0x46, 0x52, 0x42, 0x76, 0x2f, 0x33, 0x38, 0x45, 0x72, + 0x6a, 0x48, 0x54, 0x31, 0x72, 0x30, 0x69, 0x57, 0x41, 0x46, 0x66, 0x32, + 0x43, 0x33, 0x42, 0x55, 0x72, 0x7a, 0x39, 0x76, 0x48, 0x43, 0x76, 0x38, + 0x53, 0x35, 0x64, 0x49, 0x61, 0x32, 0x4c, 0x58, 0x31, 0x72, 0x7a, 0x4e, + 0x4c, 0x7a, 0x0a, 0x52, 0x74, 0x30, 0x76, 0x78, 0x75, 0x42, 0x71, 0x77, + 0x38, 0x4d, 0x30, 0x41, 0x79, 0x78, 0x39, 0x6c, 0x74, 0x31, 0x61, 0x77, + 0x67, 0x36, 0x6e, 0x43, 0x70, 0x6e, 0x42, 0x42, 0x59, 0x75, 0x72, 0x44, + 0x43, 0x2f, 0x7a, 0x58, 0x44, 0x72, 0x50, 0x62, 0x44, 0x64, 0x56, 0x43, + 0x59, 0x66, 0x65, 0x55, 0x30, 0x42, 0x73, 0x57, 0x4f, 0x2f, 0x38, 0x74, + 0x71, 0x74, 0x6c, 0x62, 0x67, 0x54, 0x32, 0x0a, 0x47, 0x39, 0x77, 0x38, + 0x34, 0x46, 0x6f, 0x56, 0x78, 0x70, 0x37, 0x5a, 0x38, 0x56, 0x6c, 0x49, + 0x4d, 0x43, 0x46, 0x6c, 0x41, 0x32, 0x7a, 0x73, 0x36, 0x53, 0x46, 0x7a, + 0x37, 0x4a, 0x73, 0x44, 0x6f, 0x65, 0x41, 0x33, 0x72, 0x61, 0x41, 0x56, + 0x47, 0x49, 0x2f, 0x36, 0x75, 0x67, 0x4c, 0x4f, 0x70, 0x79, 0x79, 0x70, + 0x45, 0x42, 0x4d, 0x73, 0x31, 0x4f, 0x55, 0x49, 0x4a, 0x71, 0x73, 0x69, + 0x0a, 0x6c, 0x32, 0x44, 0x34, 0x6b, 0x46, 0x35, 0x30, 0x31, 0x4b, 0x4b, + 0x61, 0x55, 0x37, 0x33, 0x79, 0x71, 0x57, 0x6a, 0x67, 0x6f, 0x6d, 0x37, + 0x43, 0x31, 0x32, 0x79, 0x78, 0x6f, 0x77, 0x2b, 0x65, 0x76, 0x2b, 0x74, + 0x6f, 0x35, 0x31, 0x62, 0x79, 0x72, 0x76, 0x4c, 0x6a, 0x4b, 0x7a, 0x67, + 0x36, 0x43, 0x59, 0x47, 0x31, 0x61, 0x34, 0x58, 0x58, 0x76, 0x69, 0x33, + 0x74, 0x50, 0x78, 0x71, 0x33, 0x0a, 0x73, 0x6d, 0x50, 0x69, 0x39, 0x57, + 0x49, 0x73, 0x67, 0x74, 0x52, 0x71, 0x41, 0x45, 0x46, 0x51, 0x38, 0x54, + 0x6d, 0x44, 0x6e, 0x35, 0x58, 0x70, 0x4e, 0x70, 0x61, 0x59, 0x62, 0x67, + 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, + 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, + 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x53, 0x65, 0x63, 0x75, 0x72, + 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x65, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x4f, + 0x3d, 0x43, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x20, 0x43, 0x41, 0x20, 0x4c, + 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x53, 0x65, 0x63, + 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x20, 0x4f, 0x3d, 0x43, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x20, 0x43, 0x41, + 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x0a, 0x23, 0x20, 0x4c, + 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6d, 0x6f, 0x64, + 0x6f, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x72, 0x6f, 0x6f, 0x74, 0x22, 0x0a, + 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x31, 0x0a, + 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x64, 0x33, 0x3a, 0x64, 0x39, + 0x3a, 0x62, 0x64, 0x3a, 0x61, 0x65, 0x3a, 0x39, 0x66, 0x3a, 0x61, 0x63, + 0x3a, 0x36, 0x37, 0x3a, 0x32, 0x34, 0x3a, 0x62, 0x33, 0x3a, 0x63, 0x38, + 0x3a, 0x31, 0x62, 0x3a, 0x35, 0x32, 0x3a, 0x65, 0x31, 0x3a, 0x62, 0x39, + 0x3a, 0x61, 0x39, 0x3a, 0x62, 0x64, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, + 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, + 0x74, 0x3a, 0x20, 0x34, 0x61, 0x3a, 0x36, 0x35, 0x3a, 0x64, 0x35, 0x3a, + 0x66, 0x34, 0x3a, 0x31, 0x64, 0x3a, 0x65, 0x66, 0x3a, 0x33, 0x39, 0x3a, + 0x62, 0x38, 0x3a, 0x62, 0x38, 0x3a, 0x39, 0x30, 0x3a, 0x34, 0x61, 0x3a, + 0x34, 0x61, 0x3a, 0x64, 0x33, 0x3a, 0x36, 0x34, 0x3a, 0x38, 0x31, 0x3a, + 0x33, 0x33, 0x3a, 0x63, 0x66, 0x3a, 0x63, 0x37, 0x3a, 0x61, 0x31, 0x3a, + 0x64, 0x31, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, + 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, + 0x20, 0x62, 0x64, 0x3a, 0x38, 0x31, 0x3a, 0x63, 0x65, 0x3a, 0x33, 0x62, + 0x3a, 0x34, 0x66, 0x3a, 0x36, 0x35, 0x3a, 0x39, 0x31, 0x3a, 0x64, 0x31, + 0x3a, 0x31, 0x61, 0x3a, 0x36, 0x37, 0x3a, 0x62, 0x35, 0x3a, 0x66, 0x63, + 0x3a, 0x37, 0x61, 0x3a, 0x34, 0x37, 0x3a, 0x66, 0x64, 0x3a, 0x65, 0x66, + 0x3a, 0x32, 0x35, 0x3a, 0x35, 0x32, 0x3a, 0x31, 0x62, 0x3a, 0x66, 0x39, + 0x3a, 0x61, 0x61, 0x3a, 0x34, 0x65, 0x3a, 0x31, 0x38, 0x3a, 0x62, 0x39, + 0x3a, 0x65, 0x33, 0x3a, 0x64, 0x66, 0x3a, 0x32, 0x65, 0x3a, 0x33, 0x34, + 0x3a, 0x61, 0x37, 0x3a, 0x38, 0x30, 0x3a, 0x33, 0x62, 0x3a, 0x65, 0x38, + 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, + 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x45, 0x50, 0x7a, 0x43, + 0x43, 0x41, 0x79, 0x65, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, + 0x42, 0x41, 0x54, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, + 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x42, + 0x2b, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, + 0x47, 0x45, 0x77, 0x4a, 0x48, 0x51, 0x6a, 0x45, 0x62, 0x0a, 0x4d, 0x42, + 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x41, 0x77, 0x53, 0x52, 0x33, + 0x4a, 0x6c, 0x59, 0x58, 0x52, 0x6c, 0x63, 0x69, 0x42, 0x4e, 0x59, 0x57, + 0x35, 0x6a, 0x61, 0x47, 0x56, 0x7a, 0x64, 0x47, 0x56, 0x79, 0x4d, 0x52, + 0x41, 0x77, 0x44, 0x67, 0x59, 0x44, 0x56, 0x51, 0x51, 0x48, 0x44, 0x41, + 0x64, 0x54, 0x59, 0x57, 0x78, 0x6d, 0x62, 0x33, 0x4a, 0x6b, 0x4d, 0x52, + 0x6f, 0x77, 0x0a, 0x47, 0x41, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4b, 0x44, + 0x42, 0x46, 0x44, 0x62, 0x32, 0x31, 0x76, 0x5a, 0x47, 0x38, 0x67, 0x51, + 0x30, 0x45, 0x67, 0x54, 0x47, 0x6c, 0x74, 0x61, 0x58, 0x52, 0x6c, 0x5a, + 0x44, 0x45, 0x6b, 0x4d, 0x43, 0x49, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, + 0x77, 0x77, 0x62, 0x55, 0x32, 0x56, 0x6a, 0x64, 0x58, 0x4a, 0x6c, 0x49, + 0x45, 0x4e, 0x6c, 0x63, 0x6e, 0x52, 0x70, 0x0a, 0x5a, 0x6d, 0x6c, 0x6a, + 0x59, 0x58, 0x52, 0x6c, 0x49, 0x46, 0x4e, 0x6c, 0x63, 0x6e, 0x5a, 0x70, + 0x59, 0x32, 0x56, 0x7a, 0x4d, 0x42, 0x34, 0x58, 0x44, 0x54, 0x41, 0x30, + 0x4d, 0x44, 0x45, 0x77, 0x4d, 0x54, 0x41, 0x77, 0x4d, 0x44, 0x41, 0x77, + 0x4d, 0x46, 0x6f, 0x58, 0x44, 0x54, 0x49, 0x34, 0x4d, 0x54, 0x49, 0x7a, + 0x4d, 0x54, 0x49, 0x7a, 0x4e, 0x54, 0x6b, 0x31, 0x4f, 0x56, 0x6f, 0x77, + 0x0a, 0x66, 0x6a, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x42, 0x68, 0x4d, 0x43, 0x52, 0x30, 0x49, 0x78, 0x47, 0x7a, 0x41, + 0x5a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x67, 0x4d, 0x45, 0x6b, 0x64, + 0x79, 0x5a, 0x57, 0x46, 0x30, 0x5a, 0x58, 0x49, 0x67, 0x54, 0x57, 0x46, + 0x75, 0x59, 0x32, 0x68, 0x6c, 0x63, 0x33, 0x52, 0x6c, 0x63, 0x6a, 0x45, + 0x51, 0x4d, 0x41, 0x34, 0x47, 0x0a, 0x41, 0x31, 0x55, 0x45, 0x42, 0x77, + 0x77, 0x48, 0x55, 0x32, 0x46, 0x73, 0x5a, 0x6d, 0x39, 0x79, 0x5a, 0x44, + 0x45, 0x61, 0x4d, 0x42, 0x67, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x67, + 0x77, 0x52, 0x51, 0x32, 0x39, 0x74, 0x62, 0x32, 0x52, 0x76, 0x49, 0x45, + 0x4e, 0x42, 0x49, 0x45, 0x78, 0x70, 0x62, 0x57, 0x6c, 0x30, 0x5a, 0x57, + 0x51, 0x78, 0x4a, 0x44, 0x41, 0x69, 0x42, 0x67, 0x4e, 0x56, 0x0a, 0x42, + 0x41, 0x4d, 0x4d, 0x47, 0x31, 0x4e, 0x6c, 0x59, 0x33, 0x56, 0x79, 0x5a, + 0x53, 0x42, 0x44, 0x5a, 0x58, 0x4a, 0x30, 0x61, 0x57, 0x5a, 0x70, 0x59, + 0x32, 0x46, 0x30, 0x5a, 0x53, 0x42, 0x54, 0x5a, 0x58, 0x4a, 0x32, 0x61, + 0x57, 0x4e, 0x6c, 0x63, 0x7a, 0x43, 0x43, 0x41, 0x53, 0x49, 0x77, 0x44, + 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, + 0x51, 0x45, 0x42, 0x0a, 0x42, 0x51, 0x41, 0x44, 0x67, 0x67, 0x45, 0x50, + 0x41, 0x44, 0x43, 0x43, 0x41, 0x51, 0x6f, 0x43, 0x67, 0x67, 0x45, 0x42, + 0x41, 0x4d, 0x42, 0x78, 0x4d, 0x34, 0x4b, 0x4b, 0x30, 0x48, 0x44, 0x72, + 0x63, 0x34, 0x65, 0x43, 0x51, 0x4e, 0x55, 0x64, 0x35, 0x4d, 0x76, 0x4a, + 0x44, 0x6b, 0x4b, 0x51, 0x2b, 0x64, 0x34, 0x30, 0x75, 0x61, 0x47, 0x36, + 0x45, 0x66, 0x51, 0x6c, 0x68, 0x66, 0x50, 0x4d, 0x0a, 0x63, 0x6d, 0x33, + 0x79, 0x65, 0x35, 0x64, 0x72, 0x73, 0x77, 0x66, 0x78, 0x64, 0x79, 0x53, + 0x52, 0x58, 0x79, 0x57, 0x50, 0x39, 0x6e, 0x51, 0x39, 0x35, 0x49, 0x44, + 0x43, 0x2b, 0x44, 0x77, 0x4e, 0x38, 0x37, 0x39, 0x41, 0x36, 0x76, 0x66, + 0x49, 0x55, 0x74, 0x46, 0x79, 0x62, 0x2b, 0x2f, 0x49, 0x71, 0x30, 0x47, + 0x34, 0x62, 0x69, 0x34, 0x58, 0x4b, 0x70, 0x56, 0x70, 0x44, 0x4d, 0x33, + 0x53, 0x0a, 0x48, 0x70, 0x52, 0x37, 0x4c, 0x5a, 0x51, 0x64, 0x71, 0x6e, + 0x58, 0x58, 0x73, 0x35, 0x6a, 0x4c, 0x72, 0x4c, 0x78, 0x6b, 0x55, 0x30, + 0x43, 0x38, 0x6a, 0x36, 0x79, 0x73, 0x4e, 0x73, 0x74, 0x63, 0x72, 0x62, + 0x76, 0x64, 0x34, 0x4a, 0x51, 0x58, 0x37, 0x4e, 0x46, 0x63, 0x30, 0x4c, + 0x2f, 0x76, 0x70, 0x5a, 0x58, 0x4a, 0x6b, 0x4d, 0x57, 0x77, 0x72, 0x50, + 0x73, 0x62, 0x51, 0x39, 0x39, 0x36, 0x0a, 0x43, 0x46, 0x32, 0x33, 0x75, + 0x50, 0x4a, 0x41, 0x47, 0x79, 0x73, 0x6e, 0x6e, 0x6c, 0x44, 0x4f, 0x58, + 0x6d, 0x57, 0x43, 0x69, 0x49, 0x78, 0x65, 0x30, 0x30, 0x34, 0x4d, 0x65, + 0x75, 0x6f, 0x49, 0x6b, 0x62, 0x59, 0x32, 0x71, 0x69, 0x74, 0x43, 0x2b, + 0x2b, 0x72, 0x43, 0x6f, 0x7a, 0x6e, 0x6c, 0x32, 0x79, 0x59, 0x34, 0x72, + 0x59, 0x73, 0x4b, 0x37, 0x68, 0x6c, 0x6a, 0x78, 0x78, 0x77, 0x6b, 0x0a, + 0x33, 0x77, 0x4e, 0x34, 0x32, 0x75, 0x62, 0x71, 0x77, 0x55, 0x63, 0x61, + 0x43, 0x77, 0x74, 0x47, 0x43, 0x64, 0x30, 0x43, 0x2f, 0x4e, 0x37, 0x4c, + 0x68, 0x31, 0x2f, 0x58, 0x4d, 0x47, 0x4e, 0x6f, 0x6f, 0x61, 0x37, 0x63, + 0x4d, 0x71, 0x47, 0x36, 0x76, 0x76, 0x35, 0x45, 0x71, 0x32, 0x69, 0x32, + 0x70, 0x52, 0x63, 0x56, 0x2f, 0x62, 0x33, 0x56, 0x70, 0x36, 0x65, 0x61, + 0x35, 0x45, 0x51, 0x7a, 0x0a, 0x36, 0x59, 0x69, 0x4f, 0x2f, 0x4f, 0x31, + 0x52, 0x36, 0x35, 0x4e, 0x78, 0x54, 0x71, 0x30, 0x42, 0x35, 0x30, 0x53, + 0x4f, 0x71, 0x79, 0x33, 0x4c, 0x71, 0x50, 0x34, 0x42, 0x53, 0x55, 0x6a, + 0x77, 0x77, 0x4e, 0x33, 0x48, 0x61, 0x4e, 0x69, 0x53, 0x2f, 0x6a, 0x30, + 0x43, 0x41, 0x77, 0x45, 0x41, 0x41, 0x61, 0x4f, 0x42, 0x78, 0x7a, 0x43, + 0x42, 0x78, 0x44, 0x41, 0x64, 0x42, 0x67, 0x4e, 0x56, 0x0a, 0x48, 0x51, + 0x34, 0x45, 0x46, 0x67, 0x51, 0x55, 0x50, 0x4e, 0x69, 0x54, 0x69, 0x4d, + 0x4c, 0x41, 0x67, 0x67, 0x6e, 0x4d, 0x41, 0x5a, 0x6b, 0x47, 0x6b, 0x79, + 0x44, 0x70, 0x6e, 0x6e, 0x41, 0x4a, 0x59, 0x30, 0x38, 0x77, 0x44, 0x67, + 0x59, 0x44, 0x56, 0x52, 0x30, 0x50, 0x41, 0x51, 0x48, 0x2f, 0x42, 0x41, + 0x51, 0x44, 0x41, 0x67, 0x45, 0x47, 0x4d, 0x41, 0x38, 0x47, 0x41, 0x31, + 0x55, 0x64, 0x0a, 0x45, 0x77, 0x45, 0x42, 0x2f, 0x77, 0x51, 0x46, 0x4d, + 0x41, 0x4d, 0x42, 0x41, 0x66, 0x38, 0x77, 0x67, 0x59, 0x45, 0x47, 0x41, + 0x31, 0x55, 0x64, 0x48, 0x77, 0x52, 0x36, 0x4d, 0x48, 0x67, 0x77, 0x4f, + 0x36, 0x41, 0x35, 0x6f, 0x44, 0x65, 0x47, 0x4e, 0x57, 0x68, 0x30, 0x64, + 0x48, 0x41, 0x36, 0x4c, 0x79, 0x39, 0x6a, 0x63, 0x6d, 0x77, 0x75, 0x59, + 0x32, 0x39, 0x74, 0x62, 0x32, 0x52, 0x76, 0x0a, 0x59, 0x32, 0x45, 0x75, + 0x59, 0x32, 0x39, 0x74, 0x4c, 0x31, 0x4e, 0x6c, 0x59, 0x33, 0x56, 0x79, + 0x5a, 0x55, 0x4e, 0x6c, 0x63, 0x6e, 0x52, 0x70, 0x5a, 0x6d, 0x6c, 0x6a, + 0x59, 0x58, 0x52, 0x6c, 0x55, 0x32, 0x56, 0x79, 0x64, 0x6d, 0x6c, 0x6a, + 0x5a, 0x58, 0x4d, 0x75, 0x59, 0x33, 0x4a, 0x73, 0x4d, 0x44, 0x6d, 0x67, + 0x4e, 0x36, 0x41, 0x31, 0x68, 0x6a, 0x4e, 0x6f, 0x64, 0x48, 0x52, 0x77, + 0x0a, 0x4f, 0x69, 0x38, 0x76, 0x59, 0x33, 0x4a, 0x73, 0x4c, 0x6d, 0x4e, + 0x76, 0x62, 0x57, 0x39, 0x6b, 0x62, 0x79, 0x35, 0x75, 0x5a, 0x58, 0x51, + 0x76, 0x55, 0x32, 0x56, 0x6a, 0x64, 0x58, 0x4a, 0x6c, 0x51, 0x32, 0x56, + 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, 0x56, + 0x54, 0x5a, 0x58, 0x4a, 0x32, 0x61, 0x57, 0x4e, 0x6c, 0x63, 0x79, 0x35, + 0x6a, 0x63, 0x6d, 0x77, 0x77, 0x0a, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, + 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x46, 0x42, 0x51, + 0x41, 0x44, 0x67, 0x67, 0x45, 0x42, 0x41, 0x49, 0x63, 0x42, 0x62, 0x53, + 0x4d, 0x64, 0x66, 0x6c, 0x73, 0x58, 0x66, 0x63, 0x46, 0x68, 0x4d, 0x73, + 0x2b, 0x50, 0x35, 0x2f, 0x4f, 0x4b, 0x6c, 0x46, 0x6c, 0x6d, 0x34, 0x4a, + 0x34, 0x6f, 0x71, 0x46, 0x37, 0x54, 0x74, 0x2f, 0x51, 0x30, 0x0a, 0x35, + 0x71, 0x6f, 0x35, 0x73, 0x70, 0x63, 0x57, 0x78, 0x59, 0x4a, 0x76, 0x4d, + 0x71, 0x54, 0x70, 0x6a, 0x4f, 0x65, 0x76, 0x2f, 0x65, 0x2f, 0x43, 0x36, + 0x4c, 0x6c, 0x4c, 0x71, 0x71, 0x50, 0x30, 0x35, 0x74, 0x71, 0x4e, 0x5a, + 0x53, 0x48, 0x37, 0x75, 0x6f, 0x44, 0x72, 0x4a, 0x69, 0x69, 0x46, 0x47, + 0x76, 0x34, 0x35, 0x6a, 0x4e, 0x35, 0x62, 0x42, 0x41, 0x53, 0x30, 0x56, + 0x50, 0x6d, 0x6a, 0x0a, 0x5a, 0x35, 0x35, 0x42, 0x2b, 0x67, 0x6c, 0x53, + 0x7a, 0x41, 0x56, 0x49, 0x71, 0x4d, 0x6b, 0x2f, 0x49, 0x51, 0x51, 0x65, + 0x7a, 0x6b, 0x68, 0x72, 0x2f, 0x49, 0x58, 0x6f, 0x77, 0x6e, 0x75, 0x76, + 0x66, 0x37, 0x66, 0x4d, 0x2b, 0x46, 0x38, 0x36, 0x2f, 0x54, 0x58, 0x47, + 0x44, 0x65, 0x2b, 0x58, 0x33, 0x45, 0x79, 0x72, 0x45, 0x65, 0x46, 0x72, + 0x79, 0x7a, 0x48, 0x52, 0x62, 0x50, 0x74, 0x49, 0x0a, 0x67, 0x4b, 0x76, + 0x63, 0x6e, 0x44, 0x65, 0x34, 0x49, 0x52, 0x52, 0x4c, 0x44, 0x58, 0x45, + 0x39, 0x37, 0x49, 0x4d, 0x7a, 0x62, 0x74, 0x46, 0x75, 0x4d, 0x68, 0x62, + 0x73, 0x6d, 0x4d, 0x63, 0x57, 0x69, 0x31, 0x6d, 0x6d, 0x4e, 0x4b, 0x73, + 0x46, 0x56, 0x79, 0x32, 0x54, 0x39, 0x36, 0x6f, 0x54, 0x79, 0x39, 0x49, + 0x54, 0x34, 0x72, 0x63, 0x75, 0x4f, 0x38, 0x31, 0x72, 0x55, 0x42, 0x63, + 0x4a, 0x0a, 0x61, 0x44, 0x36, 0x31, 0x4a, 0x6c, 0x66, 0x75, 0x74, 0x75, + 0x43, 0x32, 0x33, 0x62, 0x6b, 0x70, 0x67, 0x48, 0x6c, 0x39, 0x6a, 0x36, + 0x50, 0x77, 0x70, 0x43, 0x69, 0x6b, 0x46, 0x63, 0x53, 0x46, 0x39, 0x43, + 0x66, 0x55, 0x61, 0x37, 0x2f, 0x6c, 0x58, 0x4f, 0x52, 0x6c, 0x41, 0x6e, + 0x5a, 0x55, 0x74, 0x4f, 0x4d, 0x33, 0x5a, 0x69, 0x54, 0x54, 0x47, 0x57, + 0x48, 0x49, 0x55, 0x68, 0x44, 0x6c, 0x0a, 0x69, 0x7a, 0x65, 0x61, 0x75, + 0x61, 0x6e, 0x35, 0x48, 0x62, 0x2f, 0x71, 0x6d, 0x5a, 0x4a, 0x68, 0x6c, + 0x76, 0x38, 0x42, 0x7a, 0x61, 0x46, 0x66, 0x44, 0x62, 0x78, 0x78, 0x76, + 0x41, 0x36, 0x73, 0x43, 0x78, 0x31, 0x48, 0x52, 0x52, 0x33, 0x42, 0x37, + 0x48, 0x7a, 0x73, 0x2f, 0x53, 0x6b, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, + 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, + 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, + 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x20, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x20, 0x4f, 0x3d, 0x43, 0x6f, 0x6d, 0x6f, 0x64, + 0x6f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, + 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, + 0x43, 0x4e, 0x3d, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x20, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x4f, 0x3d, 0x43, 0x6f, + 0x6d, 0x6f, 0x64, 0x6f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, + 0x74, 0x65, 0x64, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x43, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x20, 0x54, 0x72, 0x75, + 0x73, 0x74, 0x65, 0x64, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x20, 0x72, 0x6f, 0x6f, 0x74, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, + 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x31, 0x0a, 0x23, 0x20, 0x4d, 0x44, + 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, + 0x74, 0x3a, 0x20, 0x39, 0x31, 0x3a, 0x31, 0x62, 0x3a, 0x33, 0x66, 0x3a, + 0x36, 0x65, 0x3a, 0x63, 0x64, 0x3a, 0x39, 0x65, 0x3a, 0x61, 0x62, 0x3a, + 0x65, 0x65, 0x3a, 0x30, 0x37, 0x3a, 0x66, 0x65, 0x3a, 0x31, 0x66, 0x3a, + 0x37, 0x31, 0x3a, 0x64, 0x32, 0x3a, 0x62, 0x33, 0x3a, 0x36, 0x31, 0x3a, + 0x32, 0x37, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, + 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x65, + 0x31, 0x3a, 0x39, 0x66, 0x3a, 0x65, 0x33, 0x3a, 0x30, 0x65, 0x3a, 0x38, + 0x62, 0x3a, 0x38, 0x34, 0x3a, 0x36, 0x30, 0x3a, 0x39, 0x65, 0x3a, 0x38, + 0x30, 0x3a, 0x39, 0x62, 0x3a, 0x31, 0x37, 0x3a, 0x30, 0x64, 0x3a, 0x37, + 0x32, 0x3a, 0x61, 0x38, 0x3a, 0x63, 0x35, 0x3a, 0x62, 0x61, 0x3a, 0x36, + 0x65, 0x3a, 0x31, 0x34, 0x3a, 0x30, 0x39, 0x3a, 0x62, 0x64, 0x0a, 0x23, + 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, + 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x33, 0x66, 0x3a, + 0x30, 0x36, 0x3a, 0x65, 0x35, 0x3a, 0x35, 0x36, 0x3a, 0x38, 0x31, 0x3a, + 0x64, 0x34, 0x3a, 0x39, 0x36, 0x3a, 0x66, 0x35, 0x3a, 0x62, 0x65, 0x3a, + 0x31, 0x36, 0x3a, 0x39, 0x65, 0x3a, 0x62, 0x35, 0x3a, 0x33, 0x38, 0x3a, + 0x39, 0x66, 0x3a, 0x39, 0x66, 0x3a, 0x32, 0x62, 0x3a, 0x38, 0x66, 0x3a, + 0x66, 0x36, 0x3a, 0x31, 0x65, 0x3a, 0x31, 0x37, 0x3a, 0x30, 0x38, 0x3a, + 0x64, 0x66, 0x3a, 0x36, 0x38, 0x3a, 0x38, 0x31, 0x3a, 0x37, 0x32, 0x3a, + 0x34, 0x38, 0x3a, 0x34, 0x39, 0x3a, 0x63, 0x64, 0x3a, 0x35, 0x64, 0x3a, + 0x32, 0x37, 0x3a, 0x63, 0x62, 0x3a, 0x36, 0x39, 0x0a, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, + 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x0a, 0x4d, 0x49, 0x49, 0x45, 0x51, 0x7a, 0x43, 0x43, 0x41, 0x79, 0x75, + 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x42, 0x41, 0x54, 0x41, + 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, + 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x42, 0x2f, 0x4d, 0x51, 0x73, + 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, + 0x48, 0x51, 0x6a, 0x45, 0x62, 0x0a, 0x4d, 0x42, 0x6b, 0x47, 0x41, 0x31, + 0x55, 0x45, 0x43, 0x41, 0x77, 0x53, 0x52, 0x33, 0x4a, 0x6c, 0x59, 0x58, + 0x52, 0x6c, 0x63, 0x69, 0x42, 0x4e, 0x59, 0x57, 0x35, 0x6a, 0x61, 0x47, + 0x56, 0x7a, 0x64, 0x47, 0x56, 0x79, 0x4d, 0x52, 0x41, 0x77, 0x44, 0x67, + 0x59, 0x44, 0x56, 0x51, 0x51, 0x48, 0x44, 0x41, 0x64, 0x54, 0x59, 0x57, + 0x78, 0x6d, 0x62, 0x33, 0x4a, 0x6b, 0x4d, 0x52, 0x6f, 0x77, 0x0a, 0x47, + 0x41, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4b, 0x44, 0x42, 0x46, 0x44, 0x62, + 0x32, 0x31, 0x76, 0x5a, 0x47, 0x38, 0x67, 0x51, 0x30, 0x45, 0x67, 0x54, + 0x47, 0x6c, 0x74, 0x61, 0x58, 0x52, 0x6c, 0x5a, 0x44, 0x45, 0x6c, 0x4d, + 0x43, 0x4d, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x77, 0x77, 0x63, 0x56, + 0x48, 0x4a, 0x31, 0x63, 0x33, 0x52, 0x6c, 0x5a, 0x43, 0x42, 0x44, 0x5a, + 0x58, 0x4a, 0x30, 0x0a, 0x61, 0x57, 0x5a, 0x70, 0x59, 0x32, 0x46, 0x30, + 0x5a, 0x53, 0x42, 0x54, 0x5a, 0x58, 0x4a, 0x32, 0x61, 0x57, 0x4e, 0x6c, + 0x63, 0x7a, 0x41, 0x65, 0x46, 0x77, 0x30, 0x77, 0x4e, 0x44, 0x41, 0x78, + 0x4d, 0x44, 0x45, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x44, 0x42, 0x61, + 0x46, 0x77, 0x30, 0x79, 0x4f, 0x44, 0x45, 0x79, 0x4d, 0x7a, 0x45, 0x79, + 0x4d, 0x7a, 0x55, 0x35, 0x4e, 0x54, 0x6c, 0x61, 0x0a, 0x4d, 0x48, 0x38, + 0x78, 0x43, 0x7a, 0x41, 0x4a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x59, + 0x54, 0x41, 0x6b, 0x64, 0x43, 0x4d, 0x52, 0x73, 0x77, 0x47, 0x51, 0x59, + 0x44, 0x56, 0x51, 0x51, 0x49, 0x44, 0x42, 0x4a, 0x48, 0x63, 0x6d, 0x56, + 0x68, 0x64, 0x47, 0x56, 0x79, 0x49, 0x45, 0x31, 0x68, 0x62, 0x6d, 0x4e, + 0x6f, 0x5a, 0x58, 0x4e, 0x30, 0x5a, 0x58, 0x49, 0x78, 0x45, 0x44, 0x41, + 0x4f, 0x0a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x63, 0x4d, 0x42, 0x31, + 0x4e, 0x68, 0x62, 0x47, 0x5a, 0x76, 0x63, 0x6d, 0x51, 0x78, 0x47, 0x6a, + 0x41, 0x59, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x4d, 0x45, 0x55, + 0x4e, 0x76, 0x62, 0x57, 0x39, 0x6b, 0x62, 0x79, 0x42, 0x44, 0x51, 0x53, + 0x42, 0x4d, 0x61, 0x57, 0x31, 0x70, 0x64, 0x47, 0x56, 0x6b, 0x4d, 0x53, + 0x55, 0x77, 0x49, 0x77, 0x59, 0x44, 0x0a, 0x56, 0x51, 0x51, 0x44, 0x44, + 0x42, 0x78, 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x47, 0x56, 0x6b, 0x49, + 0x45, 0x4e, 0x6c, 0x63, 0x6e, 0x52, 0x70, 0x5a, 0x6d, 0x6c, 0x6a, 0x59, + 0x58, 0x52, 0x6c, 0x49, 0x46, 0x4e, 0x6c, 0x63, 0x6e, 0x5a, 0x70, 0x59, + 0x32, 0x56, 0x7a, 0x4d, 0x49, 0x49, 0x42, 0x49, 0x6a, 0x41, 0x4e, 0x42, + 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x0a, + 0x41, 0x51, 0x45, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x51, 0x38, 0x41, + 0x4d, 0x49, 0x49, 0x42, 0x43, 0x67, 0x4b, 0x43, 0x41, 0x51, 0x45, 0x41, + 0x33, 0x33, 0x46, 0x76, 0x4e, 0x6c, 0x68, 0x54, 0x57, 0x76, 0x49, 0x32, + 0x56, 0x46, 0x65, 0x41, 0x78, 0x48, 0x51, 0x49, 0x49, 0x4f, 0x30, 0x59, + 0x66, 0x79, 0x6f, 0x64, 0x35, 0x6a, 0x57, 0x61, 0x48, 0x69, 0x57, 0x73, + 0x6e, 0x4f, 0x57, 0x57, 0x0a, 0x66, 0x6e, 0x4a, 0x53, 0x6f, 0x42, 0x56, + 0x43, 0x32, 0x31, 0x6e, 0x64, 0x5a, 0x48, 0x6f, 0x61, 0x30, 0x4c, 0x68, + 0x37, 0x33, 0x54, 0x6b, 0x56, 0x76, 0x46, 0x56, 0x49, 0x78, 0x4f, 0x30, + 0x36, 0x41, 0x4f, 0x6f, 0x78, 0x45, 0x62, 0x72, 0x79, 0x63, 0x58, 0x51, + 0x61, 0x5a, 0x37, 0x6a, 0x50, 0x4d, 0x38, 0x79, 0x6f, 0x4d, 0x61, 0x2b, + 0x6a, 0x34, 0x39, 0x64, 0x2f, 0x76, 0x7a, 0x4d, 0x74, 0x0a, 0x54, 0x47, + 0x6f, 0x38, 0x37, 0x49, 0x76, 0x44, 0x6b, 0x74, 0x4a, 0x54, 0x64, 0x79, + 0x52, 0x30, 0x6e, 0x41, 0x64, 0x75, 0x63, 0x50, 0x79, 0x39, 0x43, 0x31, + 0x74, 0x32, 0x75, 0x6c, 0x2f, 0x79, 0x2f, 0x39, 0x63, 0x33, 0x53, 0x30, + 0x70, 0x67, 0x65, 0x50, 0x66, 0x77, 0x2b, 0x73, 0x70, 0x77, 0x74, 0x4f, + 0x70, 0x5a, 0x71, 0x71, 0x50, 0x4f, 0x53, 0x43, 0x2b, 0x70, 0x77, 0x37, + 0x49, 0x4c, 0x0a, 0x66, 0x68, 0x64, 0x79, 0x46, 0x67, 0x79, 0x6d, 0x42, + 0x77, 0x77, 0x62, 0x4f, 0x4d, 0x2f, 0x4a, 0x59, 0x72, 0x63, 0x2f, 0x6f, + 0x4a, 0x4f, 0x6c, 0x68, 0x30, 0x48, 0x79, 0x74, 0x33, 0x42, 0x41, 0x64, + 0x39, 0x69, 0x2b, 0x46, 0x48, 0x7a, 0x6a, 0x71, 0x4d, 0x42, 0x36, 0x6a, + 0x75, 0x6c, 0x6a, 0x61, 0x74, 0x45, 0x50, 0x6d, 0x73, 0x62, 0x53, 0x39, + 0x49, 0x73, 0x36, 0x46, 0x41, 0x52, 0x57, 0x0a, 0x31, 0x4f, 0x32, 0x34, + 0x7a, 0x47, 0x37, 0x31, 0x2b, 0x2b, 0x49, 0x73, 0x57, 0x4c, 0x31, 0x2f, + 0x54, 0x32, 0x73, 0x72, 0x39, 0x32, 0x41, 0x6b, 0x57, 0x43, 0x54, 0x4f, + 0x4a, 0x75, 0x38, 0x30, 0x6b, 0x54, 0x72, 0x56, 0x34, 0x34, 0x48, 0x51, + 0x73, 0x76, 0x41, 0x45, 0x41, 0x74, 0x64, 0x62, 0x74, 0x7a, 0x36, 0x53, + 0x72, 0x47, 0x73, 0x53, 0x69, 0x76, 0x6e, 0x6b, 0x42, 0x62, 0x41, 0x37, + 0x0a, 0x6b, 0x55, 0x6c, 0x63, 0x73, 0x75, 0x74, 0x54, 0x36, 0x76, 0x69, + 0x66, 0x52, 0x34, 0x62, 0x75, 0x76, 0x35, 0x58, 0x41, 0x77, 0x41, 0x61, + 0x66, 0x30, 0x6c, 0x74, 0x65, 0x45, 0x52, 0x76, 0x30, 0x78, 0x77, 0x51, + 0x31, 0x4b, 0x64, 0x4a, 0x56, 0x58, 0x4f, 0x54, 0x74, 0x36, 0x77, 0x49, + 0x44, 0x41, 0x51, 0x41, 0x42, 0x6f, 0x34, 0x48, 0x4a, 0x4d, 0x49, 0x48, + 0x47, 0x4d, 0x42, 0x30, 0x47, 0x0a, 0x41, 0x31, 0x55, 0x64, 0x44, 0x67, + 0x51, 0x57, 0x42, 0x42, 0x54, 0x46, 0x65, 0x31, 0x69, 0x39, 0x37, 0x64, + 0x6f, 0x6c, 0x61, 0x64, 0x4c, 0x33, 0x57, 0x52, 0x61, 0x6f, 0x73, 0x7a, + 0x4c, 0x41, 0x65, 0x79, 0x64, 0x62, 0x39, 0x44, 0x41, 0x4f, 0x42, 0x67, + 0x4e, 0x56, 0x48, 0x51, 0x38, 0x42, 0x41, 0x66, 0x38, 0x45, 0x42, 0x41, + 0x4d, 0x43, 0x41, 0x51, 0x59, 0x77, 0x44, 0x77, 0x59, 0x44, 0x0a, 0x56, + 0x52, 0x30, 0x54, 0x41, 0x51, 0x48, 0x2f, 0x42, 0x41, 0x55, 0x77, 0x41, + 0x77, 0x45, 0x42, 0x2f, 0x7a, 0x43, 0x42, 0x67, 0x77, 0x59, 0x44, 0x56, + 0x52, 0x30, 0x66, 0x42, 0x48, 0x77, 0x77, 0x65, 0x6a, 0x41, 0x38, 0x6f, + 0x44, 0x71, 0x67, 0x4f, 0x49, 0x59, 0x32, 0x61, 0x48, 0x52, 0x30, 0x63, + 0x44, 0x6f, 0x76, 0x4c, 0x32, 0x4e, 0x79, 0x62, 0x43, 0x35, 0x6a, 0x62, + 0x32, 0x31, 0x76, 0x0a, 0x5a, 0x47, 0x39, 0x6a, 0x59, 0x53, 0x35, 0x6a, + 0x62, 0x32, 0x30, 0x76, 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x52, 0x6c, + 0x5a, 0x45, 0x4e, 0x6c, 0x63, 0x6e, 0x52, 0x70, 0x5a, 0x6d, 0x6c, 0x6a, + 0x59, 0x58, 0x52, 0x6c, 0x55, 0x32, 0x56, 0x79, 0x64, 0x6d, 0x6c, 0x6a, + 0x5a, 0x58, 0x4d, 0x75, 0x59, 0x33, 0x4a, 0x73, 0x4d, 0x44, 0x71, 0x67, + 0x4f, 0x4b, 0x41, 0x32, 0x68, 0x6a, 0x52, 0x6f, 0x0a, 0x64, 0x48, 0x52, + 0x77, 0x4f, 0x69, 0x38, 0x76, 0x59, 0x33, 0x4a, 0x73, 0x4c, 0x6d, 0x4e, + 0x76, 0x62, 0x57, 0x39, 0x6b, 0x62, 0x79, 0x35, 0x75, 0x5a, 0x58, 0x51, + 0x76, 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x52, 0x6c, 0x5a, 0x45, 0x4e, + 0x6c, 0x63, 0x6e, 0x52, 0x70, 0x5a, 0x6d, 0x6c, 0x6a, 0x59, 0x58, 0x52, + 0x6c, 0x55, 0x32, 0x56, 0x79, 0x64, 0x6d, 0x6c, 0x6a, 0x5a, 0x58, 0x4d, + 0x75, 0x0a, 0x59, 0x33, 0x4a, 0x73, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, + 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x42, 0x51, + 0x55, 0x41, 0x41, 0x34, 0x49, 0x42, 0x41, 0x51, 0x44, 0x49, 0x6b, 0x34, + 0x45, 0x37, 0x69, 0x62, 0x53, 0x76, 0x75, 0x49, 0x51, 0x53, 0x54, 0x49, + 0x33, 0x53, 0x38, 0x4e, 0x74, 0x77, 0x75, 0x6c, 0x65, 0x47, 0x46, 0x54, + 0x51, 0x51, 0x75, 0x53, 0x39, 0x2f, 0x0a, 0x48, 0x72, 0x43, 0x6f, 0x69, + 0x57, 0x43, 0x68, 0x69, 0x73, 0x4a, 0x33, 0x44, 0x46, 0x42, 0x4b, 0x6d, + 0x77, 0x43, 0x4c, 0x32, 0x49, 0x76, 0x30, 0x51, 0x65, 0x4c, 0x51, 0x67, + 0x34, 0x70, 0x4b, 0x48, 0x42, 0x51, 0x47, 0x73, 0x4b, 0x4e, 0x6f, 0x42, + 0x58, 0x41, 0x78, 0x4d, 0x4b, 0x64, 0x54, 0x6d, 0x77, 0x37, 0x70, 0x53, + 0x71, 0x42, 0x59, 0x61, 0x57, 0x63, 0x4f, 0x72, 0x70, 0x33, 0x32, 0x0a, + 0x70, 0x53, 0x78, 0x42, 0x76, 0x7a, 0x77, 0x47, 0x61, 0x2b, 0x52, 0x5a, + 0x7a, 0x47, 0x30, 0x51, 0x38, 0x5a, 0x5a, 0x76, 0x48, 0x39, 0x2f, 0x30, + 0x42, 0x41, 0x4b, 0x6b, 0x6e, 0x30, 0x55, 0x2b, 0x79, 0x4e, 0x6a, 0x36, + 0x4e, 0x6b, 0x5a, 0x45, 0x55, 0x44, 0x2b, 0x43, 0x6c, 0x35, 0x45, 0x66, + 0x4b, 0x4e, 0x73, 0x59, 0x45, 0x59, 0x77, 0x71, 0x35, 0x47, 0x57, 0x44, + 0x56, 0x78, 0x49, 0x53, 0x0a, 0x6a, 0x42, 0x63, 0x2f, 0x6c, 0x44, 0x62, + 0x2b, 0x58, 0x62, 0x44, 0x41, 0x42, 0x48, 0x63, 0x54, 0x75, 0x50, 0x51, + 0x56, 0x31, 0x54, 0x38, 0x34, 0x7a, 0x4a, 0x51, 0x36, 0x56, 0x64, 0x43, + 0x73, 0x6d, 0x50, 0x57, 0x36, 0x41, 0x46, 0x2f, 0x67, 0x68, 0x68, 0x6d, + 0x42, 0x65, 0x43, 0x38, 0x6f, 0x77, 0x48, 0x37, 0x54, 0x7a, 0x45, 0x49, + 0x4b, 0x39, 0x61, 0x35, 0x51, 0x6f, 0x4e, 0x45, 0x2b, 0x0a, 0x78, 0x71, + 0x46, 0x78, 0x37, 0x44, 0x2b, 0x67, 0x49, 0x49, 0x78, 0x6d, 0x4f, 0x6f, + 0x6d, 0x30, 0x6a, 0x74, 0x54, 0x59, 0x73, 0x55, 0x30, 0x6c, 0x52, 0x2b, + 0x34, 0x76, 0x69, 0x4d, 0x69, 0x31, 0x34, 0x51, 0x56, 0x46, 0x77, 0x4c, + 0x34, 0x55, 0x63, 0x64, 0x35, 0x36, 0x2f, 0x59, 0x35, 0x37, 0x66, 0x55, + 0x30, 0x49, 0x6c, 0x71, 0x55, 0x53, 0x63, 0x2f, 0x41, 0x74, 0x79, 0x6a, + 0x63, 0x6e, 0x0a, 0x64, 0x42, 0x49, 0x6e, 0x54, 0x4d, 0x75, 0x32, 0x6c, + 0x2b, 0x6e, 0x5a, 0x72, 0x67, 0x68, 0x74, 0x57, 0x6a, 0x6c, 0x41, 0x33, + 0x51, 0x56, 0x48, 0x64, 0x57, 0x70, 0x61, 0x49, 0x62, 0x4f, 0x6a, 0x47, + 0x4d, 0x39, 0x4f, 0x39, 0x79, 0x35, 0x58, 0x74, 0x35, 0x68, 0x77, 0x58, + 0x73, 0x6a, 0x45, 0x65, 0x4c, 0x42, 0x69, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, + 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, + 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, + 0x55, 0x54, 0x4e, 0x20, 0x2d, 0x20, 0x44, 0x41, 0x54, 0x41, 0x43, 0x6f, + 0x72, 0x70, 0x20, 0x53, 0x47, 0x43, 0x20, 0x4f, 0x3d, 0x54, 0x68, 0x65, + 0x20, 0x55, 0x53, 0x45, 0x52, 0x54, 0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, + 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x4f, 0x55, 0x3d, 0x68, 0x74, + 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, 0x23, + 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, + 0x3d, 0x55, 0x54, 0x4e, 0x20, 0x2d, 0x20, 0x44, 0x41, 0x54, 0x41, 0x43, + 0x6f, 0x72, 0x70, 0x20, 0x53, 0x47, 0x43, 0x20, 0x4f, 0x3d, 0x54, 0x68, + 0x65, 0x20, 0x55, 0x53, 0x45, 0x52, 0x54, 0x52, 0x55, 0x53, 0x54, 0x20, + 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x4f, 0x55, 0x3d, 0x68, + 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, + 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x55, 0x54, + 0x4e, 0x20, 0x44, 0x41, 0x54, 0x41, 0x43, 0x6f, 0x72, 0x70, 0x20, 0x53, + 0x47, 0x43, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x22, 0x0a, + 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x39, 0x31, + 0x33, 0x37, 0x34, 0x32, 0x39, 0x34, 0x35, 0x34, 0x32, 0x38, 0x38, 0x34, + 0x36, 0x38, 0x39, 0x38, 0x35, 0x35, 0x31, 0x36, 0x37, 0x35, 0x37, 0x37, + 0x36, 0x38, 0x30, 0x32, 0x34, 0x31, 0x30, 0x37, 0x37, 0x36, 0x30, 0x39, + 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x62, 0x33, 0x3a, 0x61, + 0x35, 0x3a, 0x33, 0x65, 0x3a, 0x37, 0x37, 0x3a, 0x32, 0x31, 0x3a, 0x36, + 0x64, 0x3a, 0x61, 0x63, 0x3a, 0x34, 0x61, 0x3a, 0x63, 0x30, 0x3a, 0x63, + 0x39, 0x3a, 0x66, 0x62, 0x3a, 0x64, 0x35, 0x3a, 0x34, 0x31, 0x3a, 0x33, + 0x64, 0x3a, 0x63, 0x61, 0x3a, 0x30, 0x36, 0x0a, 0x23, 0x20, 0x53, 0x48, + 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x3a, 0x20, 0x35, 0x38, 0x3a, 0x31, 0x31, 0x3a, 0x39, 0x66, + 0x3a, 0x30, 0x65, 0x3a, 0x31, 0x32, 0x3a, 0x38, 0x32, 0x3a, 0x38, 0x37, + 0x3a, 0x65, 0x61, 0x3a, 0x35, 0x30, 0x3a, 0x66, 0x64, 0x3a, 0x64, 0x39, + 0x3a, 0x38, 0x37, 0x3a, 0x34, 0x35, 0x3a, 0x36, 0x66, 0x3a, 0x34, 0x66, + 0x3a, 0x37, 0x38, 0x3a, 0x64, 0x63, 0x3a, 0x66, 0x61, 0x3a, 0x64, 0x36, + 0x3a, 0x64, 0x34, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, + 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, + 0x3a, 0x20, 0x38, 0x35, 0x3a, 0x66, 0x62, 0x3a, 0x32, 0x66, 0x3a, 0x39, + 0x31, 0x3a, 0x64, 0x64, 0x3a, 0x31, 0x32, 0x3a, 0x32, 0x37, 0x3a, 0x35, + 0x61, 0x3a, 0x30, 0x31, 0x3a, 0x34, 0x35, 0x3a, 0x62, 0x36, 0x3a, 0x33, + 0x36, 0x3a, 0x35, 0x33, 0x3a, 0x34, 0x66, 0x3a, 0x38, 0x34, 0x3a, 0x30, + 0x32, 0x3a, 0x34, 0x61, 0x3a, 0x64, 0x36, 0x3a, 0x38, 0x62, 0x3a, 0x36, + 0x39, 0x3a, 0x62, 0x38, 0x3a, 0x65, 0x65, 0x3a, 0x38, 0x38, 0x3a, 0x36, + 0x38, 0x3a, 0x34, 0x66, 0x3a, 0x66, 0x37, 0x3a, 0x31, 0x31, 0x3a, 0x33, + 0x37, 0x3a, 0x35, 0x38, 0x3a, 0x30, 0x35, 0x3a, 0x62, 0x33, 0x3a, 0x34, + 0x38, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, + 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x45, 0x58, 0x6a, + 0x43, 0x43, 0x41, 0x30, 0x61, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, + 0x49, 0x51, 0x52, 0x4c, 0x34, 0x4d, 0x69, 0x31, 0x41, 0x41, 0x49, 0x62, + 0x51, 0x52, 0x30, 0x79, 0x70, 0x6f, 0x42, 0x71, 0x6d, 0x74, 0x61, 0x54, + 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, + 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x43, 0x42, 0x0a, 0x6b, + 0x7a, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, + 0x68, 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, 0x43, 0x7a, 0x41, 0x4a, 0x42, + 0x67, 0x4e, 0x56, 0x42, 0x41, 0x67, 0x54, 0x41, 0x6c, 0x56, 0x55, 0x4d, + 0x52, 0x63, 0x77, 0x46, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x48, 0x45, + 0x77, 0x35, 0x54, 0x59, 0x57, 0x78, 0x30, 0x49, 0x45, 0x78, 0x68, 0x61, + 0x32, 0x55, 0x67, 0x0a, 0x51, 0x32, 0x6c, 0x30, 0x65, 0x54, 0x45, 0x65, + 0x4d, 0x42, 0x77, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x56, + 0x56, 0x47, 0x68, 0x6c, 0x49, 0x46, 0x56, 0x54, 0x52, 0x56, 0x4a, 0x55, + 0x55, 0x6c, 0x56, 0x54, 0x56, 0x43, 0x42, 0x4f, 0x5a, 0x58, 0x52, 0x33, + 0x62, 0x33, 0x4a, 0x72, 0x4d, 0x53, 0x45, 0x77, 0x48, 0x77, 0x59, 0x44, + 0x56, 0x51, 0x51, 0x4c, 0x45, 0x78, 0x68, 0x6f, 0x0a, 0x64, 0x48, 0x52, + 0x77, 0x4f, 0x69, 0x38, 0x76, 0x64, 0x33, 0x64, 0x33, 0x4c, 0x6e, 0x56, + 0x7a, 0x5a, 0x58, 0x4a, 0x30, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x35, + 0x6a, 0x62, 0x32, 0x30, 0x78, 0x47, 0x7a, 0x41, 0x5a, 0x42, 0x67, 0x4e, + 0x56, 0x42, 0x41, 0x4d, 0x54, 0x45, 0x6c, 0x56, 0x55, 0x54, 0x69, 0x41, + 0x74, 0x49, 0x45, 0x52, 0x42, 0x56, 0x45, 0x46, 0x44, 0x62, 0x33, 0x4a, + 0x77, 0x0a, 0x49, 0x46, 0x4e, 0x48, 0x51, 0x7a, 0x41, 0x65, 0x46, 0x77, + 0x30, 0x35, 0x4f, 0x54, 0x41, 0x32, 0x4d, 0x6a, 0x51, 0x78, 0x4f, 0x44, + 0x55, 0x33, 0x4d, 0x6a, 0x46, 0x61, 0x46, 0x77, 0x30, 0x78, 0x4f, 0x54, + 0x41, 0x32, 0x4d, 0x6a, 0x51, 0x78, 0x4f, 0x54, 0x41, 0x32, 0x4d, 0x7a, + 0x42, 0x61, 0x4d, 0x49, 0x47, 0x54, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, + 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x0a, 0x45, 0x77, 0x4a, 0x56, 0x55, + 0x7a, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, + 0x42, 0x4d, 0x43, 0x56, 0x56, 0x51, 0x78, 0x46, 0x7a, 0x41, 0x56, 0x42, + 0x67, 0x4e, 0x56, 0x42, 0x41, 0x63, 0x54, 0x44, 0x6c, 0x4e, 0x68, 0x62, + 0x48, 0x51, 0x67, 0x54, 0x47, 0x46, 0x72, 0x5a, 0x53, 0x42, 0x44, 0x61, + 0x58, 0x52, 0x35, 0x4d, 0x52, 0x34, 0x77, 0x48, 0x41, 0x59, 0x44, 0x0a, + 0x56, 0x51, 0x51, 0x4b, 0x45, 0x78, 0x56, 0x55, 0x61, 0x47, 0x55, 0x67, + 0x56, 0x56, 0x4e, 0x46, 0x55, 0x6c, 0x52, 0x53, 0x56, 0x56, 0x4e, 0x55, + 0x49, 0x45, 0x35, 0x6c, 0x64, 0x48, 0x64, 0x76, 0x63, 0x6d, 0x73, 0x78, + 0x49, 0x54, 0x41, 0x66, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, + 0x47, 0x47, 0x68, 0x30, 0x64, 0x48, 0x41, 0x36, 0x4c, 0x79, 0x39, 0x33, + 0x64, 0x33, 0x63, 0x75, 0x0a, 0x64, 0x58, 0x4e, 0x6c, 0x63, 0x6e, 0x52, + 0x79, 0x64, 0x58, 0x4e, 0x30, 0x4c, 0x6d, 0x4e, 0x76, 0x62, 0x54, 0x45, + 0x62, 0x4d, 0x42, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, + 0x53, 0x56, 0x56, 0x52, 0x4f, 0x49, 0x43, 0x30, 0x67, 0x52, 0x45, 0x46, + 0x55, 0x51, 0x55, 0x4e, 0x76, 0x63, 0x6e, 0x41, 0x67, 0x55, 0x30, 0x64, + 0x44, 0x4d, 0x49, 0x49, 0x42, 0x49, 0x6a, 0x41, 0x4e, 0x0a, 0x42, 0x67, + 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, + 0x45, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x51, 0x38, 0x41, 0x4d, 0x49, + 0x49, 0x42, 0x43, 0x67, 0x4b, 0x43, 0x41, 0x51, 0x45, 0x41, 0x33, 0x2b, + 0x35, 0x59, 0x45, 0x4b, 0x49, 0x72, 0x62, 0x6c, 0x58, 0x45, 0x6a, 0x72, + 0x38, 0x75, 0x52, 0x67, 0x6e, 0x6e, 0x34, 0x41, 0x67, 0x50, 0x4c, 0x69, + 0x74, 0x36, 0x0a, 0x45, 0x35, 0x51, 0x62, 0x76, 0x66, 0x61, 0x32, 0x67, + 0x49, 0x35, 0x6c, 0x42, 0x5a, 0x4d, 0x41, 0x48, 0x72, 0x79, 0x76, 0x34, + 0x67, 0x2b, 0x4f, 0x47, 0x51, 0x30, 0x53, 0x52, 0x2b, 0x79, 0x73, 0x72, + 0x61, 0x50, 0x36, 0x4c, 0x6e, 0x44, 0x34, 0x33, 0x6d, 0x37, 0x37, 0x56, + 0x6b, 0x49, 0x56, 0x6e, 0x69, 0x35, 0x63, 0x37, 0x79, 0x50, 0x65, 0x49, + 0x62, 0x6b, 0x46, 0x64, 0x69, 0x63, 0x5a, 0x0a, 0x44, 0x30, 0x2f, 0x57, + 0x77, 0x35, 0x79, 0x30, 0x76, 0x70, 0x51, 0x5a, 0x59, 0x2f, 0x4b, 0x6d, + 0x45, 0x51, 0x72, 0x72, 0x55, 0x30, 0x69, 0x63, 0x76, 0x76, 0x49, 0x70, + 0x4f, 0x78, 0x62, 0x6f, 0x47, 0x71, 0x42, 0x4d, 0x70, 0x73, 0x6e, 0x30, + 0x47, 0x46, 0x6c, 0x6f, 0x77, 0x48, 0x44, 0x79, 0x55, 0x77, 0x44, 0x41, + 0x58, 0x6c, 0x43, 0x43, 0x70, 0x56, 0x5a, 0x76, 0x4e, 0x76, 0x6c, 0x4b, + 0x0a, 0x34, 0x45, 0x53, 0x47, 0x6f, 0x45, 0x31, 0x4f, 0x31, 0x6b, 0x64, + 0x75, 0x53, 0x55, 0x72, 0x4c, 0x5a, 0x39, 0x65, 0x6d, 0x78, 0x41, 0x57, + 0x35, 0x6a, 0x68, 0x37, 0x30, 0x2f, 0x50, 0x2f, 0x4e, 0x35, 0x7a, 0x62, + 0x67, 0x6e, 0x41, 0x56, 0x73, 0x73, 0x6a, 0x4d, 0x69, 0x46, 0x64, 0x43, + 0x30, 0x34, 0x4d, 0x77, 0x58, 0x77, 0x4c, 0x4c, 0x41, 0x39, 0x50, 0x34, + 0x79, 0x50, 0x79, 0x6b, 0x71, 0x0a, 0x6c, 0x58, 0x76, 0x59, 0x38, 0x71, + 0x64, 0x4f, 0x44, 0x31, 0x52, 0x38, 0x6f, 0x51, 0x32, 0x41, 0x73, 0x77, + 0x6b, 0x44, 0x77, 0x66, 0x39, 0x63, 0x33, 0x56, 0x36, 0x61, 0x50, 0x72, + 0x79, 0x75, 0x76, 0x45, 0x65, 0x4b, 0x61, 0x71, 0x35, 0x78, 0x79, 0x68, + 0x2b, 0x78, 0x4b, 0x72, 0x68, 0x66, 0x51, 0x67, 0x55, 0x4c, 0x37, 0x45, + 0x59, 0x77, 0x30, 0x58, 0x49, 0x4c, 0x79, 0x75, 0x6c, 0x57, 0x0a, 0x62, + 0x66, 0x58, 0x76, 0x33, 0x33, 0x69, 0x2b, 0x59, 0x62, 0x71, 0x79, 0x70, + 0x61, 0x34, 0x45, 0x54, 0x4c, 0x79, 0x6f, 0x72, 0x47, 0x6b, 0x56, 0x6c, + 0x37, 0x33, 0x76, 0x36, 0x37, 0x53, 0x4d, 0x76, 0x7a, 0x58, 0x34, 0x31, + 0x4d, 0x50, 0x52, 0x4b, 0x41, 0x35, 0x63, 0x4f, 0x70, 0x39, 0x77, 0x47, + 0x44, 0x4d, 0x67, 0x64, 0x38, 0x53, 0x69, 0x72, 0x77, 0x49, 0x44, 0x41, + 0x51, 0x41, 0x42, 0x0a, 0x6f, 0x34, 0x47, 0x72, 0x4d, 0x49, 0x47, 0x6f, + 0x4d, 0x41, 0x73, 0x47, 0x41, 0x31, 0x55, 0x64, 0x44, 0x77, 0x51, 0x45, + 0x41, 0x77, 0x49, 0x42, 0x78, 0x6a, 0x41, 0x50, 0x42, 0x67, 0x4e, 0x56, + 0x48, 0x52, 0x4d, 0x42, 0x41, 0x66, 0x38, 0x45, 0x42, 0x54, 0x41, 0x44, + 0x41, 0x51, 0x48, 0x2f, 0x4d, 0x42, 0x30, 0x47, 0x41, 0x31, 0x55, 0x64, + 0x44, 0x67, 0x51, 0x57, 0x42, 0x42, 0x52, 0x54, 0x0a, 0x4d, 0x74, 0x47, + 0x7a, 0x7a, 0x33, 0x2f, 0x36, 0x34, 0x50, 0x47, 0x67, 0x58, 0x59, 0x56, + 0x4f, 0x6b, 0x74, 0x4b, 0x65, 0x52, 0x52, 0x32, 0x30, 0x54, 0x7a, 0x41, + 0x39, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x52, 0x38, 0x45, 0x4e, 0x6a, 0x41, + 0x30, 0x4d, 0x44, 0x4b, 0x67, 0x4d, 0x4b, 0x41, 0x75, 0x68, 0x69, 0x78, + 0x6f, 0x64, 0x48, 0x52, 0x77, 0x4f, 0x69, 0x38, 0x76, 0x59, 0x33, 0x4a, + 0x73, 0x0a, 0x4c, 0x6e, 0x56, 0x7a, 0x5a, 0x58, 0x4a, 0x30, 0x63, 0x6e, + 0x56, 0x7a, 0x64, 0x43, 0x35, 0x6a, 0x62, 0x32, 0x30, 0x76, 0x56, 0x56, + 0x52, 0x4f, 0x4c, 0x55, 0x52, 0x42, 0x56, 0x45, 0x46, 0x44, 0x62, 0x33, + 0x4a, 0x77, 0x55, 0x30, 0x64, 0x44, 0x4c, 0x6d, 0x4e, 0x79, 0x62, 0x44, + 0x41, 0x71, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x53, 0x55, 0x45, 0x49, 0x7a, + 0x41, 0x68, 0x42, 0x67, 0x67, 0x72, 0x0a, 0x42, 0x67, 0x45, 0x46, 0x42, + 0x51, 0x63, 0x44, 0x41, 0x51, 0x59, 0x4b, 0x4b, 0x77, 0x59, 0x42, 0x42, + 0x41, 0x47, 0x43, 0x4e, 0x77, 0x6f, 0x44, 0x41, 0x77, 0x59, 0x4a, 0x59, + 0x49, 0x5a, 0x49, 0x41, 0x59, 0x62, 0x34, 0x51, 0x67, 0x51, 0x42, 0x4d, + 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, + 0x51, 0x45, 0x42, 0x42, 0x51, 0x55, 0x41, 0x41, 0x34, 0x49, 0x42, 0x0a, + 0x41, 0x51, 0x41, 0x6e, 0x4e, 0x5a, 0x63, 0x41, 0x69, 0x6f, 0x73, 0x6f, + 0x76, 0x63, 0x59, 0x7a, 0x4d, 0x42, 0x34, 0x70, 0x2f, 0x4f, 0x4c, 0x33, + 0x31, 0x5a, 0x6a, 0x55, 0x51, 0x4c, 0x74, 0x67, 0x79, 0x72, 0x2b, 0x72, + 0x46, 0x79, 0x77, 0x4a, 0x4e, 0x6e, 0x39, 0x51, 0x2b, 0x6b, 0x48, 0x63, + 0x72, 0x70, 0x59, 0x36, 0x43, 0x69, 0x4d, 0x2b, 0x69, 0x56, 0x6e, 0x4a, + 0x6f, 0x77, 0x66, 0x74, 0x0a, 0x47, 0x7a, 0x65, 0x74, 0x2f, 0x48, 0x79, + 0x2b, 0x55, 0x55, 0x6c, 0x61, 0x33, 0x6a, 0x6f, 0x4b, 0x56, 0x41, 0x67, + 0x57, 0x52, 0x63, 0x4b, 0x5a, 0x73, 0x59, 0x66, 0x4e, 0x6a, 0x47, 0x6a, + 0x67, 0x61, 0x51, 0x50, 0x70, 0x78, 0x45, 0x36, 0x59, 0x73, 0x6a, 0x75, + 0x4d, 0x46, 0x72, 0x4d, 0x4f, 0x6f, 0x41, 0x79, 0x59, 0x55, 0x4a, 0x75, + 0x54, 0x71, 0x58, 0x41, 0x4a, 0x79, 0x43, 0x79, 0x6a, 0x0a, 0x6a, 0x39, + 0x38, 0x43, 0x35, 0x4f, 0x42, 0x78, 0x4f, 0x76, 0x47, 0x30, 0x49, 0x33, + 0x4b, 0x67, 0x71, 0x67, 0x48, 0x66, 0x33, 0x35, 0x67, 0x2b, 0x46, 0x46, + 0x43, 0x67, 0x4d, 0x53, 0x61, 0x39, 0x4b, 0x4f, 0x6c, 0x61, 0x4d, 0x43, + 0x5a, 0x31, 0x2b, 0x58, 0x74, 0x67, 0x48, 0x49, 0x33, 0x7a, 0x7a, 0x56, + 0x41, 0x6d, 0x62, 0x51, 0x51, 0x6e, 0x6d, 0x74, 0x2f, 0x56, 0x44, 0x55, + 0x56, 0x48, 0x0a, 0x4b, 0x57, 0x73, 0x73, 0x35, 0x6e, 0x62, 0x5a, 0x71, + 0x53, 0x6c, 0x39, 0x4d, 0x74, 0x33, 0x4a, 0x4e, 0x6a, 0x79, 0x39, 0x72, + 0x6a, 0x58, 0x78, 0x45, 0x5a, 0x34, 0x64, 0x75, 0x35, 0x41, 0x2f, 0x45, + 0x6b, 0x64, 0x4f, 0x6a, 0x74, 0x64, 0x2b, 0x44, 0x32, 0x4a, 0x7a, 0x48, + 0x56, 0x49, 0x6d, 0x4f, 0x42, 0x77, 0x59, 0x53, 0x66, 0x30, 0x77, 0x64, + 0x4a, 0x72, 0x45, 0x35, 0x53, 0x49, 0x76, 0x0a, 0x32, 0x4d, 0x43, 0x4e, + 0x37, 0x5a, 0x46, 0x36, 0x54, 0x41, 0x43, 0x50, 0x63, 0x6e, 0x39, 0x64, + 0x32, 0x74, 0x30, 0x62, 0x69, 0x30, 0x56, 0x72, 0x35, 0x39, 0x31, 0x70, + 0x6c, 0x36, 0x6a, 0x46, 0x56, 0x6b, 0x77, 0x50, 0x44, 0x50, 0x61, 0x66, + 0x65, 0x70, 0x45, 0x33, 0x39, 0x70, 0x65, 0x43, 0x34, 0x4e, 0x31, 0x78, + 0x61, 0x66, 0x39, 0x32, 0x50, 0x32, 0x42, 0x4e, 0x50, 0x4d, 0x2f, 0x33, + 0x0a, 0x6d, 0x66, 0x6e, 0x47, 0x56, 0x2f, 0x54, 0x4a, 0x56, 0x54, 0x6c, + 0x34, 0x75, 0x69, 0x78, 0x35, 0x79, 0x61, 0x61, 0x49, 0x4b, 0x2f, 0x51, + 0x49, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, + 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, + 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x55, 0x54, 0x4e, 0x2d, 0x55, 0x53, + 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74, 0x2d, 0x48, 0x61, 0x72, 0x64, + 0x77, 0x61, 0x72, 0x65, 0x20, 0x4f, 0x3d, 0x54, 0x68, 0x65, 0x20, 0x55, + 0x53, 0x45, 0x52, 0x54, 0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, + 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x4f, 0x55, 0x3d, 0x68, 0x74, 0x74, 0x70, + 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, + 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, 0x23, 0x20, 0x53, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x55, + 0x54, 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74, + 0x2d, 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x20, 0x4f, 0x3d, + 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52, 0x54, 0x52, 0x55, 0x53, + 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x4f, 0x55, + 0x3d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, + 0x6d, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, + 0x55, 0x54, 0x4e, 0x20, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, + 0x74, 0x20, 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x20, 0x52, + 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, + 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x39, 0x31, 0x33, 0x37, 0x34, 0x32, + 0x39, 0x34, 0x35, 0x34, 0x32, 0x38, 0x38, 0x34, 0x37, 0x30, 0x34, 0x30, + 0x32, 0x32, 0x32, 0x36, 0x37, 0x30, 0x33, 0x39, 0x32, 0x32, 0x31, 0x31, + 0x38, 0x34, 0x35, 0x33, 0x31, 0x31, 0x39, 0x37, 0x0a, 0x23, 0x20, 0x4d, + 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x3a, 0x20, 0x34, 0x63, 0x3a, 0x35, 0x36, 0x3a, 0x34, 0x31, + 0x3a, 0x65, 0x35, 0x3a, 0x30, 0x64, 0x3a, 0x62, 0x62, 0x3a, 0x32, 0x62, + 0x3a, 0x65, 0x38, 0x3a, 0x63, 0x61, 0x3a, 0x61, 0x33, 0x3a, 0x65, 0x64, + 0x3a, 0x31, 0x38, 0x3a, 0x30, 0x38, 0x3a, 0x61, 0x64, 0x3a, 0x34, 0x33, + 0x3a, 0x33, 0x39, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x30, 0x34, 0x3a, 0x38, 0x33, 0x3a, 0x65, 0x64, 0x3a, 0x33, 0x33, 0x3a, + 0x39, 0x39, 0x3a, 0x61, 0x63, 0x3a, 0x33, 0x36, 0x3a, 0x30, 0x38, 0x3a, + 0x30, 0x35, 0x3a, 0x38, 0x37, 0x3a, 0x32, 0x32, 0x3a, 0x65, 0x64, 0x3a, + 0x62, 0x63, 0x3a, 0x35, 0x65, 0x3a, 0x34, 0x36, 0x3a, 0x30, 0x30, 0x3a, + 0x65, 0x33, 0x3a, 0x62, 0x65, 0x3a, 0x66, 0x39, 0x3a, 0x64, 0x37, 0x0a, + 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, + 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x36, 0x65, + 0x3a, 0x61, 0x35, 0x3a, 0x34, 0x37, 0x3a, 0x34, 0x31, 0x3a, 0x64, 0x30, + 0x3a, 0x30, 0x34, 0x3a, 0x36, 0x36, 0x3a, 0x37, 0x65, 0x3a, 0x65, 0x64, + 0x3a, 0x31, 0x62, 0x3a, 0x34, 0x38, 0x3a, 0x31, 0x36, 0x3a, 0x36, 0x33, + 0x3a, 0x34, 0x61, 0x3a, 0x61, 0x33, 0x3a, 0x61, 0x37, 0x3a, 0x39, 0x65, + 0x3a, 0x36, 0x65, 0x3a, 0x34, 0x62, 0x3a, 0x39, 0x36, 0x3a, 0x39, 0x35, + 0x3a, 0x30, 0x66, 0x3a, 0x38, 0x32, 0x3a, 0x37, 0x39, 0x3a, 0x64, 0x61, + 0x3a, 0x66, 0x63, 0x3a, 0x38, 0x64, 0x3a, 0x39, 0x62, 0x3a, 0x64, 0x38, + 0x3a, 0x38, 0x31, 0x3a, 0x32, 0x31, 0x3a, 0x33, 0x37, 0x0a, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, + 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x45, 0x64, 0x44, 0x43, 0x43, 0x41, 0x31, + 0x79, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x51, 0x52, 0x4c, + 0x34, 0x4d, 0x69, 0x31, 0x41, 0x41, 0x4a, 0x4c, 0x51, 0x52, 0x30, 0x7a, + 0x59, 0x71, 0x2f, 0x6d, 0x55, 0x4b, 0x2f, 0x54, 0x41, 0x4e, 0x42, 0x67, + 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, + 0x55, 0x46, 0x41, 0x44, 0x43, 0x42, 0x0a, 0x6c, 0x7a, 0x45, 0x4c, 0x4d, + 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x56, + 0x56, 0x4d, 0x78, 0x43, 0x7a, 0x41, 0x4a, 0x42, 0x67, 0x4e, 0x56, 0x42, + 0x41, 0x67, 0x54, 0x41, 0x6c, 0x56, 0x55, 0x4d, 0x52, 0x63, 0x77, 0x46, + 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x48, 0x45, 0x77, 0x35, 0x54, 0x59, + 0x57, 0x78, 0x30, 0x49, 0x45, 0x78, 0x68, 0x61, 0x32, 0x55, 0x67, 0x0a, + 0x51, 0x32, 0x6c, 0x30, 0x65, 0x54, 0x45, 0x65, 0x4d, 0x42, 0x77, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x56, 0x56, 0x47, 0x68, 0x6c, + 0x49, 0x46, 0x56, 0x54, 0x52, 0x56, 0x4a, 0x55, 0x55, 0x6c, 0x56, 0x54, + 0x56, 0x43, 0x42, 0x4f, 0x5a, 0x58, 0x52, 0x33, 0x62, 0x33, 0x4a, 0x72, + 0x4d, 0x53, 0x45, 0x77, 0x48, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, + 0x45, 0x78, 0x68, 0x6f, 0x0a, 0x64, 0x48, 0x52, 0x77, 0x4f, 0x69, 0x38, + 0x76, 0x64, 0x33, 0x64, 0x33, 0x4c, 0x6e, 0x56, 0x7a, 0x5a, 0x58, 0x4a, + 0x30, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x35, 0x6a, 0x62, 0x32, 0x30, + 0x78, 0x48, 0x7a, 0x41, 0x64, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x4d, + 0x54, 0x46, 0x6c, 0x56, 0x55, 0x54, 0x69, 0x31, 0x56, 0x55, 0x30, 0x56, + 0x53, 0x52, 0x6d, 0x6c, 0x79, 0x63, 0x33, 0x51, 0x74, 0x0a, 0x53, 0x47, + 0x46, 0x79, 0x5a, 0x48, 0x64, 0x68, 0x63, 0x6d, 0x55, 0x77, 0x48, 0x68, + 0x63, 0x4e, 0x4f, 0x54, 0x6b, 0x77, 0x4e, 0x7a, 0x41, 0x35, 0x4d, 0x54, + 0x67, 0x78, 0x4d, 0x44, 0x51, 0x79, 0x57, 0x68, 0x63, 0x4e, 0x4d, 0x54, + 0x6b, 0x77, 0x4e, 0x7a, 0x41, 0x35, 0x4d, 0x54, 0x67, 0x78, 0x4f, 0x54, + 0x49, 0x79, 0x57, 0x6a, 0x43, 0x42, 0x6c, 0x7a, 0x45, 0x4c, 0x4d, 0x41, + 0x6b, 0x47, 0x0a, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x56, + 0x56, 0x4d, 0x78, 0x43, 0x7a, 0x41, 0x4a, 0x42, 0x67, 0x4e, 0x56, 0x42, + 0x41, 0x67, 0x54, 0x41, 0x6c, 0x56, 0x55, 0x4d, 0x52, 0x63, 0x77, 0x46, + 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x48, 0x45, 0x77, 0x35, 0x54, 0x59, + 0x57, 0x78, 0x30, 0x49, 0x45, 0x78, 0x68, 0x61, 0x32, 0x55, 0x67, 0x51, + 0x32, 0x6c, 0x30, 0x65, 0x54, 0x45, 0x65, 0x0a, 0x4d, 0x42, 0x77, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x56, 0x56, 0x47, 0x68, 0x6c, + 0x49, 0x46, 0x56, 0x54, 0x52, 0x56, 0x4a, 0x55, 0x55, 0x6c, 0x56, 0x54, + 0x56, 0x43, 0x42, 0x4f, 0x5a, 0x58, 0x52, 0x33, 0x62, 0x33, 0x4a, 0x72, + 0x4d, 0x53, 0x45, 0x77, 0x48, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, + 0x45, 0x78, 0x68, 0x6f, 0x64, 0x48, 0x52, 0x77, 0x4f, 0x69, 0x38, 0x76, + 0x0a, 0x64, 0x33, 0x64, 0x33, 0x4c, 0x6e, 0x56, 0x7a, 0x5a, 0x58, 0x4a, + 0x30, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x35, 0x6a, 0x62, 0x32, 0x30, + 0x78, 0x48, 0x7a, 0x41, 0x64, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x4d, + 0x54, 0x46, 0x6c, 0x56, 0x55, 0x54, 0x69, 0x31, 0x56, 0x55, 0x30, 0x56, + 0x53, 0x52, 0x6d, 0x6c, 0x79, 0x63, 0x33, 0x51, 0x74, 0x53, 0x47, 0x46, + 0x79, 0x5a, 0x48, 0x64, 0x68, 0x0a, 0x63, 0x6d, 0x55, 0x77, 0x67, 0x67, + 0x45, 0x69, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, + 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x41, 0x51, 0x55, 0x41, 0x41, 0x34, + 0x49, 0x42, 0x44, 0x77, 0x41, 0x77, 0x67, 0x67, 0x45, 0x4b, 0x41, 0x6f, + 0x49, 0x42, 0x41, 0x51, 0x43, 0x78, 0x39, 0x38, 0x4d, 0x34, 0x50, 0x37, + 0x53, 0x6f, 0x66, 0x38, 0x38, 0x35, 0x67, 0x6c, 0x46, 0x6e, 0x0a, 0x30, + 0x47, 0x32, 0x66, 0x30, 0x76, 0x39, 0x59, 0x38, 0x2b, 0x65, 0x66, 0x4b, + 0x2b, 0x77, 0x4e, 0x69, 0x56, 0x53, 0x5a, 0x75, 0x54, 0x69, 0x5a, 0x46, + 0x76, 0x66, 0x67, 0x49, 0x58, 0x6c, 0x49, 0x77, 0x72, 0x74, 0x68, 0x64, + 0x42, 0x4b, 0x57, 0x48, 0x54, 0x78, 0x71, 0x63, 0x74, 0x55, 0x38, 0x45, + 0x47, 0x63, 0x36, 0x4f, 0x65, 0x30, 0x72, 0x45, 0x38, 0x31, 0x6d, 0x36, + 0x35, 0x55, 0x4a, 0x0a, 0x4d, 0x36, 0x52, 0x73, 0x6c, 0x37, 0x48, 0x6f, + 0x78, 0x75, 0x7a, 0x42, 0x64, 0x58, 0x6d, 0x63, 0x52, 0x6c, 0x36, 0x4e, + 0x71, 0x39, 0x42, 0x71, 0x2f, 0x62, 0x6b, 0x71, 0x56, 0x52, 0x63, 0x51, + 0x56, 0x4c, 0x4d, 0x5a, 0x38, 0x4a, 0x72, 0x32, 0x38, 0x62, 0x46, 0x64, + 0x74, 0x71, 0x64, 0x74, 0x2b, 0x2b, 0x42, 0x78, 0x46, 0x32, 0x75, 0x69, + 0x69, 0x50, 0x73, 0x41, 0x33, 0x2f, 0x34, 0x61, 0x0a, 0x4d, 0x58, 0x63, + 0x4d, 0x6d, 0x67, 0x46, 0x36, 0x73, 0x54, 0x4c, 0x6a, 0x4b, 0x77, 0x45, + 0x48, 0x4f, 0x47, 0x37, 0x44, 0x70, 0x56, 0x34, 0x6a, 0x76, 0x45, 0x57, + 0x62, 0x65, 0x31, 0x44, 0x42, 0x79, 0x54, 0x43, 0x50, 0x32, 0x2b, 0x55, + 0x72, 0x65, 0x74, 0x4e, 0x62, 0x2b, 0x7a, 0x4e, 0x41, 0x48, 0x71, 0x44, + 0x56, 0x6d, 0x42, 0x65, 0x38, 0x69, 0x34, 0x66, 0x44, 0x69, 0x64, 0x4e, + 0x64, 0x0a, 0x6f, 0x49, 0x36, 0x79, 0x71, 0x71, 0x72, 0x32, 0x6a, 0x6d, + 0x6d, 0x49, 0x42, 0x73, 0x58, 0x36, 0x69, 0x53, 0x48, 0x7a, 0x43, 0x4a, + 0x31, 0x70, 0x4c, 0x67, 0x6b, 0x7a, 0x6d, 0x79, 0x6b, 0x4e, 0x52, 0x67, + 0x2b, 0x4d, 0x7a, 0x45, 0x6b, 0x30, 0x73, 0x47, 0x6c, 0x52, 0x76, 0x66, + 0x6b, 0x47, 0x7a, 0x57, 0x69, 0x74, 0x5a, 0x6b, 0x79, 0x38, 0x50, 0x71, + 0x78, 0x68, 0x76, 0x51, 0x71, 0x49, 0x0a, 0x44, 0x73, 0x6a, 0x66, 0x50, + 0x65, 0x35, 0x38, 0x42, 0x45, 0x79, 0x64, 0x43, 0x6c, 0x35, 0x72, 0x6b, + 0x64, 0x62, 0x75, 0x78, 0x2b, 0x30, 0x6f, 0x6a, 0x61, 0x74, 0x4e, 0x68, + 0x34, 0x6c, 0x7a, 0x30, 0x47, 0x36, 0x6b, 0x30, 0x42, 0x34, 0x57, 0x69, + 0x78, 0x54, 0x68, 0x64, 0x6b, 0x51, 0x44, 0x66, 0x32, 0x4f, 0x73, 0x35, + 0x4d, 0x31, 0x4a, 0x6e, 0x4d, 0x57, 0x53, 0x39, 0x4b, 0x73, 0x79, 0x0a, + 0x6f, 0x55, 0x68, 0x62, 0x41, 0x67, 0x4d, 0x42, 0x41, 0x41, 0x47, 0x6a, + 0x67, 0x62, 0x6b, 0x77, 0x67, 0x62, 0x59, 0x77, 0x43, 0x77, 0x59, 0x44, + 0x56, 0x52, 0x30, 0x50, 0x42, 0x41, 0x51, 0x44, 0x41, 0x67, 0x48, 0x47, + 0x4d, 0x41, 0x38, 0x47, 0x41, 0x31, 0x55, 0x64, 0x45, 0x77, 0x45, 0x42, + 0x2f, 0x77, 0x51, 0x46, 0x4d, 0x41, 0x4d, 0x42, 0x41, 0x66, 0x38, 0x77, + 0x48, 0x51, 0x59, 0x44, 0x0a, 0x56, 0x52, 0x30, 0x4f, 0x42, 0x42, 0x59, + 0x45, 0x46, 0x4b, 0x46, 0x79, 0x58, 0x79, 0x59, 0x62, 0x4b, 0x4a, 0x68, + 0x44, 0x6c, 0x56, 0x30, 0x48, 0x4e, 0x39, 0x57, 0x46, 0x6c, 0x70, 0x31, + 0x4c, 0x30, 0x73, 0x4e, 0x46, 0x4d, 0x45, 0x51, 0x47, 0x41, 0x31, 0x55, + 0x64, 0x48, 0x77, 0x51, 0x39, 0x4d, 0x44, 0x73, 0x77, 0x4f, 0x61, 0x41, + 0x33, 0x6f, 0x44, 0x57, 0x47, 0x4d, 0x32, 0x68, 0x30, 0x0a, 0x64, 0x48, + 0x41, 0x36, 0x4c, 0x79, 0x39, 0x6a, 0x63, 0x6d, 0x77, 0x75, 0x64, 0x58, + 0x4e, 0x6c, 0x63, 0x6e, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x4c, 0x6d, + 0x4e, 0x76, 0x62, 0x53, 0x39, 0x56, 0x56, 0x45, 0x34, 0x74, 0x56, 0x56, + 0x4e, 0x46, 0x55, 0x6b, 0x5a, 0x70, 0x63, 0x6e, 0x4e, 0x30, 0x4c, 0x55, + 0x68, 0x68, 0x63, 0x6d, 0x52, 0x33, 0x59, 0x58, 0x4a, 0x6c, 0x4c, 0x6d, + 0x4e, 0x79, 0x0a, 0x62, 0x44, 0x41, 0x78, 0x42, 0x67, 0x4e, 0x56, 0x48, + 0x53, 0x55, 0x45, 0x4b, 0x6a, 0x41, 0x6f, 0x42, 0x67, 0x67, 0x72, 0x42, + 0x67, 0x45, 0x46, 0x42, 0x51, 0x63, 0x44, 0x41, 0x51, 0x59, 0x49, 0x4b, + 0x77, 0x59, 0x42, 0x42, 0x51, 0x55, 0x48, 0x41, 0x77, 0x55, 0x47, 0x43, + 0x43, 0x73, 0x47, 0x41, 0x51, 0x55, 0x46, 0x42, 0x77, 0x4d, 0x47, 0x42, + 0x67, 0x67, 0x72, 0x42, 0x67, 0x45, 0x46, 0x0a, 0x42, 0x51, 0x63, 0x44, + 0x42, 0x7a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, + 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x41, 0x4f, 0x43, + 0x41, 0x51, 0x45, 0x41, 0x52, 0x78, 0x6b, 0x50, 0x33, 0x6e, 0x54, 0x47, + 0x6d, 0x5a, 0x65, 0x76, 0x2f, 0x4b, 0x30, 0x6f, 0x58, 0x6e, 0x57, 0x4f, + 0x36, 0x79, 0x31, 0x6e, 0x37, 0x6b, 0x35, 0x37, 0x4b, 0x39, 0x63, 0x4d, + 0x0a, 0x2f, 0x2f, 0x62, 0x65, 0x79, 0x31, 0x57, 0x69, 0x43, 0x75, 0x46, + 0x4d, 0x56, 0x47, 0x57, 0x54, 0x59, 0x47, 0x75, 0x66, 0x45, 0x70, 0x79, + 0x74, 0x58, 0x6f, 0x4d, 0x73, 0x36, 0x31, 0x71, 0x75, 0x77, 0x4f, 0x51, + 0x74, 0x39, 0x41, 0x42, 0x6a, 0x48, 0x62, 0x6a, 0x41, 0x62, 0x50, 0x4c, + 0x50, 0x53, 0x62, 0x74, 0x4e, 0x6b, 0x32, 0x38, 0x47, 0x70, 0x67, 0x6f, + 0x69, 0x73, 0x6b, 0x6c, 0x69, 0x0a, 0x43, 0x45, 0x37, 0x2f, 0x79, 0x4d, + 0x67, 0x55, 0x73, 0x6f, 0x67, 0x57, 0x58, 0x65, 0x63, 0x42, 0x35, 0x42, + 0x4b, 0x56, 0x35, 0x55, 0x55, 0x30, 0x73, 0x34, 0x74, 0x70, 0x76, 0x63, + 0x2b, 0x30, 0x68, 0x59, 0x39, 0x31, 0x55, 0x5a, 0x35, 0x39, 0x4f, 0x6a, + 0x67, 0x36, 0x46, 0x45, 0x67, 0x53, 0x78, 0x76, 0x75, 0x6e, 0x4f, 0x78, + 0x71, 0x4e, 0x44, 0x59, 0x4a, 0x41, 0x42, 0x2b, 0x67, 0x45, 0x0a, 0x43, + 0x4a, 0x43, 0x68, 0x69, 0x63, 0x73, 0x5a, 0x55, 0x4e, 0x2f, 0x4b, 0x48, + 0x41, 0x47, 0x38, 0x48, 0x51, 0x51, 0x5a, 0x65, 0x78, 0x42, 0x32, 0x6c, + 0x7a, 0x76, 0x75, 0x6b, 0x4a, 0x44, 0x4b, 0x78, 0x41, 0x34, 0x66, 0x46, + 0x6d, 0x35, 0x31, 0x37, 0x7a, 0x50, 0x34, 0x30, 0x32, 0x39, 0x62, 0x48, + 0x70, 0x62, 0x6a, 0x34, 0x48, 0x52, 0x33, 0x64, 0x48, 0x75, 0x4b, 0x6f, + 0x6d, 0x34, 0x74, 0x0a, 0x33, 0x58, 0x62, 0x57, 0x4f, 0x54, 0x43, 0x43, + 0x38, 0x4b, 0x75, 0x63, 0x55, 0x76, 0x49, 0x71, 0x78, 0x36, 0x39, 0x4a, + 0x58, 0x6e, 0x37, 0x48, 0x61, 0x4f, 0x57, 0x43, 0x67, 0x63, 0x68, 0x71, + 0x4a, 0x2f, 0x6b, 0x6e, 0x69, 0x43, 0x72, 0x56, 0x57, 0x46, 0x43, 0x56, + 0x48, 0x2f, 0x41, 0x37, 0x48, 0x46, 0x65, 0x37, 0x66, 0x52, 0x51, 0x35, + 0x59, 0x69, 0x75, 0x61, 0x79, 0x5a, 0x53, 0x53, 0x0a, 0x4b, 0x71, 0x4d, + 0x69, 0x44, 0x50, 0x2b, 0x4a, 0x4a, 0x6e, 0x31, 0x66, 0x49, 0x79, 0x74, + 0x48, 0x31, 0x78, 0x55, 0x64, 0x71, 0x57, 0x71, 0x65, 0x55, 0x51, 0x30, + 0x71, 0x55, 0x5a, 0x36, 0x42, 0x2b, 0x64, 0x51, 0x37, 0x58, 0x6e, 0x41, + 0x53, 0x66, 0x78, 0x41, 0x79, 0x6e, 0x42, 0x36, 0x37, 0x6e, 0x66, 0x68, + 0x6d, 0x71, 0x41, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, + 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, + 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, + 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x58, 0x52, + 0x61, 0x6d, 0x70, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x4f, + 0x3d, 0x58, 0x52, 0x61, 0x6d, 0x70, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, + 0x69, 0x74, 0x79, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x20, 0x49, 0x6e, 0x63, 0x20, 0x4f, 0x55, 0x3d, 0x77, 0x77, 0x77, 0x2e, + 0x78, 0x72, 0x61, 0x6d, 0x70, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, + 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x58, 0x52, 0x61, 0x6d, + 0x70, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x43, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x4f, 0x3d, 0x58, + 0x52, 0x61, 0x6d, 0x70, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, + 0x79, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x49, + 0x6e, 0x63, 0x20, 0x4f, 0x55, 0x3d, 0x77, 0x77, 0x77, 0x2e, 0x78, 0x72, + 0x61, 0x6d, 0x70, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2e, + 0x63, 0x6f, 0x6d, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x58, 0x52, 0x61, 0x6d, 0x70, 0x20, 0x47, 0x6c, 0x6f, 0x62, + 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x22, 0x0a, + 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x31, 0x30, + 0x37, 0x31, 0x30, 0x38, 0x39, 0x30, 0x38, 0x38, 0x30, 0x33, 0x36, 0x35, + 0x31, 0x35, 0x30, 0x39, 0x36, 0x39, 0x32, 0x39, 0x38, 0x30, 0x31, 0x32, + 0x34, 0x32, 0x33, 0x33, 0x37, 0x34, 0x35, 0x30, 0x31, 0x34, 0x39, 0x35, + 0x37, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, + 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x61, 0x31, 0x3a, + 0x30, 0x62, 0x3a, 0x34, 0x34, 0x3a, 0x62, 0x33, 0x3a, 0x63, 0x61, 0x3a, + 0x31, 0x30, 0x3a, 0x64, 0x38, 0x3a, 0x30, 0x30, 0x3a, 0x36, 0x65, 0x3a, + 0x39, 0x64, 0x3a, 0x30, 0x66, 0x3a, 0x64, 0x38, 0x3a, 0x30, 0x66, 0x3a, + 0x39, 0x32, 0x3a, 0x30, 0x61, 0x3a, 0x64, 0x31, 0x0a, 0x23, 0x20, 0x53, + 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, + 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x62, 0x38, 0x3a, 0x30, 0x31, 0x3a, 0x38, + 0x36, 0x3a, 0x64, 0x31, 0x3a, 0x65, 0x62, 0x3a, 0x39, 0x63, 0x3a, 0x38, + 0x36, 0x3a, 0x61, 0x35, 0x3a, 0x34, 0x31, 0x3a, 0x30, 0x34, 0x3a, 0x63, + 0x66, 0x3a, 0x33, 0x30, 0x3a, 0x35, 0x34, 0x3a, 0x66, 0x33, 0x3a, 0x34, + 0x63, 0x3a, 0x35, 0x32, 0x3a, 0x62, 0x37, 0x3a, 0x65, 0x35, 0x3a, 0x35, + 0x38, 0x3a, 0x63, 0x36, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, + 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, + 0x74, 0x3a, 0x20, 0x63, 0x65, 0x3a, 0x63, 0x64, 0x3a, 0x64, 0x63, 0x3a, + 0x39, 0x30, 0x3a, 0x35, 0x30, 0x3a, 0x39, 0x39, 0x3a, 0x64, 0x38, 0x3a, + 0x64, 0x61, 0x3a, 0x64, 0x66, 0x3a, 0x63, 0x35, 0x3a, 0x62, 0x31, 0x3a, + 0x64, 0x32, 0x3a, 0x30, 0x39, 0x3a, 0x62, 0x37, 0x3a, 0x33, 0x37, 0x3a, + 0x63, 0x62, 0x3a, 0x65, 0x32, 0x3a, 0x63, 0x31, 0x3a, 0x38, 0x63, 0x3a, + 0x66, 0x62, 0x3a, 0x32, 0x63, 0x3a, 0x31, 0x30, 0x3a, 0x63, 0x30, 0x3a, + 0x66, 0x66, 0x3a, 0x30, 0x62, 0x3a, 0x63, 0x66, 0x3a, 0x30, 0x64, 0x3a, + 0x33, 0x32, 0x3a, 0x38, 0x36, 0x3a, 0x66, 0x63, 0x3a, 0x31, 0x61, 0x3a, + 0x61, 0x32, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, + 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, + 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x45, 0x4d, + 0x44, 0x43, 0x43, 0x41, 0x78, 0x69, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, + 0x67, 0x49, 0x51, 0x55, 0x4a, 0x52, 0x73, 0x37, 0x42, 0x6a, 0x71, 0x31, + 0x5a, 0x78, 0x4e, 0x31, 0x5a, 0x66, 0x76, 0x64, 0x59, 0x2b, 0x67, 0x72, + 0x54, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, + 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x43, 0x42, 0x0a, + 0x67, 0x6a, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x42, 0x68, 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, 0x48, 0x6a, 0x41, 0x63, + 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, 0x46, 0x58, 0x64, 0x33, + 0x64, 0x79, 0x35, 0x34, 0x63, 0x6d, 0x46, 0x74, 0x63, 0x48, 0x4e, 0x6c, + 0x59, 0x33, 0x56, 0x79, 0x61, 0x58, 0x52, 0x35, 0x4c, 0x6d, 0x4e, 0x76, + 0x62, 0x54, 0x45, 0x6b, 0x0a, 0x4d, 0x43, 0x49, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x43, 0x68, 0x4d, 0x62, 0x57, 0x46, 0x4a, 0x68, 0x62, 0x58, 0x41, + 0x67, 0x55, 0x32, 0x56, 0x6a, 0x64, 0x58, 0x4a, 0x70, 0x64, 0x48, 0x6b, + 0x67, 0x55, 0x32, 0x56, 0x79, 0x64, 0x6d, 0x6c, 0x6a, 0x5a, 0x58, 0x4d, + 0x67, 0x53, 0x57, 0x35, 0x6a, 0x4d, 0x53, 0x30, 0x77, 0x4b, 0x77, 0x59, + 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, 0x79, 0x52, 0x59, 0x0a, 0x55, 0x6d, + 0x46, 0x74, 0x63, 0x43, 0x42, 0x48, 0x62, 0x47, 0x39, 0x69, 0x59, 0x57, + 0x77, 0x67, 0x51, 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, + 0x4e, 0x68, 0x64, 0x47, 0x6c, 0x76, 0x62, 0x69, 0x42, 0x42, 0x64, 0x58, + 0x52, 0x6f, 0x62, 0x33, 0x4a, 0x70, 0x64, 0x48, 0x6b, 0x77, 0x48, 0x68, + 0x63, 0x4e, 0x4d, 0x44, 0x51, 0x78, 0x4d, 0x54, 0x41, 0x78, 0x4d, 0x54, + 0x63, 0x78, 0x0a, 0x4e, 0x44, 0x41, 0x30, 0x57, 0x68, 0x63, 0x4e, 0x4d, + 0x7a, 0x55, 0x77, 0x4d, 0x54, 0x41, 0x78, 0x4d, 0x44, 0x55, 0x7a, 0x4e, + 0x7a, 0x45, 0x35, 0x57, 0x6a, 0x43, 0x42, 0x67, 0x6a, 0x45, 0x4c, 0x4d, + 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x56, + 0x56, 0x4d, 0x78, 0x48, 0x6a, 0x41, 0x63, 0x42, 0x67, 0x4e, 0x56, 0x42, + 0x41, 0x73, 0x54, 0x46, 0x58, 0x64, 0x33, 0x0a, 0x64, 0x79, 0x35, 0x34, + 0x63, 0x6d, 0x46, 0x74, 0x63, 0x48, 0x4e, 0x6c, 0x59, 0x33, 0x56, 0x79, + 0x61, 0x58, 0x52, 0x35, 0x4c, 0x6d, 0x4e, 0x76, 0x62, 0x54, 0x45, 0x6b, + 0x4d, 0x43, 0x49, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x62, + 0x57, 0x46, 0x4a, 0x68, 0x62, 0x58, 0x41, 0x67, 0x55, 0x32, 0x56, 0x6a, + 0x64, 0x58, 0x4a, 0x70, 0x64, 0x48, 0x6b, 0x67, 0x55, 0x32, 0x56, 0x79, + 0x0a, 0x64, 0x6d, 0x6c, 0x6a, 0x5a, 0x58, 0x4d, 0x67, 0x53, 0x57, 0x35, + 0x6a, 0x4d, 0x53, 0x30, 0x77, 0x4b, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, + 0x44, 0x45, 0x79, 0x52, 0x59, 0x55, 0x6d, 0x46, 0x74, 0x63, 0x43, 0x42, + 0x48, 0x62, 0x47, 0x39, 0x69, 0x59, 0x57, 0x77, 0x67, 0x51, 0x32, 0x56, + 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, 0x6c, + 0x76, 0x62, 0x69, 0x42, 0x42, 0x0a, 0x64, 0x58, 0x52, 0x6f, 0x62, 0x33, + 0x4a, 0x70, 0x64, 0x48, 0x6b, 0x77, 0x67, 0x67, 0x45, 0x69, 0x4d, 0x41, + 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, + 0x45, 0x42, 0x41, 0x51, 0x55, 0x41, 0x41, 0x34, 0x49, 0x42, 0x44, 0x77, + 0x41, 0x77, 0x67, 0x67, 0x45, 0x4b, 0x41, 0x6f, 0x49, 0x42, 0x41, 0x51, + 0x43, 0x59, 0x4a, 0x42, 0x36, 0x39, 0x46, 0x62, 0x53, 0x36, 0x0a, 0x33, + 0x38, 0x65, 0x4d, 0x70, 0x53, 0x65, 0x32, 0x4f, 0x41, 0x74, 0x70, 0x38, + 0x37, 0x5a, 0x4f, 0x71, 0x43, 0x77, 0x75, 0x49, 0x52, 0x31, 0x63, 0x52, + 0x4e, 0x38, 0x68, 0x58, 0x58, 0x34, 0x6a, 0x64, 0x50, 0x35, 0x65, 0x66, + 0x72, 0x52, 0x4b, 0x74, 0x36, 0x61, 0x74, 0x48, 0x36, 0x37, 0x67, 0x42, + 0x68, 0x62, 0x69, 0x6d, 0x31, 0x76, 0x5a, 0x5a, 0x33, 0x52, 0x72, 0x58, + 0x59, 0x43, 0x50, 0x0a, 0x4b, 0x5a, 0x32, 0x47, 0x47, 0x39, 0x6d, 0x63, + 0x44, 0x5a, 0x68, 0x74, 0x64, 0x68, 0x41, 0x6f, 0x57, 0x4f, 0x52, 0x6c, + 0x73, 0x48, 0x39, 0x4b, 0x6d, 0x48, 0x6d, 0x66, 0x34, 0x4d, 0x4d, 0x78, + 0x66, 0x6f, 0x41, 0x72, 0x74, 0x59, 0x7a, 0x41, 0x51, 0x44, 0x73, 0x52, + 0x68, 0x74, 0x44, 0x4c, 0x6f, 0x6f, 0x59, 0x32, 0x59, 0x4b, 0x54, 0x56, + 0x4d, 0x49, 0x4a, 0x74, 0x32, 0x57, 0x37, 0x51, 0x0a, 0x44, 0x78, 0x49, + 0x45, 0x4d, 0x35, 0x64, 0x66, 0x54, 0x32, 0x46, 0x61, 0x38, 0x4f, 0x54, + 0x35, 0x6b, 0x61, 0x76, 0x6e, 0x48, 0x54, 0x75, 0x38, 0x36, 0x4d, 0x2f, + 0x30, 0x61, 0x79, 0x30, 0x30, 0x66, 0x4f, 0x4a, 0x49, 0x59, 0x52, 0x79, + 0x4f, 0x38, 0x32, 0x46, 0x45, 0x7a, 0x47, 0x2b, 0x67, 0x53, 0x71, 0x6d, + 0x55, 0x73, 0x45, 0x33, 0x61, 0x35, 0x36, 0x6b, 0x30, 0x65, 0x6e, 0x49, + 0x34, 0x0a, 0x71, 0x45, 0x48, 0x4d, 0x50, 0x4a, 0x51, 0x52, 0x66, 0x65, + 0x76, 0x49, 0x70, 0x6f, 0x79, 0x33, 0x68, 0x73, 0x76, 0x4b, 0x4d, 0x7a, + 0x76, 0x5a, 0x50, 0x54, 0x65, 0x4c, 0x2b, 0x33, 0x6f, 0x2b, 0x68, 0x69, + 0x7a, 0x6e, 0x63, 0x39, 0x63, 0x4b, 0x56, 0x36, 0x78, 0x6b, 0x6d, 0x78, + 0x6e, 0x72, 0x39, 0x41, 0x38, 0x45, 0x43, 0x49, 0x71, 0x73, 0x41, 0x78, + 0x63, 0x5a, 0x5a, 0x50, 0x52, 0x61, 0x0a, 0x4a, 0x53, 0x4b, 0x4e, 0x4e, + 0x43, 0x79, 0x79, 0x39, 0x6d, 0x67, 0x64, 0x45, 0x6d, 0x33, 0x54, 0x69, + 0x68, 0x34, 0x55, 0x32, 0x73, 0x53, 0x50, 0x70, 0x75, 0x49, 0x6a, 0x68, + 0x64, 0x56, 0x36, 0x44, 0x62, 0x31, 0x71, 0x34, 0x4f, 0x6e, 0x73, 0x37, + 0x42, 0x65, 0x37, 0x51, 0x68, 0x74, 0x6e, 0x71, 0x69, 0x58, 0x74, 0x52, + 0x59, 0x4d, 0x68, 0x2f, 0x4d, 0x48, 0x4a, 0x66, 0x4e, 0x56, 0x69, 0x0a, + 0x50, 0x76, 0x72, 0x79, 0x78, 0x53, 0x33, 0x54, 0x2f, 0x64, 0x52, 0x6c, + 0x41, 0x67, 0x4d, 0x42, 0x41, 0x41, 0x47, 0x6a, 0x67, 0x5a, 0x38, 0x77, + 0x67, 0x5a, 0x77, 0x77, 0x45, 0x77, 0x59, 0x4a, 0x4b, 0x77, 0x59, 0x42, + 0x42, 0x41, 0x47, 0x43, 0x4e, 0x78, 0x51, 0x43, 0x42, 0x41, 0x59, 0x65, + 0x42, 0x41, 0x42, 0x44, 0x41, 0x45, 0x45, 0x77, 0x43, 0x77, 0x59, 0x44, + 0x56, 0x52, 0x30, 0x50, 0x0a, 0x42, 0x41, 0x51, 0x44, 0x41, 0x67, 0x47, + 0x47, 0x4d, 0x41, 0x38, 0x47, 0x41, 0x31, 0x55, 0x64, 0x45, 0x77, 0x45, + 0x42, 0x2f, 0x77, 0x51, 0x46, 0x4d, 0x41, 0x4d, 0x42, 0x41, 0x66, 0x38, + 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, 0x52, 0x30, 0x4f, 0x42, 0x42, 0x59, + 0x45, 0x46, 0x4d, 0x5a, 0x50, 0x6f, 0x6a, 0x30, 0x47, 0x59, 0x34, 0x51, + 0x4a, 0x6e, 0x4d, 0x35, 0x69, 0x35, 0x41, 0x53, 0x73, 0x0a, 0x6a, 0x56, + 0x79, 0x31, 0x36, 0x62, 0x59, 0x62, 0x4d, 0x44, 0x59, 0x47, 0x41, 0x31, + 0x55, 0x64, 0x48, 0x77, 0x51, 0x76, 0x4d, 0x43, 0x30, 0x77, 0x4b, 0x36, + 0x41, 0x70, 0x6f, 0x43, 0x65, 0x47, 0x4a, 0x57, 0x68, 0x30, 0x64, 0x48, + 0x41, 0x36, 0x4c, 0x79, 0x39, 0x6a, 0x63, 0x6d, 0x77, 0x75, 0x65, 0x48, + 0x4a, 0x68, 0x62, 0x58, 0x42, 0x7a, 0x5a, 0x57, 0x4e, 0x31, 0x63, 0x6d, + 0x6c, 0x30, 0x0a, 0x65, 0x53, 0x35, 0x6a, 0x62, 0x32, 0x30, 0x76, 0x57, + 0x45, 0x64, 0x44, 0x51, 0x53, 0x35, 0x6a, 0x63, 0x6d, 0x77, 0x77, 0x45, + 0x41, 0x59, 0x4a, 0x4b, 0x77, 0x59, 0x42, 0x42, 0x41, 0x47, 0x43, 0x4e, + 0x78, 0x55, 0x42, 0x42, 0x41, 0x4d, 0x43, 0x41, 0x51, 0x45, 0x77, 0x44, + 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, + 0x51, 0x45, 0x46, 0x42, 0x51, 0x41, 0x44, 0x0a, 0x67, 0x67, 0x45, 0x42, + 0x41, 0x4a, 0x45, 0x56, 0x4f, 0x51, 0x4d, 0x42, 0x47, 0x32, 0x66, 0x37, + 0x53, 0x68, 0x7a, 0x35, 0x43, 0x6d, 0x42, 0x62, 0x6f, 0x64, 0x70, 0x4e, + 0x6c, 0x32, 0x4c, 0x35, 0x4a, 0x46, 0x4d, 0x6e, 0x31, 0x34, 0x4a, 0x6b, + 0x54, 0x70, 0x41, 0x75, 0x77, 0x30, 0x6b, 0x62, 0x4b, 0x35, 0x72, 0x63, + 0x2f, 0x4b, 0x68, 0x34, 0x5a, 0x7a, 0x58, 0x78, 0x48, 0x66, 0x41, 0x52, + 0x0a, 0x76, 0x62, 0x64, 0x49, 0x34, 0x78, 0x44, 0x32, 0x44, 0x64, 0x38, + 0x2f, 0x30, 0x73, 0x6d, 0x32, 0x71, 0x6c, 0x57, 0x6b, 0x53, 0x4c, 0x6f, + 0x43, 0x32, 0x39, 0x35, 0x5a, 0x4c, 0x68, 0x56, 0x62, 0x4f, 0x35, 0x30, + 0x57, 0x66, 0x55, 0x66, 0x58, 0x4e, 0x2b, 0x70, 0x66, 0x54, 0x58, 0x59, + 0x53, 0x4e, 0x72, 0x73, 0x66, 0x31, 0x36, 0x47, 0x42, 0x42, 0x45, 0x59, + 0x67, 0x6f, 0x79, 0x78, 0x74, 0x0a, 0x71, 0x5a, 0x34, 0x42, 0x66, 0x6a, + 0x38, 0x70, 0x7a, 0x67, 0x43, 0x54, 0x33, 0x2f, 0x33, 0x4a, 0x6b, 0x6e, + 0x4f, 0x4a, 0x69, 0x57, 0x53, 0x65, 0x35, 0x79, 0x76, 0x6b, 0x48, 0x4a, + 0x45, 0x73, 0x30, 0x72, 0x6e, 0x4f, 0x66, 0x63, 0x35, 0x76, 0x4d, 0x5a, + 0x6e, 0x54, 0x35, 0x72, 0x37, 0x53, 0x48, 0x70, 0x44, 0x77, 0x43, 0x52, + 0x52, 0x35, 0x58, 0x43, 0x4f, 0x72, 0x54, 0x64, 0x4c, 0x61, 0x0a, 0x49, + 0x52, 0x39, 0x4e, 0x6d, 0x58, 0x6d, 0x64, 0x34, 0x63, 0x38, 0x6e, 0x6e, + 0x78, 0x43, 0x62, 0x48, 0x49, 0x67, 0x4e, 0x73, 0x49, 0x70, 0x6b, 0x51, + 0x54, 0x47, 0x34, 0x44, 0x6d, 0x79, 0x51, 0x4a, 0x4b, 0x53, 0x62, 0x58, + 0x48, 0x47, 0x50, 0x75, 0x72, 0x74, 0x2b, 0x48, 0x42, 0x76, 0x62, 0x61, + 0x6f, 0x41, 0x50, 0x49, 0x62, 0x7a, 0x70, 0x32, 0x36, 0x61, 0x33, 0x51, + 0x50, 0x53, 0x79, 0x0a, 0x69, 0x36, 0x6d, 0x78, 0x35, 0x4f, 0x2b, 0x61, + 0x47, 0x74, 0x41, 0x39, 0x61, 0x5a, 0x6e, 0x75, 0x71, 0x43, 0x69, 0x6a, + 0x34, 0x54, 0x79, 0x7a, 0x38, 0x4c, 0x49, 0x52, 0x6e, 0x4d, 0x39, 0x38, + 0x51, 0x4f, 0x62, 0x64, 0x35, 0x30, 0x4e, 0x39, 0x6f, 0x74, 0x67, 0x36, + 0x74, 0x61, 0x6d, 0x4e, 0x38, 0x6a, 0x53, 0x5a, 0x78, 0x4e, 0x51, 0x51, + 0x34, 0x51, 0x62, 0x39, 0x43, 0x59, 0x51, 0x51, 0x0a, 0x4f, 0x2b, 0x37, + 0x45, 0x54, 0x50, 0x54, 0x73, 0x4a, 0x33, 0x78, 0x43, 0x77, 0x6e, 0x52, + 0x38, 0x67, 0x6f, 0x6f, 0x4a, 0x79, 0x62, 0x51, 0x44, 0x4a, 0x62, 0x77, + 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, + 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, + 0x72, 0x3a, 0x20, 0x4f, 0x3d, 0x54, 0x68, 0x65, 0x20, 0x47, 0x6f, 0x20, + 0x44, 0x61, 0x64, 0x64, 0x79, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2c, + 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x47, 0x6f, 0x20, + 0x44, 0x61, 0x64, 0x64, 0x79, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, + 0x32, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, + 0x79, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, + 0x20, 0x4f, 0x3d, 0x54, 0x68, 0x65, 0x20, 0x47, 0x6f, 0x20, 0x44, 0x61, + 0x64, 0x64, 0x79, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2c, 0x20, 0x49, + 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x47, 0x6f, 0x20, 0x44, 0x61, + 0x64, 0x64, 0x79, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x32, 0x20, + 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x0a, + 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x47, 0x6f, + 0x20, 0x44, 0x61, 0x64, 0x64, 0x79, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, + 0x20, 0x32, 0x20, 0x43, 0x41, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, + 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x30, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, + 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, + 0x3a, 0x20, 0x39, 0x31, 0x3a, 0x64, 0x65, 0x3a, 0x30, 0x36, 0x3a, 0x32, + 0x35, 0x3a, 0x61, 0x62, 0x3a, 0x64, 0x61, 0x3a, 0x66, 0x64, 0x3a, 0x33, + 0x32, 0x3a, 0x31, 0x37, 0x3a, 0x30, 0x63, 0x3a, 0x62, 0x62, 0x3a, 0x32, + 0x35, 0x3a, 0x31, 0x37, 0x3a, 0x32, 0x61, 0x3a, 0x38, 0x34, 0x3a, 0x36, + 0x37, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, + 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x32, 0x37, + 0x3a, 0x39, 0x36, 0x3a, 0x62, 0x61, 0x3a, 0x65, 0x36, 0x3a, 0x33, 0x66, + 0x3a, 0x31, 0x38, 0x3a, 0x30, 0x31, 0x3a, 0x65, 0x32, 0x3a, 0x37, 0x37, + 0x3a, 0x32, 0x36, 0x3a, 0x31, 0x62, 0x3a, 0x61, 0x30, 0x3a, 0x64, 0x37, + 0x3a, 0x37, 0x37, 0x3a, 0x37, 0x30, 0x3a, 0x30, 0x32, 0x3a, 0x38, 0x66, + 0x3a, 0x32, 0x30, 0x3a, 0x65, 0x65, 0x3a, 0x65, 0x34, 0x0a, 0x23, 0x20, + 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x63, 0x33, 0x3a, 0x38, + 0x34, 0x3a, 0x36, 0x62, 0x3a, 0x66, 0x32, 0x3a, 0x34, 0x62, 0x3a, 0x39, + 0x65, 0x3a, 0x39, 0x33, 0x3a, 0x63, 0x61, 0x3a, 0x36, 0x34, 0x3a, 0x32, + 0x37, 0x3a, 0x34, 0x63, 0x3a, 0x30, 0x65, 0x3a, 0x63, 0x36, 0x3a, 0x37, + 0x63, 0x3a, 0x31, 0x65, 0x3a, 0x63, 0x63, 0x3a, 0x35, 0x65, 0x3a, 0x30, + 0x32, 0x3a, 0x34, 0x66, 0x3a, 0x66, 0x63, 0x3a, 0x61, 0x63, 0x3a, 0x64, + 0x32, 0x3a, 0x64, 0x37, 0x3a, 0x34, 0x30, 0x3a, 0x31, 0x39, 0x3a, 0x33, + 0x35, 0x3a, 0x30, 0x65, 0x3a, 0x38, 0x31, 0x3a, 0x66, 0x65, 0x3a, 0x35, + 0x34, 0x3a, 0x36, 0x61, 0x3a, 0x65, 0x34, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, + 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, + 0x4d, 0x49, 0x49, 0x45, 0x41, 0x44, 0x43, 0x43, 0x41, 0x75, 0x69, 0x67, + 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x42, 0x41, 0x44, 0x41, 0x4e, + 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, + 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x42, 0x6a, 0x4d, 0x51, 0x73, 0x77, + 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, + 0x55, 0x7a, 0x45, 0x68, 0x0a, 0x4d, 0x42, 0x38, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x43, 0x68, 0x4d, 0x59, 0x56, 0x47, 0x68, 0x6c, 0x49, 0x45, 0x64, + 0x76, 0x49, 0x45, 0x52, 0x68, 0x5a, 0x47, 0x52, 0x35, 0x49, 0x45, 0x64, + 0x79, 0x62, 0x33, 0x56, 0x77, 0x4c, 0x43, 0x42, 0x4a, 0x62, 0x6d, 0x4d, + 0x75, 0x4d, 0x54, 0x45, 0x77, 0x4c, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, + 0x4c, 0x45, 0x79, 0x68, 0x48, 0x62, 0x79, 0x42, 0x45, 0x0a, 0x59, 0x57, + 0x52, 0x6b, 0x65, 0x53, 0x42, 0x44, 0x62, 0x47, 0x46, 0x7a, 0x63, 0x79, + 0x41, 0x79, 0x49, 0x45, 0x4e, 0x6c, 0x63, 0x6e, 0x52, 0x70, 0x5a, 0x6d, + 0x6c, 0x6a, 0x59, 0x58, 0x52, 0x70, 0x62, 0x32, 0x34, 0x67, 0x51, 0x58, + 0x56, 0x30, 0x61, 0x47, 0x39, 0x79, 0x61, 0x58, 0x52, 0x35, 0x4d, 0x42, + 0x34, 0x58, 0x44, 0x54, 0x41, 0x30, 0x4d, 0x44, 0x59, 0x79, 0x4f, 0x54, + 0x45, 0x33, 0x0a, 0x4d, 0x44, 0x59, 0x79, 0x4d, 0x46, 0x6f, 0x58, 0x44, + 0x54, 0x4d, 0x30, 0x4d, 0x44, 0x59, 0x79, 0x4f, 0x54, 0x45, 0x33, 0x4d, + 0x44, 0x59, 0x79, 0x4d, 0x46, 0x6f, 0x77, 0x59, 0x7a, 0x45, 0x4c, 0x4d, + 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x56, + 0x56, 0x4d, 0x78, 0x49, 0x54, 0x41, 0x66, 0x42, 0x67, 0x4e, 0x56, 0x42, + 0x41, 0x6f, 0x54, 0x47, 0x46, 0x52, 0x6f, 0x0a, 0x5a, 0x53, 0x42, 0x48, + 0x62, 0x79, 0x42, 0x45, 0x59, 0x57, 0x52, 0x6b, 0x65, 0x53, 0x42, 0x48, + 0x63, 0x6d, 0x39, 0x31, 0x63, 0x43, 0x77, 0x67, 0x53, 0x57, 0x35, 0x6a, + 0x4c, 0x6a, 0x45, 0x78, 0x4d, 0x43, 0x38, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x43, 0x78, 0x4d, 0x6f, 0x52, 0x32, 0x38, 0x67, 0x52, 0x47, 0x46, 0x6b, + 0x5a, 0x48, 0x6b, 0x67, 0x51, 0x32, 0x78, 0x68, 0x63, 0x33, 0x4d, 0x67, + 0x0a, 0x4d, 0x69, 0x42, 0x44, 0x5a, 0x58, 0x4a, 0x30, 0x61, 0x57, 0x5a, + 0x70, 0x59, 0x32, 0x46, 0x30, 0x61, 0x57, 0x39, 0x75, 0x49, 0x45, 0x46, + 0x31, 0x64, 0x47, 0x68, 0x76, 0x63, 0x6d, 0x6c, 0x30, 0x65, 0x54, 0x43, + 0x43, 0x41, 0x53, 0x41, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, + 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x42, 0x42, 0x51, 0x41, + 0x44, 0x67, 0x67, 0x45, 0x4e, 0x0a, 0x41, 0x44, 0x43, 0x43, 0x41, 0x51, + 0x67, 0x43, 0x67, 0x67, 0x45, 0x42, 0x41, 0x4e, 0x36, 0x64, 0x31, 0x2b, + 0x70, 0x58, 0x47, 0x45, 0x6d, 0x68, 0x57, 0x2b, 0x76, 0x58, 0x58, 0x30, + 0x69, 0x47, 0x36, 0x72, 0x37, 0x64, 0x2f, 0x2b, 0x54, 0x76, 0x5a, 0x78, + 0x7a, 0x30, 0x5a, 0x57, 0x69, 0x7a, 0x56, 0x33, 0x47, 0x67, 0x58, 0x6e, + 0x65, 0x37, 0x37, 0x5a, 0x74, 0x4a, 0x36, 0x58, 0x43, 0x41, 0x0a, 0x50, + 0x56, 0x59, 0x59, 0x59, 0x77, 0x68, 0x76, 0x32, 0x76, 0x4c, 0x4d, 0x30, + 0x44, 0x39, 0x2f, 0x41, 0x6c, 0x51, 0x69, 0x56, 0x42, 0x44, 0x59, 0x73, + 0x6f, 0x48, 0x55, 0x77, 0x48, 0x55, 0x39, 0x53, 0x33, 0x2f, 0x48, 0x64, + 0x38, 0x4d, 0x2b, 0x65, 0x4b, 0x73, 0x61, 0x41, 0x37, 0x55, 0x67, 0x61, + 0x79, 0x39, 0x71, 0x4b, 0x37, 0x48, 0x46, 0x69, 0x48, 0x37, 0x45, 0x75, + 0x78, 0x36, 0x77, 0x0a, 0x77, 0x64, 0x68, 0x46, 0x4a, 0x32, 0x2b, 0x71, + 0x4e, 0x31, 0x6a, 0x33, 0x68, 0x79, 0x62, 0x58, 0x32, 0x43, 0x33, 0x32, + 0x71, 0x52, 0x65, 0x33, 0x48, 0x33, 0x49, 0x32, 0x54, 0x71, 0x59, 0x58, + 0x50, 0x32, 0x57, 0x59, 0x6b, 0x74, 0x73, 0x71, 0x62, 0x6c, 0x32, 0x69, + 0x2f, 0x6f, 0x6a, 0x67, 0x43, 0x39, 0x35, 0x2f, 0x35, 0x59, 0x30, 0x56, + 0x34, 0x65, 0x76, 0x4c, 0x4f, 0x74, 0x58, 0x69, 0x0a, 0x45, 0x71, 0x49, + 0x54, 0x4c, 0x64, 0x69, 0x4f, 0x72, 0x31, 0x38, 0x53, 0x50, 0x61, 0x41, + 0x49, 0x42, 0x51, 0x69, 0x32, 0x58, 0x4b, 0x56, 0x6c, 0x4f, 0x41, 0x52, + 0x46, 0x6d, 0x52, 0x36, 0x6a, 0x59, 0x47, 0x42, 0x30, 0x78, 0x55, 0x47, + 0x6c, 0x63, 0x6d, 0x49, 0x62, 0x59, 0x73, 0x55, 0x66, 0x62, 0x31, 0x38, + 0x61, 0x51, 0x72, 0x34, 0x43, 0x55, 0x57, 0x57, 0x6f, 0x72, 0x69, 0x4d, + 0x59, 0x0a, 0x61, 0x76, 0x78, 0x34, 0x41, 0x36, 0x6c, 0x4e, 0x66, 0x34, + 0x44, 0x44, 0x2b, 0x71, 0x74, 0x61, 0x2f, 0x4b, 0x46, 0x41, 0x70, 0x4d, + 0x6f, 0x5a, 0x46, 0x76, 0x36, 0x79, 0x79, 0x4f, 0x39, 0x65, 0x63, 0x77, + 0x33, 0x75, 0x64, 0x37, 0x32, 0x61, 0x39, 0x6e, 0x6d, 0x59, 0x76, 0x4c, + 0x45, 0x48, 0x5a, 0x36, 0x49, 0x56, 0x44, 0x64, 0x32, 0x67, 0x57, 0x4d, + 0x5a, 0x45, 0x65, 0x77, 0x6f, 0x2b, 0x0a, 0x59, 0x69, 0x68, 0x66, 0x75, + 0x6b, 0x45, 0x48, 0x55, 0x31, 0x6a, 0x50, 0x45, 0x58, 0x34, 0x34, 0x64, + 0x4d, 0x58, 0x34, 0x2f, 0x37, 0x56, 0x70, 0x6b, 0x49, 0x2b, 0x45, 0x64, + 0x4f, 0x71, 0x58, 0x47, 0x36, 0x38, 0x43, 0x41, 0x51, 0x4f, 0x6a, 0x67, + 0x63, 0x41, 0x77, 0x67, 0x62, 0x30, 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, + 0x52, 0x30, 0x4f, 0x42, 0x42, 0x59, 0x45, 0x46, 0x4e, 0x4c, 0x45, 0x0a, + 0x73, 0x4e, 0x4b, 0x52, 0x31, 0x45, 0x77, 0x52, 0x63, 0x62, 0x4e, 0x68, + 0x79, 0x7a, 0x32, 0x68, 0x2f, 0x74, 0x32, 0x6f, 0x61, 0x74, 0x54, 0x6a, + 0x4d, 0x49, 0x47, 0x4e, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x53, 0x4d, 0x45, + 0x67, 0x59, 0x55, 0x77, 0x67, 0x59, 0x4b, 0x41, 0x46, 0x4e, 0x4c, 0x45, + 0x73, 0x4e, 0x4b, 0x52, 0x31, 0x45, 0x77, 0x52, 0x63, 0x62, 0x4e, 0x68, + 0x79, 0x7a, 0x32, 0x68, 0x0a, 0x2f, 0x74, 0x32, 0x6f, 0x61, 0x74, 0x54, + 0x6a, 0x6f, 0x57, 0x65, 0x6b, 0x5a, 0x54, 0x42, 0x6a, 0x4d, 0x51, 0x73, + 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, + 0x56, 0x55, 0x7a, 0x45, 0x68, 0x4d, 0x42, 0x38, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x43, 0x68, 0x4d, 0x59, 0x56, 0x47, 0x68, 0x6c, 0x49, 0x45, 0x64, + 0x76, 0x49, 0x45, 0x52, 0x68, 0x5a, 0x47, 0x52, 0x35, 0x0a, 0x49, 0x45, + 0x64, 0x79, 0x62, 0x33, 0x56, 0x77, 0x4c, 0x43, 0x42, 0x4a, 0x62, 0x6d, + 0x4d, 0x75, 0x4d, 0x54, 0x45, 0x77, 0x4c, 0x77, 0x59, 0x44, 0x56, 0x51, + 0x51, 0x4c, 0x45, 0x79, 0x68, 0x48, 0x62, 0x79, 0x42, 0x45, 0x59, 0x57, + 0x52, 0x6b, 0x65, 0x53, 0x42, 0x44, 0x62, 0x47, 0x46, 0x7a, 0x63, 0x79, + 0x41, 0x79, 0x49, 0x45, 0x4e, 0x6c, 0x63, 0x6e, 0x52, 0x70, 0x5a, 0x6d, + 0x6c, 0x6a, 0x0a, 0x59, 0x58, 0x52, 0x70, 0x62, 0x32, 0x34, 0x67, 0x51, + 0x58, 0x56, 0x30, 0x61, 0x47, 0x39, 0x79, 0x61, 0x58, 0x52, 0x35, 0x67, + 0x67, 0x45, 0x41, 0x4d, 0x41, 0x77, 0x47, 0x41, 0x31, 0x55, 0x64, 0x45, + 0x77, 0x51, 0x46, 0x4d, 0x41, 0x4d, 0x42, 0x41, 0x66, 0x38, 0x77, 0x44, + 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, + 0x51, 0x45, 0x46, 0x42, 0x51, 0x41, 0x44, 0x0a, 0x67, 0x67, 0x45, 0x42, + 0x41, 0x44, 0x4a, 0x4c, 0x38, 0x37, 0x4c, 0x4b, 0x50, 0x70, 0x48, 0x38, + 0x45, 0x73, 0x61, 0x68, 0x42, 0x34, 0x79, 0x4f, 0x64, 0x36, 0x41, 0x7a, + 0x42, 0x68, 0x52, 0x63, 0x6b, 0x42, 0x34, 0x59, 0x39, 0x77, 0x69, 0x6d, + 0x50, 0x51, 0x6f, 0x5a, 0x2b, 0x59, 0x65, 0x41, 0x45, 0x57, 0x35, 0x70, + 0x35, 0x4a, 0x59, 0x58, 0x4d, 0x50, 0x38, 0x30, 0x6b, 0x57, 0x4e, 0x79, + 0x0a, 0x4f, 0x4f, 0x37, 0x4d, 0x48, 0x41, 0x47, 0x6a, 0x48, 0x5a, 0x51, + 0x6f, 0x70, 0x44, 0x48, 0x32, 0x65, 0x73, 0x52, 0x55, 0x31, 0x2f, 0x62, + 0x6c, 0x4d, 0x56, 0x67, 0x44, 0x6f, 0x73, 0x7a, 0x4f, 0x59, 0x74, 0x75, + 0x55, 0x52, 0x58, 0x4f, 0x31, 0x76, 0x30, 0x58, 0x4a, 0x4a, 0x4c, 0x58, + 0x56, 0x67, 0x67, 0x4b, 0x74, 0x49, 0x33, 0x6c, 0x70, 0x6a, 0x62, 0x69, + 0x32, 0x54, 0x63, 0x37, 0x50, 0x0a, 0x54, 0x4d, 0x6f, 0x7a, 0x49, 0x2b, + 0x67, 0x63, 0x69, 0x4b, 0x71, 0x64, 0x69, 0x30, 0x46, 0x75, 0x46, 0x73, + 0x6b, 0x67, 0x35, 0x59, 0x6d, 0x65, 0x7a, 0x54, 0x76, 0x61, 0x63, 0x50, + 0x64, 0x2b, 0x6d, 0x53, 0x59, 0x67, 0x46, 0x46, 0x51, 0x6c, 0x71, 0x32, + 0x35, 0x7a, 0x68, 0x65, 0x61, 0x62, 0x49, 0x5a, 0x30, 0x4b, 0x62, 0x49, + 0x49, 0x4f, 0x71, 0x50, 0x6a, 0x43, 0x44, 0x50, 0x6f, 0x51, 0x0a, 0x48, + 0x6d, 0x79, 0x57, 0x37, 0x34, 0x63, 0x4e, 0x78, 0x41, 0x39, 0x68, 0x69, + 0x36, 0x33, 0x75, 0x67, 0x79, 0x75, 0x56, 0x2b, 0x49, 0x36, 0x53, 0x68, + 0x48, 0x49, 0x35, 0x36, 0x79, 0x44, 0x71, 0x67, 0x2b, 0x32, 0x44, 0x7a, + 0x5a, 0x64, 0x75, 0x43, 0x4c, 0x7a, 0x72, 0x54, 0x69, 0x61, 0x32, 0x63, + 0x79, 0x76, 0x6b, 0x30, 0x2f, 0x5a, 0x4d, 0x2f, 0x69, 0x5a, 0x78, 0x34, + 0x6d, 0x45, 0x52, 0x0a, 0x64, 0x45, 0x72, 0x2f, 0x56, 0x78, 0x71, 0x48, + 0x44, 0x33, 0x56, 0x49, 0x4c, 0x73, 0x39, 0x52, 0x61, 0x52, 0x65, 0x67, + 0x41, 0x68, 0x4a, 0x68, 0x6c, 0x64, 0x58, 0x52, 0x51, 0x4c, 0x49, 0x51, + 0x54, 0x4f, 0x37, 0x45, 0x72, 0x42, 0x42, 0x44, 0x70, 0x71, 0x57, 0x65, + 0x43, 0x74, 0x57, 0x56, 0x59, 0x70, 0x6f, 0x4e, 0x7a, 0x34, 0x69, 0x43, + 0x78, 0x54, 0x49, 0x4d, 0x35, 0x43, 0x75, 0x66, 0x0a, 0x52, 0x65, 0x59, + 0x4e, 0x6e, 0x79, 0x69, 0x63, 0x73, 0x62, 0x6b, 0x71, 0x57, 0x6c, 0x65, + 0x74, 0x4e, 0x77, 0x2b, 0x76, 0x48, 0x58, 0x2f, 0x62, 0x76, 0x5a, 0x38, + 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, + 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, + 0x72, 0x3a, 0x20, 0x4f, 0x3d, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, + 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, + 0x3d, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x43, + 0x6c, 0x61, 0x73, 0x73, 0x20, 0x32, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x4f, 0x3d, 0x53, 0x74, 0x61, 0x72, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x6f, + 0x6c, 0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, + 0x20, 0x4f, 0x55, 0x3d, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x32, 0x20, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x0a, 0x23, 0x20, + 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x53, 0x74, 0x61, 0x72, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, + 0x32, 0x20, 0x43, 0x41, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, + 0x61, 0x6c, 0x3a, 0x20, 0x30, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, + 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, + 0x20, 0x33, 0x32, 0x3a, 0x34, 0x61, 0x3a, 0x34, 0x62, 0x3a, 0x62, 0x62, + 0x3a, 0x63, 0x38, 0x3a, 0x36, 0x33, 0x3a, 0x36, 0x39, 0x3a, 0x39, 0x62, + 0x3a, 0x62, 0x65, 0x3a, 0x37, 0x34, 0x3a, 0x39, 0x61, 0x3a, 0x63, 0x36, + 0x3a, 0x64, 0x64, 0x3a, 0x31, 0x64, 0x3a, 0x34, 0x36, 0x3a, 0x32, 0x34, + 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, + 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x61, 0x64, 0x3a, + 0x37, 0x65, 0x3a, 0x31, 0x63, 0x3a, 0x32, 0x38, 0x3a, 0x62, 0x30, 0x3a, + 0x36, 0x34, 0x3a, 0x65, 0x66, 0x3a, 0x38, 0x66, 0x3a, 0x36, 0x30, 0x3a, + 0x30, 0x33, 0x3a, 0x34, 0x30, 0x3a, 0x32, 0x30, 0x3a, 0x31, 0x34, 0x3a, + 0x63, 0x33, 0x3a, 0x64, 0x30, 0x3a, 0x65, 0x33, 0x3a, 0x33, 0x37, 0x3a, + 0x30, 0x65, 0x3a, 0x62, 0x35, 0x3a, 0x38, 0x61, 0x0a, 0x23, 0x20, 0x53, + 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x31, 0x34, 0x3a, 0x36, 0x35, + 0x3a, 0x66, 0x61, 0x3a, 0x32, 0x30, 0x3a, 0x35, 0x33, 0x3a, 0x39, 0x37, + 0x3a, 0x62, 0x38, 0x3a, 0x37, 0x36, 0x3a, 0x66, 0x61, 0x3a, 0x61, 0x36, + 0x3a, 0x66, 0x30, 0x3a, 0x61, 0x39, 0x3a, 0x39, 0x35, 0x3a, 0x38, 0x65, + 0x3a, 0x35, 0x35, 0x3a, 0x39, 0x30, 0x3a, 0x65, 0x34, 0x3a, 0x30, 0x66, + 0x3a, 0x63, 0x63, 0x3a, 0x37, 0x66, 0x3a, 0x61, 0x61, 0x3a, 0x34, 0x66, + 0x3a, 0x62, 0x37, 0x3a, 0x63, 0x32, 0x3a, 0x63, 0x38, 0x3a, 0x36, 0x37, + 0x3a, 0x37, 0x35, 0x3a, 0x32, 0x31, 0x3a, 0x66, 0x62, 0x3a, 0x35, 0x66, + 0x3a, 0x62, 0x36, 0x3a, 0x35, 0x38, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, + 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, + 0x49, 0x49, 0x45, 0x44, 0x7a, 0x43, 0x43, 0x41, 0x76, 0x65, 0x67, 0x41, + 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x42, 0x41, 0x44, 0x41, 0x4e, 0x42, + 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, + 0x51, 0x55, 0x46, 0x41, 0x44, 0x42, 0x6f, 0x4d, 0x51, 0x73, 0x77, 0x43, + 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, 0x55, + 0x7a, 0x45, 0x6c, 0x0a, 0x4d, 0x43, 0x4d, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x43, 0x68, 0x4d, 0x63, 0x55, 0x33, 0x52, 0x68, 0x63, 0x6d, 0x5a, 0x70, + 0x5a, 0x57, 0x78, 0x6b, 0x49, 0x46, 0x52, 0x6c, 0x59, 0x32, 0x68, 0x75, + 0x62, 0x32, 0x78, 0x76, 0x5a, 0x32, 0x6c, 0x6c, 0x63, 0x79, 0x77, 0x67, + 0x53, 0x57, 0x35, 0x6a, 0x4c, 0x6a, 0x45, 0x79, 0x4d, 0x44, 0x41, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x43, 0x78, 0x4d, 0x70, 0x0a, 0x55, 0x33, 0x52, + 0x68, 0x63, 0x6d, 0x5a, 0x70, 0x5a, 0x57, 0x78, 0x6b, 0x49, 0x45, 0x4e, + 0x73, 0x59, 0x58, 0x4e, 0x7a, 0x49, 0x44, 0x49, 0x67, 0x51, 0x32, 0x56, + 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, 0x6c, + 0x76, 0x62, 0x69, 0x42, 0x42, 0x64, 0x58, 0x52, 0x6f, 0x62, 0x33, 0x4a, + 0x70, 0x64, 0x48, 0x6b, 0x77, 0x48, 0x68, 0x63, 0x4e, 0x4d, 0x44, 0x51, + 0x77, 0x0a, 0x4e, 0x6a, 0x49, 0x35, 0x4d, 0x54, 0x63, 0x7a, 0x4f, 0x54, + 0x45, 0x32, 0x57, 0x68, 0x63, 0x4e, 0x4d, 0x7a, 0x51, 0x77, 0x4e, 0x6a, + 0x49, 0x35, 0x4d, 0x54, 0x63, 0x7a, 0x4f, 0x54, 0x45, 0x32, 0x57, 0x6a, + 0x42, 0x6f, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, + 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, 0x6c, 0x4d, 0x43, + 0x4d, 0x47, 0x41, 0x31, 0x55, 0x45, 0x0a, 0x43, 0x68, 0x4d, 0x63, 0x55, + 0x33, 0x52, 0x68, 0x63, 0x6d, 0x5a, 0x70, 0x5a, 0x57, 0x78, 0x6b, 0x49, + 0x46, 0x52, 0x6c, 0x59, 0x32, 0x68, 0x75, 0x62, 0x32, 0x78, 0x76, 0x5a, + 0x32, 0x6c, 0x6c, 0x63, 0x79, 0x77, 0x67, 0x53, 0x57, 0x35, 0x6a, 0x4c, + 0x6a, 0x45, 0x79, 0x4d, 0x44, 0x41, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, + 0x78, 0x4d, 0x70, 0x55, 0x33, 0x52, 0x68, 0x63, 0x6d, 0x5a, 0x70, 0x0a, + 0x5a, 0x57, 0x78, 0x6b, 0x49, 0x45, 0x4e, 0x73, 0x59, 0x58, 0x4e, 0x7a, + 0x49, 0x44, 0x49, 0x67, 0x51, 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, 0x6d, + 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, 0x6c, 0x76, 0x62, 0x69, 0x42, 0x42, + 0x64, 0x58, 0x52, 0x6f, 0x62, 0x33, 0x4a, 0x70, 0x64, 0x48, 0x6b, 0x77, + 0x67, 0x67, 0x45, 0x67, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, + 0x53, 0x49, 0x62, 0x33, 0x0a, 0x44, 0x51, 0x45, 0x42, 0x41, 0x51, 0x55, + 0x41, 0x41, 0x34, 0x49, 0x42, 0x44, 0x51, 0x41, 0x77, 0x67, 0x67, 0x45, + 0x49, 0x41, 0x6f, 0x49, 0x42, 0x41, 0x51, 0x43, 0x33, 0x4d, 0x73, 0x6a, + 0x2b, 0x36, 0x58, 0x47, 0x6d, 0x42, 0x49, 0x57, 0x74, 0x44, 0x42, 0x46, + 0x6b, 0x33, 0x38, 0x35, 0x4e, 0x37, 0x38, 0x67, 0x44, 0x47, 0x49, 0x63, + 0x2f, 0x6f, 0x61, 0x76, 0x37, 0x50, 0x4b, 0x61, 0x66, 0x0a, 0x38, 0x4d, + 0x4f, 0x68, 0x32, 0x74, 0x54, 0x59, 0x62, 0x69, 0x74, 0x54, 0x6b, 0x50, + 0x73, 0x6b, 0x70, 0x44, 0x36, 0x45, 0x38, 0x4a, 0x37, 0x6f, 0x58, 0x2b, + 0x7a, 0x6c, 0x4a, 0x30, 0x54, 0x31, 0x4b, 0x4b, 0x59, 0x2f, 0x65, 0x39, + 0x37, 0x67, 0x4b, 0x76, 0x44, 0x49, 0x72, 0x31, 0x4d, 0x76, 0x6e, 0x73, + 0x6f, 0x46, 0x41, 0x5a, 0x4d, 0x65, 0x6a, 0x32, 0x59, 0x63, 0x4f, 0x61, + 0x64, 0x4e, 0x0a, 0x2b, 0x6c, 0x71, 0x32, 0x63, 0x77, 0x51, 0x6c, 0x5a, + 0x75, 0x74, 0x33, 0x66, 0x2b, 0x64, 0x5a, 0x78, 0x6b, 0x71, 0x5a, 0x4a, + 0x52, 0x52, 0x55, 0x36, 0x79, 0x62, 0x48, 0x38, 0x33, 0x38, 0x5a, 0x31, + 0x54, 0x42, 0x77, 0x6a, 0x36, 0x2b, 0x77, 0x52, 0x69, 0x72, 0x2f, 0x72, + 0x65, 0x73, 0x70, 0x37, 0x64, 0x65, 0x66, 0x71, 0x67, 0x53, 0x48, 0x6f, + 0x39, 0x54, 0x35, 0x69, 0x61, 0x55, 0x30, 0x0a, 0x58, 0x39, 0x74, 0x44, + 0x6b, 0x59, 0x49, 0x32, 0x32, 0x57, 0x59, 0x38, 0x73, 0x62, 0x69, 0x35, + 0x67, 0x76, 0x32, 0x63, 0x4f, 0x6a, 0x34, 0x51, 0x79, 0x44, 0x76, 0x76, + 0x42, 0x6d, 0x56, 0x6d, 0x65, 0x70, 0x73, 0x5a, 0x47, 0x44, 0x33, 0x2f, + 0x63, 0x56, 0x45, 0x38, 0x4d, 0x43, 0x35, 0x66, 0x76, 0x6a, 0x31, 0x33, + 0x63, 0x37, 0x4a, 0x64, 0x42, 0x6d, 0x7a, 0x44, 0x49, 0x31, 0x61, 0x61, + 0x0a, 0x4b, 0x34, 0x55, 0x6d, 0x6b, 0x68, 0x79, 0x6e, 0x41, 0x72, 0x50, + 0x6b, 0x50, 0x77, 0x32, 0x76, 0x43, 0x48, 0x6d, 0x43, 0x75, 0x44, 0x59, + 0x39, 0x36, 0x70, 0x7a, 0x54, 0x4e, 0x62, 0x4f, 0x38, 0x61, 0x63, 0x72, + 0x31, 0x7a, 0x4a, 0x33, 0x6f, 0x2f, 0x57, 0x53, 0x4e, 0x46, 0x34, 0x41, + 0x7a, 0x62, 0x6c, 0x35, 0x4b, 0x58, 0x5a, 0x6e, 0x4a, 0x48, 0x6f, 0x65, + 0x30, 0x6e, 0x52, 0x72, 0x41, 0x0a, 0x31, 0x57, 0x34, 0x54, 0x4e, 0x53, + 0x4e, 0x65, 0x33, 0x35, 0x74, 0x66, 0x50, 0x65, 0x2f, 0x57, 0x39, 0x33, + 0x62, 0x43, 0x36, 0x6a, 0x36, 0x37, 0x65, 0x41, 0x30, 0x63, 0x51, 0x6d, + 0x64, 0x72, 0x42, 0x4e, 0x6a, 0x34, 0x31, 0x74, 0x70, 0x76, 0x69, 0x2f, + 0x4a, 0x45, 0x6f, 0x41, 0x47, 0x72, 0x41, 0x67, 0x45, 0x44, 0x6f, 0x34, + 0x48, 0x46, 0x4d, 0x49, 0x48, 0x43, 0x4d, 0x42, 0x30, 0x47, 0x0a, 0x41, + 0x31, 0x55, 0x64, 0x44, 0x67, 0x51, 0x57, 0x42, 0x42, 0x53, 0x2f, 0x58, + 0x37, 0x66, 0x52, 0x7a, 0x74, 0x30, 0x66, 0x68, 0x76, 0x52, 0x62, 0x56, + 0x61, 0x7a, 0x63, 0x31, 0x78, 0x44, 0x43, 0x44, 0x71, 0x6d, 0x49, 0x35, + 0x7a, 0x43, 0x42, 0x6b, 0x67, 0x59, 0x44, 0x56, 0x52, 0x30, 0x6a, 0x42, + 0x49, 0x47, 0x4b, 0x4d, 0x49, 0x47, 0x48, 0x67, 0x42, 0x53, 0x2f, 0x58, + 0x37, 0x66, 0x52, 0x0a, 0x7a, 0x74, 0x30, 0x66, 0x68, 0x76, 0x52, 0x62, + 0x56, 0x61, 0x7a, 0x63, 0x31, 0x78, 0x44, 0x43, 0x44, 0x71, 0x6d, 0x49, + 0x35, 0x36, 0x46, 0x73, 0x70, 0x47, 0x6f, 0x77, 0x61, 0x44, 0x45, 0x4c, + 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, + 0x56, 0x56, 0x4d, 0x78, 0x4a, 0x54, 0x41, 0x6a, 0x42, 0x67, 0x4e, 0x56, + 0x42, 0x41, 0x6f, 0x54, 0x48, 0x46, 0x4e, 0x30, 0x0a, 0x59, 0x58, 0x4a, + 0x6d, 0x61, 0x57, 0x56, 0x73, 0x5a, 0x43, 0x42, 0x55, 0x5a, 0x57, 0x4e, + 0x6f, 0x62, 0x6d, 0x39, 0x73, 0x62, 0x32, 0x64, 0x70, 0x5a, 0x58, 0x4d, + 0x73, 0x49, 0x45, 0x6c, 0x75, 0x59, 0x79, 0x34, 0x78, 0x4d, 0x6a, 0x41, + 0x77, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, 0x4b, 0x56, 0x4e, + 0x30, 0x59, 0x58, 0x4a, 0x6d, 0x61, 0x57, 0x56, 0x73, 0x5a, 0x43, 0x42, + 0x44, 0x0a, 0x62, 0x47, 0x46, 0x7a, 0x63, 0x79, 0x41, 0x79, 0x49, 0x45, + 0x4e, 0x6c, 0x63, 0x6e, 0x52, 0x70, 0x5a, 0x6d, 0x6c, 0x6a, 0x59, 0x58, + 0x52, 0x70, 0x62, 0x32, 0x34, 0x67, 0x51, 0x58, 0x56, 0x30, 0x61, 0x47, + 0x39, 0x79, 0x61, 0x58, 0x52, 0x35, 0x67, 0x67, 0x45, 0x41, 0x4d, 0x41, + 0x77, 0x47, 0x41, 0x31, 0x55, 0x64, 0x45, 0x77, 0x51, 0x46, 0x4d, 0x41, + 0x4d, 0x42, 0x41, 0x66, 0x38, 0x77, 0x0a, 0x44, 0x51, 0x59, 0x4a, 0x4b, + 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x46, 0x42, + 0x51, 0x41, 0x44, 0x67, 0x67, 0x45, 0x42, 0x41, 0x41, 0x57, 0x64, 0x50, + 0x34, 0x69, 0x64, 0x30, 0x63, 0x6b, 0x61, 0x56, 0x61, 0x47, 0x73, 0x61, + 0x66, 0x50, 0x7a, 0x57, 0x64, 0x71, 0x62, 0x41, 0x59, 0x63, 0x61, 0x54, + 0x31, 0x65, 0x70, 0x6f, 0x58, 0x6b, 0x4a, 0x4b, 0x74, 0x76, 0x33, 0x0a, + 0x4c, 0x37, 0x49, 0x65, 0x7a, 0x4d, 0x64, 0x65, 0x61, 0x74, 0x69, 0x44, + 0x68, 0x36, 0x47, 0x58, 0x37, 0x30, 0x6b, 0x31, 0x50, 0x6e, 0x63, 0x47, + 0x51, 0x56, 0x68, 0x69, 0x76, 0x34, 0x35, 0x59, 0x75, 0x41, 0x70, 0x6e, + 0x50, 0x2b, 0x79, 0x7a, 0x33, 0x53, 0x46, 0x6d, 0x48, 0x38, 0x6c, 0x55, + 0x2b, 0x6e, 0x4c, 0x4d, 0x50, 0x55, 0x78, 0x41, 0x32, 0x49, 0x47, 0x76, + 0x64, 0x35, 0x36, 0x44, 0x0a, 0x65, 0x72, 0x75, 0x69, 0x78, 0x2f, 0x55, + 0x30, 0x46, 0x34, 0x37, 0x5a, 0x45, 0x55, 0x44, 0x30, 0x2f, 0x43, 0x77, + 0x71, 0x54, 0x52, 0x56, 0x2f, 0x70, 0x32, 0x4a, 0x64, 0x4c, 0x69, 0x58, + 0x54, 0x41, 0x41, 0x73, 0x67, 0x47, 0x68, 0x31, 0x6f, 0x2b, 0x52, 0x65, + 0x34, 0x39, 0x4c, 0x32, 0x4c, 0x37, 0x53, 0x68, 0x5a, 0x33, 0x55, 0x30, + 0x57, 0x69, 0x78, 0x65, 0x44, 0x79, 0x4c, 0x4a, 0x6c, 0x0a, 0x78, 0x79, + 0x31, 0x36, 0x70, 0x61, 0x71, 0x38, 0x55, 0x34, 0x5a, 0x74, 0x33, 0x56, + 0x65, 0x6b, 0x79, 0x76, 0x67, 0x67, 0x51, 0x51, 0x74, 0x6f, 0x38, 0x50, + 0x54, 0x37, 0x64, 0x4c, 0x35, 0x57, 0x58, 0x58, 0x70, 0x35, 0x39, 0x66, + 0x6b, 0x64, 0x68, 0x65, 0x4d, 0x74, 0x6c, 0x62, 0x37, 0x31, 0x63, 0x5a, + 0x42, 0x44, 0x7a, 0x49, 0x30, 0x66, 0x6d, 0x67, 0x41, 0x4b, 0x68, 0x79, + 0x6e, 0x70, 0x0a, 0x56, 0x53, 0x4a, 0x59, 0x41, 0x43, 0x50, 0x71, 0x34, + 0x78, 0x4a, 0x44, 0x4b, 0x56, 0x74, 0x48, 0x43, 0x4e, 0x32, 0x4d, 0x51, + 0x57, 0x70, 0x6c, 0x42, 0x71, 0x6a, 0x6c, 0x49, 0x61, 0x70, 0x42, 0x74, + 0x4a, 0x55, 0x68, 0x6c, 0x62, 0x6c, 0x39, 0x30, 0x54, 0x53, 0x72, 0x45, + 0x39, 0x61, 0x74, 0x76, 0x4e, 0x7a, 0x69, 0x50, 0x54, 0x6e, 0x4e, 0x76, + 0x54, 0x35, 0x31, 0x63, 0x4b, 0x45, 0x59, 0x0a, 0x57, 0x51, 0x50, 0x4a, + 0x49, 0x72, 0x53, 0x50, 0x6e, 0x4e, 0x56, 0x65, 0x4b, 0x74, 0x65, 0x6c, + 0x74, 0x74, 0x51, 0x4b, 0x62, 0x66, 0x69, 0x33, 0x51, 0x42, 0x46, 0x47, + 0x6d, 0x68, 0x39, 0x35, 0x44, 0x6d, 0x4b, 0x2f, 0x44, 0x35, 0x66, 0x73, + 0x34, 0x43, 0x38, 0x66, 0x46, 0x35, 0x51, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, + 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, + 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, + 0x3d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x4f, 0x3d, + 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x4c, 0x74, 0x64, + 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, + 0x44, 0x69, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x20, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x53, 0x69, 0x67, 0x6e, + 0x69, 0x6e, 0x67, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, + 0x6f, 0x6d, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x74, 0x79, 0x20, 0x4f, 0x3d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, + 0x6d, 0x20, 0x4c, 0x74, 0x64, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x53, 0x65, + 0x63, 0x75, 0x72, 0x65, 0x20, 0x44, 0x69, 0x67, 0x69, 0x74, 0x61, 0x6c, + 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, + 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x0a, 0x23, 0x20, 0x4c, + 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x53, 0x74, 0x61, 0x72, 0x74, + 0x43, 0x6f, 0x6d, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x74, 0x79, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, + 0x6c, 0x3a, 0x20, 0x31, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x32, 0x32, 0x3a, 0x34, 0x64, 0x3a, 0x38, 0x66, 0x3a, 0x38, 0x61, 0x3a, + 0x66, 0x63, 0x3a, 0x66, 0x37, 0x3a, 0x33, 0x35, 0x3a, 0x63, 0x32, 0x3a, + 0x62, 0x62, 0x3a, 0x35, 0x37, 0x3a, 0x33, 0x34, 0x3a, 0x39, 0x30, 0x3a, + 0x37, 0x62, 0x3a, 0x38, 0x62, 0x3a, 0x32, 0x32, 0x3a, 0x31, 0x36, 0x0a, + 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x33, 0x65, 0x3a, 0x32, + 0x62, 0x3a, 0x66, 0x37, 0x3a, 0x66, 0x32, 0x3a, 0x30, 0x33, 0x3a, 0x31, + 0x62, 0x3a, 0x39, 0x36, 0x3a, 0x66, 0x33, 0x3a, 0x38, 0x63, 0x3a, 0x65, + 0x36, 0x3a, 0x63, 0x34, 0x3a, 0x64, 0x38, 0x3a, 0x61, 0x38, 0x3a, 0x35, + 0x64, 0x3a, 0x33, 0x65, 0x3a, 0x32, 0x64, 0x3a, 0x35, 0x38, 0x3a, 0x34, + 0x37, 0x3a, 0x36, 0x61, 0x3a, 0x30, 0x66, 0x0a, 0x23, 0x20, 0x53, 0x48, + 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, + 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x63, 0x37, 0x3a, 0x36, 0x36, 0x3a, + 0x61, 0x39, 0x3a, 0x62, 0x65, 0x3a, 0x66, 0x32, 0x3a, 0x64, 0x34, 0x3a, + 0x30, 0x37, 0x3a, 0x31, 0x63, 0x3a, 0x38, 0x36, 0x3a, 0x33, 0x61, 0x3a, + 0x33, 0x31, 0x3a, 0x61, 0x61, 0x3a, 0x34, 0x39, 0x3a, 0x32, 0x30, 0x3a, + 0x65, 0x38, 0x3a, 0x31, 0x33, 0x3a, 0x62, 0x32, 0x3a, 0x64, 0x31, 0x3a, + 0x39, 0x38, 0x3a, 0x36, 0x30, 0x3a, 0x38, 0x63, 0x3a, 0x62, 0x37, 0x3a, + 0x62, 0x37, 0x3a, 0x63, 0x66, 0x3a, 0x65, 0x32, 0x3a, 0x31, 0x31, 0x3a, + 0x34, 0x33, 0x3a, 0x62, 0x38, 0x3a, 0x33, 0x36, 0x3a, 0x64, 0x66, 0x3a, + 0x30, 0x39, 0x3a, 0x65, 0x61, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, + 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, + 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, + 0x49, 0x48, 0x79, 0x54, 0x43, 0x43, 0x42, 0x62, 0x47, 0x67, 0x41, 0x77, + 0x49, 0x42, 0x41, 0x67, 0x49, 0x42, 0x41, 0x54, 0x41, 0x4e, 0x42, 0x67, + 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, + 0x55, 0x46, 0x41, 0x44, 0x42, 0x39, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, + 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x4a, 0x54, 0x44, + 0x45, 0x57, 0x0a, 0x4d, 0x42, 0x51, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, + 0x68, 0x4d, 0x4e, 0x55, 0x33, 0x52, 0x68, 0x63, 0x6e, 0x52, 0x44, 0x62, + 0x32, 0x30, 0x67, 0x54, 0x48, 0x52, 0x6b, 0x4c, 0x6a, 0x45, 0x72, 0x4d, + 0x43, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x78, 0x4d, 0x69, 0x55, + 0x32, 0x56, 0x6a, 0x64, 0x58, 0x4a, 0x6c, 0x49, 0x45, 0x52, 0x70, 0x5a, + 0x32, 0x6c, 0x30, 0x59, 0x57, 0x77, 0x67, 0x0a, 0x51, 0x32, 0x56, 0x79, + 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, 0x55, 0x67, + 0x55, 0x32, 0x6c, 0x6e, 0x62, 0x6d, 0x6c, 0x75, 0x5a, 0x7a, 0x45, 0x70, + 0x4d, 0x43, 0x63, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x67, + 0x55, 0x33, 0x52, 0x68, 0x63, 0x6e, 0x52, 0x44, 0x62, 0x32, 0x30, 0x67, + 0x51, 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, + 0x0a, 0x64, 0x47, 0x6c, 0x76, 0x62, 0x69, 0x42, 0x42, 0x64, 0x58, 0x52, + 0x6f, 0x62, 0x33, 0x4a, 0x70, 0x64, 0x48, 0x6b, 0x77, 0x48, 0x68, 0x63, + 0x4e, 0x4d, 0x44, 0x59, 0x77, 0x4f, 0x54, 0x45, 0x33, 0x4d, 0x54, 0x6b, + 0x30, 0x4e, 0x6a, 0x4d, 0x32, 0x57, 0x68, 0x63, 0x4e, 0x4d, 0x7a, 0x59, + 0x77, 0x4f, 0x54, 0x45, 0x33, 0x4d, 0x54, 0x6b, 0x30, 0x4e, 0x6a, 0x4d, + 0x32, 0x57, 0x6a, 0x42, 0x39, 0x0a, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, + 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x4a, 0x54, 0x44, + 0x45, 0x57, 0x4d, 0x42, 0x51, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, + 0x4d, 0x4e, 0x55, 0x33, 0x52, 0x68, 0x63, 0x6e, 0x52, 0x44, 0x62, 0x32, + 0x30, 0x67, 0x54, 0x48, 0x52, 0x6b, 0x4c, 0x6a, 0x45, 0x72, 0x4d, 0x43, + 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x78, 0x4d, 0x69, 0x0a, 0x55, + 0x32, 0x56, 0x6a, 0x64, 0x58, 0x4a, 0x6c, 0x49, 0x45, 0x52, 0x70, 0x5a, + 0x32, 0x6c, 0x30, 0x59, 0x57, 0x77, 0x67, 0x51, 0x32, 0x56, 0x79, 0x64, + 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, 0x55, 0x67, 0x55, + 0x32, 0x6c, 0x6e, 0x62, 0x6d, 0x6c, 0x75, 0x5a, 0x7a, 0x45, 0x70, 0x4d, + 0x43, 0x63, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x67, 0x55, + 0x33, 0x52, 0x68, 0x0a, 0x63, 0x6e, 0x52, 0x44, 0x62, 0x32, 0x30, 0x67, + 0x51, 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, + 0x64, 0x47, 0x6c, 0x76, 0x62, 0x69, 0x42, 0x42, 0x64, 0x58, 0x52, 0x6f, + 0x62, 0x33, 0x4a, 0x70, 0x64, 0x48, 0x6b, 0x77, 0x67, 0x67, 0x49, 0x69, + 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, + 0x44, 0x51, 0x45, 0x42, 0x41, 0x51, 0x55, 0x41, 0x0a, 0x41, 0x34, 0x49, + 0x43, 0x44, 0x77, 0x41, 0x77, 0x67, 0x67, 0x49, 0x4b, 0x41, 0x6f, 0x49, + 0x43, 0x41, 0x51, 0x44, 0x42, 0x69, 0x4e, 0x73, 0x4a, 0x76, 0x47, 0x78, + 0x47, 0x66, 0x48, 0x69, 0x66, 0x6c, 0x58, 0x75, 0x31, 0x4d, 0x35, 0x44, + 0x79, 0x63, 0x6d, 0x4c, 0x57, 0x77, 0x54, 0x59, 0x67, 0x49, 0x69, 0x52, + 0x65, 0x7a, 0x75, 0x6c, 0x33, 0x38, 0x6b, 0x4d, 0x4b, 0x6f, 0x67, 0x5a, + 0x6b, 0x0a, 0x70, 0x4d, 0x79, 0x4f, 0x4e, 0x76, 0x67, 0x34, 0x35, 0x69, + 0x50, 0x77, 0x62, 0x6d, 0x32, 0x78, 0x50, 0x4e, 0x31, 0x79, 0x6f, 0x34, + 0x55, 0x63, 0x6f, 0x64, 0x4d, 0x39, 0x74, 0x44, 0x4d, 0x72, 0x30, 0x79, + 0x2b, 0x76, 0x2f, 0x75, 0x71, 0x77, 0x51, 0x56, 0x6c, 0x6e, 0x74, 0x73, + 0x51, 0x47, 0x66, 0x51, 0x71, 0x65, 0x64, 0x49, 0x58, 0x57, 0x65, 0x55, + 0x79, 0x41, 0x4e, 0x33, 0x72, 0x66, 0x0a, 0x4f, 0x51, 0x56, 0x53, 0x57, + 0x66, 0x66, 0x30, 0x47, 0x30, 0x5a, 0x44, 0x70, 0x4e, 0x4b, 0x46, 0x68, + 0x64, 0x4c, 0x44, 0x63, 0x66, 0x4e, 0x31, 0x59, 0x6a, 0x53, 0x36, 0x4c, + 0x49, 0x70, 0x2f, 0x48, 0x6f, 0x2f, 0x75, 0x37, 0x54, 0x54, 0x51, 0x45, + 0x63, 0x65, 0x57, 0x7a, 0x56, 0x49, 0x39, 0x75, 0x6a, 0x50, 0x57, 0x33, + 0x55, 0x33, 0x65, 0x43, 0x7a, 0x74, 0x4b, 0x53, 0x35, 0x2f, 0x43, 0x0a, + 0x4a, 0x69, 0x2f, 0x36, 0x74, 0x52, 0x59, 0x63, 0x63, 0x6a, 0x56, 0x33, + 0x79, 0x6a, 0x78, 0x64, 0x35, 0x73, 0x72, 0x68, 0x4a, 0x6f, 0x73, 0x61, + 0x4e, 0x6e, 0x5a, 0x63, 0x41, 0x64, 0x74, 0x30, 0x46, 0x43, 0x58, 0x2b, + 0x37, 0x62, 0x57, 0x67, 0x69, 0x41, 0x2f, 0x64, 0x65, 0x4d, 0x6f, 0x74, + 0x48, 0x77, 0x65, 0x58, 0x4d, 0x41, 0x45, 0x74, 0x63, 0x6e, 0x6e, 0x36, + 0x52, 0x74, 0x59, 0x54, 0x0a, 0x4b, 0x71, 0x69, 0x35, 0x70, 0x71, 0x75, + 0x44, 0x53, 0x52, 0x33, 0x6c, 0x38, 0x75, 0x2f, 0x64, 0x35, 0x41, 0x47, + 0x4f, 0x47, 0x41, 0x71, 0x50, 0x59, 0x31, 0x4d, 0x57, 0x68, 0x57, 0x4b, + 0x70, 0x44, 0x68, 0x6b, 0x36, 0x7a, 0x4c, 0x56, 0x6d, 0x70, 0x73, 0x4a, + 0x72, 0x64, 0x41, 0x66, 0x6b, 0x4b, 0x2b, 0x46, 0x32, 0x50, 0x72, 0x52, + 0x74, 0x32, 0x50, 0x5a, 0x45, 0x34, 0x58, 0x4e, 0x69, 0x0a, 0x48, 0x7a, + 0x76, 0x45, 0x76, 0x71, 0x42, 0x54, 0x56, 0x69, 0x56, 0x73, 0x55, 0x51, + 0x6e, 0x33, 0x71, 0x71, 0x76, 0x4b, 0x76, 0x33, 0x62, 0x39, 0x62, 0x5a, + 0x76, 0x7a, 0x6e, 0x64, 0x75, 0x2f, 0x50, 0x57, 0x61, 0x38, 0x44, 0x46, + 0x61, 0x71, 0x72, 0x35, 0x68, 0x49, 0x6c, 0x54, 0x70, 0x4c, 0x33, 0x36, + 0x64, 0x59, 0x55, 0x4e, 0x6b, 0x34, 0x64, 0x61, 0x6c, 0x62, 0x36, 0x6b, + 0x4d, 0x4d, 0x0a, 0x41, 0x76, 0x2b, 0x5a, 0x36, 0x2b, 0x68, 0x73, 0x54, + 0x58, 0x42, 0x62, 0x4b, 0x57, 0x57, 0x63, 0x33, 0x61, 0x70, 0x64, 0x7a, + 0x4b, 0x38, 0x42, 0x4d, 0x65, 0x77, 0x4d, 0x36, 0x39, 0x4b, 0x4e, 0x36, + 0x4f, 0x71, 0x63, 0x65, 0x2b, 0x5a, 0x75, 0x39, 0x79, 0x64, 0x6d, 0x44, + 0x42, 0x70, 0x49, 0x31, 0x32, 0x35, 0x43, 0x34, 0x7a, 0x2f, 0x65, 0x49, + 0x54, 0x35, 0x37, 0x34, 0x51, 0x31, 0x77, 0x0a, 0x2b, 0x32, 0x4f, 0x71, + 0x71, 0x47, 0x77, 0x61, 0x56, 0x4c, 0x52, 0x63, 0x4a, 0x58, 0x72, 0x4a, + 0x6f, 0x73, 0x6d, 0x4c, 0x46, 0x71, 0x61, 0x37, 0x4c, 0x48, 0x34, 0x58, + 0x58, 0x67, 0x56, 0x4e, 0x57, 0x47, 0x34, 0x53, 0x48, 0x51, 0x48, 0x75, + 0x45, 0x68, 0x41, 0x4e, 0x78, 0x6a, 0x4a, 0x2f, 0x47, 0x50, 0x2f, 0x38, + 0x39, 0x50, 0x72, 0x4e, 0x62, 0x70, 0x48, 0x6f, 0x4e, 0x6b, 0x6d, 0x2b, + 0x0a, 0x47, 0x6b, 0x68, 0x70, 0x69, 0x38, 0x4b, 0x57, 0x54, 0x52, 0x6f, + 0x53, 0x73, 0x6d, 0x6b, 0x58, 0x77, 0x51, 0x71, 0x51, 0x31, 0x76, 0x70, + 0x35, 0x49, 0x6b, 0x69, 0x2f, 0x75, 0x6e, 0x74, 0x70, 0x2b, 0x48, 0x44, + 0x48, 0x2b, 0x6e, 0x6f, 0x33, 0x32, 0x4e, 0x67, 0x4e, 0x30, 0x6e, 0x5a, + 0x50, 0x56, 0x2f, 0x2b, 0x51, 0x74, 0x2b, 0x4f, 0x52, 0x30, 0x74, 0x33, + 0x76, 0x77, 0x6d, 0x43, 0x33, 0x0a, 0x5a, 0x7a, 0x72, 0x64, 0x2f, 0x71, + 0x71, 0x63, 0x38, 0x4e, 0x53, 0x4c, 0x66, 0x33, 0x49, 0x69, 0x7a, 0x73, + 0x61, 0x66, 0x6c, 0x37, 0x62, 0x34, 0x72, 0x34, 0x71, 0x67, 0x45, 0x4b, + 0x6a, 0x5a, 0x2b, 0x78, 0x6a, 0x47, 0x74, 0x72, 0x56, 0x63, 0x55, 0x6a, + 0x79, 0x4a, 0x74, 0x68, 0x6b, 0x71, 0x63, 0x77, 0x45, 0x4b, 0x44, 0x77, + 0x4f, 0x7a, 0x45, 0x6d, 0x44, 0x79, 0x65, 0x69, 0x2b, 0x42, 0x0a, 0x32, + 0x36, 0x4e, 0x75, 0x2f, 0x79, 0x59, 0x77, 0x6c, 0x2f, 0x57, 0x4c, 0x33, + 0x59, 0x6c, 0x58, 0x74, 0x71, 0x30, 0x39, 0x73, 0x36, 0x38, 0x72, 0x78, + 0x62, 0x64, 0x32, 0x41, 0x76, 0x43, 0x6c, 0x31, 0x69, 0x75, 0x61, 0x68, + 0x68, 0x51, 0x71, 0x63, 0x76, 0x62, 0x6a, 0x4d, 0x34, 0x78, 0x64, 0x43, + 0x55, 0x73, 0x54, 0x33, 0x37, 0x75, 0x4d, 0x64, 0x42, 0x4e, 0x53, 0x53, + 0x77, 0x49, 0x44, 0x0a, 0x41, 0x51, 0x41, 0x42, 0x6f, 0x34, 0x49, 0x43, + 0x55, 0x6a, 0x43, 0x43, 0x41, 0x6b, 0x34, 0x77, 0x44, 0x41, 0x59, 0x44, + 0x56, 0x52, 0x30, 0x54, 0x42, 0x41, 0x55, 0x77, 0x41, 0x77, 0x45, 0x42, + 0x2f, 0x7a, 0x41, 0x4c, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x38, 0x45, + 0x42, 0x41, 0x4d, 0x43, 0x41, 0x61, 0x34, 0x77, 0x48, 0x51, 0x59, 0x44, + 0x56, 0x52, 0x30, 0x4f, 0x42, 0x42, 0x59, 0x45, 0x0a, 0x46, 0x45, 0x34, + 0x4c, 0x37, 0x78, 0x71, 0x6b, 0x51, 0x46, 0x75, 0x6c, 0x46, 0x32, 0x6d, + 0x48, 0x4d, 0x4d, 0x6f, 0x30, 0x61, 0x45, 0x50, 0x51, 0x51, 0x61, 0x37, + 0x79, 0x4d, 0x47, 0x51, 0x47, 0x41, 0x31, 0x55, 0x64, 0x48, 0x77, 0x52, + 0x64, 0x4d, 0x46, 0x73, 0x77, 0x4c, 0x4b, 0x41, 0x71, 0x6f, 0x43, 0x69, + 0x47, 0x4a, 0x6d, 0x68, 0x30, 0x64, 0x48, 0x41, 0x36, 0x4c, 0x79, 0x39, + 0x6a, 0x0a, 0x5a, 0x58, 0x4a, 0x30, 0x4c, 0x6e, 0x4e, 0x30, 0x59, 0x58, + 0x4a, 0x30, 0x59, 0x32, 0x39, 0x74, 0x4c, 0x6d, 0x39, 0x79, 0x5a, 0x79, + 0x39, 0x7a, 0x5a, 0x6e, 0x4e, 0x6a, 0x59, 0x53, 0x31, 0x6a, 0x63, 0x6d, + 0x77, 0x75, 0x59, 0x33, 0x4a, 0x73, 0x4d, 0x43, 0x75, 0x67, 0x4b, 0x61, + 0x41, 0x6e, 0x68, 0x69, 0x56, 0x6f, 0x64, 0x48, 0x52, 0x77, 0x4f, 0x69, + 0x38, 0x76, 0x59, 0x33, 0x4a, 0x73, 0x0a, 0x4c, 0x6e, 0x4e, 0x30, 0x59, + 0x58, 0x4a, 0x30, 0x59, 0x32, 0x39, 0x74, 0x4c, 0x6d, 0x39, 0x79, 0x5a, + 0x79, 0x39, 0x7a, 0x5a, 0x6e, 0x4e, 0x6a, 0x59, 0x53, 0x31, 0x6a, 0x63, + 0x6d, 0x77, 0x75, 0x59, 0x33, 0x4a, 0x73, 0x4d, 0x49, 0x49, 0x42, 0x58, + 0x51, 0x59, 0x44, 0x56, 0x52, 0x30, 0x67, 0x42, 0x49, 0x49, 0x42, 0x56, + 0x44, 0x43, 0x43, 0x41, 0x56, 0x41, 0x77, 0x67, 0x67, 0x46, 0x4d, 0x0a, + 0x42, 0x67, 0x73, 0x72, 0x42, 0x67, 0x45, 0x45, 0x41, 0x59, 0x47, 0x31, + 0x4e, 0x77, 0x45, 0x42, 0x41, 0x54, 0x43, 0x43, 0x41, 0x54, 0x73, 0x77, + 0x4c, 0x77, 0x59, 0x49, 0x4b, 0x77, 0x59, 0x42, 0x42, 0x51, 0x55, 0x48, + 0x41, 0x67, 0x45, 0x57, 0x49, 0x32, 0x68, 0x30, 0x64, 0x48, 0x41, 0x36, + 0x4c, 0x79, 0x39, 0x6a, 0x5a, 0x58, 0x4a, 0x30, 0x4c, 0x6e, 0x4e, 0x30, + 0x59, 0x58, 0x4a, 0x30, 0x0a, 0x59, 0x32, 0x39, 0x74, 0x4c, 0x6d, 0x39, + 0x79, 0x5a, 0x79, 0x39, 0x77, 0x62, 0x32, 0x78, 0x70, 0x59, 0x33, 0x6b, + 0x75, 0x63, 0x47, 0x52, 0x6d, 0x4d, 0x44, 0x55, 0x47, 0x43, 0x43, 0x73, + 0x47, 0x41, 0x51, 0x55, 0x46, 0x42, 0x77, 0x49, 0x42, 0x46, 0x69, 0x6c, + 0x6f, 0x64, 0x48, 0x52, 0x77, 0x4f, 0x69, 0x38, 0x76, 0x59, 0x32, 0x56, + 0x79, 0x64, 0x43, 0x35, 0x7a, 0x64, 0x47, 0x46, 0x79, 0x0a, 0x64, 0x47, + 0x4e, 0x76, 0x62, 0x53, 0x35, 0x76, 0x63, 0x6d, 0x63, 0x76, 0x61, 0x57, + 0x35, 0x30, 0x5a, 0x58, 0x4a, 0x74, 0x5a, 0x57, 0x52, 0x70, 0x59, 0x58, + 0x52, 0x6c, 0x4c, 0x6e, 0x42, 0x6b, 0x5a, 0x6a, 0x43, 0x42, 0x30, 0x41, + 0x59, 0x49, 0x4b, 0x77, 0x59, 0x42, 0x42, 0x51, 0x55, 0x48, 0x41, 0x67, + 0x49, 0x77, 0x67, 0x63, 0x4d, 0x77, 0x4a, 0x78, 0x59, 0x67, 0x55, 0x33, + 0x52, 0x68, 0x0a, 0x63, 0x6e, 0x51, 0x67, 0x51, 0x32, 0x39, 0x74, 0x62, + 0x57, 0x56, 0x79, 0x59, 0x32, 0x6c, 0x68, 0x62, 0x43, 0x41, 0x6f, 0x55, + 0x33, 0x52, 0x68, 0x63, 0x6e, 0x52, 0x44, 0x62, 0x32, 0x30, 0x70, 0x49, + 0x45, 0x78, 0x30, 0x5a, 0x43, 0x34, 0x77, 0x41, 0x77, 0x49, 0x42, 0x41, + 0x52, 0x71, 0x42, 0x6c, 0x30, 0x78, 0x70, 0x62, 0x57, 0x6c, 0x30, 0x5a, + 0x57, 0x51, 0x67, 0x54, 0x47, 0x6c, 0x68, 0x0a, 0x59, 0x6d, 0x6c, 0x73, + 0x61, 0x58, 0x52, 0x35, 0x4c, 0x43, 0x42, 0x79, 0x5a, 0x57, 0x46, 0x6b, + 0x49, 0x48, 0x52, 0x6f, 0x5a, 0x53, 0x42, 0x7a, 0x5a, 0x57, 0x4e, 0x30, + 0x61, 0x57, 0x39, 0x75, 0x49, 0x43, 0x70, 0x4d, 0x5a, 0x57, 0x64, 0x68, + 0x62, 0x43, 0x42, 0x4d, 0x61, 0x57, 0x31, 0x70, 0x64, 0x47, 0x46, 0x30, + 0x61, 0x57, 0x39, 0x75, 0x63, 0x79, 0x6f, 0x67, 0x62, 0x32, 0x59, 0x67, + 0x0a, 0x64, 0x47, 0x68, 0x6c, 0x49, 0x46, 0x4e, 0x30, 0x59, 0x58, 0x4a, + 0x30, 0x51, 0x32, 0x39, 0x74, 0x49, 0x45, 0x4e, 0x6c, 0x63, 0x6e, 0x52, + 0x70, 0x5a, 0x6d, 0x6c, 0x6a, 0x59, 0x58, 0x52, 0x70, 0x62, 0x32, 0x34, + 0x67, 0x51, 0x58, 0x56, 0x30, 0x61, 0x47, 0x39, 0x79, 0x61, 0x58, 0x52, + 0x35, 0x49, 0x46, 0x42, 0x76, 0x62, 0x47, 0x6c, 0x6a, 0x65, 0x53, 0x42, + 0x68, 0x64, 0x6d, 0x46, 0x70, 0x0a, 0x62, 0x47, 0x46, 0x69, 0x62, 0x47, + 0x55, 0x67, 0x59, 0x58, 0x51, 0x67, 0x61, 0x48, 0x52, 0x30, 0x63, 0x44, + 0x6f, 0x76, 0x4c, 0x32, 0x4e, 0x6c, 0x63, 0x6e, 0x51, 0x75, 0x63, 0x33, + 0x52, 0x68, 0x63, 0x6e, 0x52, 0x6a, 0x62, 0x32, 0x30, 0x75, 0x62, 0x33, + 0x4a, 0x6e, 0x4c, 0x33, 0x42, 0x76, 0x62, 0x47, 0x6c, 0x6a, 0x65, 0x53, + 0x35, 0x77, 0x5a, 0x47, 0x59, 0x77, 0x45, 0x51, 0x59, 0x4a, 0x0a, 0x59, + 0x49, 0x5a, 0x49, 0x41, 0x59, 0x62, 0x34, 0x51, 0x67, 0x45, 0x42, 0x42, + 0x41, 0x51, 0x44, 0x41, 0x67, 0x41, 0x48, 0x4d, 0x44, 0x67, 0x47, 0x43, + 0x57, 0x43, 0x47, 0x53, 0x41, 0x47, 0x47, 0x2b, 0x45, 0x49, 0x42, 0x44, + 0x51, 0x51, 0x72, 0x46, 0x69, 0x6c, 0x54, 0x64, 0x47, 0x46, 0x79, 0x64, + 0x45, 0x4e, 0x76, 0x62, 0x53, 0x42, 0x47, 0x63, 0x6d, 0x56, 0x6c, 0x49, + 0x46, 0x4e, 0x54, 0x0a, 0x54, 0x43, 0x42, 0x44, 0x5a, 0x58, 0x4a, 0x30, + 0x61, 0x57, 0x5a, 0x70, 0x59, 0x32, 0x46, 0x30, 0x61, 0x57, 0x39, 0x75, + 0x49, 0x45, 0x46, 0x31, 0x64, 0x47, 0x68, 0x76, 0x63, 0x6d, 0x6c, 0x30, + 0x65, 0x54, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, + 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x41, 0x4f, 0x43, + 0x41, 0x67, 0x45, 0x41, 0x46, 0x6d, 0x79, 0x5a, 0x0a, 0x39, 0x47, 0x59, + 0x4d, 0x4e, 0x50, 0x58, 0x51, 0x68, 0x56, 0x35, 0x39, 0x43, 0x75, 0x7a, + 0x61, 0x45, 0x45, 0x34, 0x34, 0x48, 0x46, 0x37, 0x66, 0x70, 0x69, 0x55, + 0x46, 0x53, 0x35, 0x45, 0x79, 0x77, 0x65, 0x67, 0x37, 0x38, 0x54, 0x33, + 0x64, 0x52, 0x41, 0x6c, 0x62, 0x42, 0x30, 0x6d, 0x4b, 0x4b, 0x63, 0x74, + 0x6d, 0x41, 0x72, 0x65, 0x78, 0x6d, 0x76, 0x63, 0x6c, 0x6d, 0x41, 0x6b, + 0x38, 0x0a, 0x6a, 0x68, 0x76, 0x68, 0x33, 0x54, 0x61, 0x48, 0x4b, 0x30, + 0x75, 0x37, 0x61, 0x4e, 0x4d, 0x35, 0x5a, 0x6a, 0x32, 0x67, 0x4a, 0x73, + 0x66, 0x79, 0x4f, 0x5a, 0x45, 0x64, 0x55, 0x61, 0x75, 0x43, 0x65, 0x33, + 0x37, 0x56, 0x7a, 0x6c, 0x72, 0x6b, 0x34, 0x67, 0x4e, 0x58, 0x63, 0x47, + 0x6d, 0x58, 0x43, 0x50, 0x6c, 0x65, 0x57, 0x4b, 0x59, 0x4b, 0x33, 0x34, + 0x77, 0x47, 0x6d, 0x6b, 0x55, 0x57, 0x0a, 0x46, 0x6a, 0x67, 0x4b, 0x58, + 0x6c, 0x66, 0x32, 0x59, 0x73, 0x64, 0x36, 0x41, 0x67, 0x58, 0x6d, 0x76, + 0x42, 0x36, 0x31, 0x38, 0x70, 0x37, 0x30, 0x71, 0x53, 0x6d, 0x44, 0x2b, + 0x4c, 0x49, 0x55, 0x34, 0x32, 0x34, 0x6f, 0x68, 0x30, 0x54, 0x44, 0x6b, + 0x42, 0x72, 0x65, 0x4f, 0x4b, 0x6b, 0x38, 0x72, 0x45, 0x4e, 0x4e, 0x5a, + 0x45, 0x58, 0x4f, 0x33, 0x53, 0x69, 0x70, 0x58, 0x50, 0x4a, 0x7a, 0x0a, + 0x65, 0x77, 0x54, 0x34, 0x46, 0x2b, 0x69, 0x72, 0x73, 0x66, 0x4d, 0x75, + 0x58, 0x47, 0x52, 0x75, 0x63, 0x7a, 0x45, 0x36, 0x45, 0x72, 0x69, 0x38, + 0x73, 0x78, 0x48, 0x6b, 0x66, 0x59, 0x2b, 0x42, 0x55, 0x5a, 0x6f, 0x37, + 0x6a, 0x59, 0x6e, 0x30, 0x54, 0x5a, 0x4e, 0x6d, 0x65, 0x7a, 0x77, 0x44, + 0x37, 0x64, 0x4f, 0x61, 0x48, 0x5a, 0x72, 0x7a, 0x5a, 0x56, 0x44, 0x31, + 0x6f, 0x4e, 0x42, 0x31, 0x0a, 0x6e, 0x79, 0x2b, 0x76, 0x38, 0x4f, 0x71, + 0x43, 0x51, 0x35, 0x6a, 0x34, 0x61, 0x5a, 0x79, 0x4a, 0x65, 0x63, 0x52, + 0x44, 0x6a, 0x6b, 0x5a, 0x79, 0x34, 0x32, 0x51, 0x32, 0x45, 0x71, 0x2f, + 0x33, 0x4a, 0x52, 0x34, 0x34, 0x69, 0x5a, 0x42, 0x33, 0x66, 0x73, 0x4e, + 0x72, 0x61, 0x72, 0x6e, 0x44, 0x79, 0x30, 0x52, 0x4c, 0x72, 0x48, 0x69, + 0x51, 0x69, 0x2b, 0x66, 0x48, 0x4c, 0x42, 0x35, 0x4c, 0x0a, 0x45, 0x55, + 0x54, 0x49, 0x4e, 0x46, 0x49, 0x6e, 0x7a, 0x51, 0x70, 0x64, 0x6e, 0x34, + 0x58, 0x42, 0x69, 0x64, 0x55, 0x61, 0x65, 0x50, 0x4b, 0x56, 0x45, 0x46, + 0x4d, 0x79, 0x33, 0x59, 0x43, 0x45, 0x5a, 0x6e, 0x58, 0x5a, 0x74, 0x57, + 0x67, 0x6f, 0x2b, 0x32, 0x45, 0x75, 0x76, 0x6f, 0x53, 0x6f, 0x4f, 0x4d, + 0x43, 0x5a, 0x45, 0x6f, 0x61, 0x6c, 0x48, 0x6d, 0x64, 0x6b, 0x72, 0x51, + 0x59, 0x75, 0x0a, 0x4c, 0x36, 0x6c, 0x77, 0x68, 0x63, 0x65, 0x57, 0x44, + 0x33, 0x79, 0x4a, 0x5a, 0x66, 0x57, 0x4f, 0x51, 0x31, 0x51, 0x4f, 0x71, + 0x39, 0x32, 0x6c, 0x67, 0x44, 0x6d, 0x55, 0x59, 0x4d, 0x41, 0x30, 0x79, + 0x5a, 0x5a, 0x77, 0x4c, 0x4b, 0x4d, 0x53, 0x39, 0x52, 0x39, 0x49, 0x65, + 0x37, 0x30, 0x63, 0x66, 0x6d, 0x75, 0x33, 0x6e, 0x5a, 0x44, 0x30, 0x49, + 0x6a, 0x75, 0x75, 0x2b, 0x50, 0x77, 0x71, 0x0a, 0x79, 0x76, 0x71, 0x43, + 0x55, 0x71, 0x44, 0x76, 0x72, 0x30, 0x74, 0x56, 0x6b, 0x2b, 0x76, 0x42, + 0x74, 0x66, 0x41, 0x69, 0x69, 0x36, 0x77, 0x30, 0x54, 0x69, 0x59, 0x69, + 0x42, 0x4b, 0x47, 0x48, 0x4c, 0x48, 0x56, 0x4b, 0x74, 0x2b, 0x56, 0x39, + 0x45, 0x39, 0x65, 0x34, 0x44, 0x47, 0x54, 0x41, 0x4e, 0x74, 0x4c, 0x4a, + 0x4c, 0x34, 0x59, 0x53, 0x6a, 0x43, 0x4d, 0x4a, 0x77, 0x52, 0x75, 0x43, + 0x0a, 0x4f, 0x33, 0x4e, 0x4a, 0x6f, 0x32, 0x70, 0x58, 0x68, 0x35, 0x54, + 0x6c, 0x31, 0x6e, 0x6a, 0x46, 0x6d, 0x55, 0x4e, 0x6a, 0x34, 0x30, 0x33, + 0x67, 0x64, 0x79, 0x33, 0x68, 0x5a, 0x5a, 0x6c, 0x79, 0x61, 0x51, 0x51, + 0x61, 0x52, 0x77, 0x6e, 0x6d, 0x44, 0x77, 0x46, 0x57, 0x4a, 0x50, 0x73, + 0x66, 0x76, 0x77, 0x35, 0x35, 0x71, 0x56, 0x67, 0x75, 0x75, 0x63, 0x51, + 0x4a, 0x41, 0x58, 0x36, 0x56, 0x0a, 0x75, 0x6d, 0x30, 0x41, 0x42, 0x6a, + 0x36, 0x79, 0x36, 0x6b, 0x6f, 0x51, 0x4f, 0x64, 0x6a, 0x51, 0x4b, 0x2f, + 0x57, 0x2f, 0x37, 0x48, 0x57, 0x2f, 0x6c, 0x77, 0x4c, 0x46, 0x43, 0x52, + 0x73, 0x49, 0x33, 0x46, 0x55, 0x33, 0x34, 0x6f, 0x48, 0x37, 0x4e, 0x34, + 0x52, 0x44, 0x59, 0x69, 0x44, 0x4b, 0x35, 0x31, 0x5a, 0x4c, 0x5a, 0x65, + 0x72, 0x2b, 0x62, 0x4d, 0x45, 0x6b, 0x6b, 0x79, 0x53, 0x68, 0x0a, 0x4e, + 0x4f, 0x73, 0x46, 0x2f, 0x35, 0x6f, 0x69, 0x72, 0x70, 0x74, 0x39, 0x50, + 0x2f, 0x46, 0x6c, 0x55, 0x51, 0x71, 0x6d, 0x4d, 0x47, 0x71, 0x7a, 0x39, + 0x49, 0x67, 0x63, 0x67, 0x41, 0x33, 0x38, 0x63, 0x6f, 0x72, 0x6f, 0x67, + 0x31, 0x34, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, + 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, + 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x44, 0x69, 0x67, 0x69, + 0x43, 0x65, 0x72, 0x74, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x65, 0x64, + 0x20, 0x49, 0x44, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, + 0x4f, 0x3d, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, + 0x6e, 0x63, 0x20, 0x4f, 0x55, 0x3d, 0x77, 0x77, 0x77, 0x2e, 0x64, 0x69, + 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, 0x23, + 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, + 0x3d, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x41, 0x73, + 0x73, 0x75, 0x72, 0x65, 0x64, 0x20, 0x49, 0x44, 0x20, 0x52, 0x6f, 0x6f, + 0x74, 0x20, 0x43, 0x41, 0x20, 0x4f, 0x3d, 0x44, 0x69, 0x67, 0x69, 0x43, + 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x20, 0x4f, 0x55, 0x3d, 0x77, + 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, + 0x63, 0x6f, 0x6d, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x41, + 0x73, 0x73, 0x75, 0x72, 0x65, 0x64, 0x20, 0x49, 0x44, 0x20, 0x52, 0x6f, + 0x6f, 0x74, 0x20, 0x43, 0x41, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, + 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x31, 0x37, 0x31, 0x35, 0x34, 0x37, 0x31, + 0x37, 0x39, 0x33, 0x34, 0x31, 0x32, 0x30, 0x35, 0x38, 0x37, 0x38, 0x36, + 0x32, 0x31, 0x36, 0x37, 0x37, 0x39, 0x34, 0x39, 0x31, 0x34, 0x30, 0x37, + 0x31, 0x34, 0x32, 0x35, 0x30, 0x38, 0x31, 0x0a, 0x23, 0x20, 0x4d, 0x44, + 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, + 0x74, 0x3a, 0x20, 0x38, 0x37, 0x3a, 0x63, 0x65, 0x3a, 0x30, 0x62, 0x3a, + 0x37, 0x62, 0x3a, 0x32, 0x61, 0x3a, 0x30, 0x65, 0x3a, 0x34, 0x39, 0x3a, + 0x30, 0x30, 0x3a, 0x65, 0x31, 0x3a, 0x35, 0x38, 0x3a, 0x37, 0x31, 0x3a, + 0x39, 0x62, 0x3a, 0x33, 0x37, 0x3a, 0x61, 0x38, 0x3a, 0x39, 0x33, 0x3a, + 0x37, 0x32, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, + 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x30, + 0x35, 0x3a, 0x36, 0x33, 0x3a, 0x62, 0x38, 0x3a, 0x36, 0x33, 0x3a, 0x30, + 0x64, 0x3a, 0x36, 0x32, 0x3a, 0x64, 0x37, 0x3a, 0x35, 0x61, 0x3a, 0x62, + 0x62, 0x3a, 0x63, 0x38, 0x3a, 0x61, 0x62, 0x3a, 0x31, 0x65, 0x3a, 0x34, + 0x62, 0x3a, 0x64, 0x66, 0x3a, 0x62, 0x35, 0x3a, 0x61, 0x38, 0x3a, 0x39, + 0x39, 0x3a, 0x62, 0x32, 0x3a, 0x34, 0x64, 0x3a, 0x34, 0x33, 0x0a, 0x23, + 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, + 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x33, 0x65, 0x3a, + 0x39, 0x30, 0x3a, 0x39, 0x39, 0x3a, 0x62, 0x35, 0x3a, 0x30, 0x31, 0x3a, + 0x35, 0x65, 0x3a, 0x38, 0x66, 0x3a, 0x34, 0x38, 0x3a, 0x36, 0x63, 0x3a, + 0x30, 0x30, 0x3a, 0x62, 0x63, 0x3a, 0x65, 0x61, 0x3a, 0x39, 0x64, 0x3a, + 0x31, 0x31, 0x3a, 0x31, 0x65, 0x3a, 0x65, 0x37, 0x3a, 0x32, 0x31, 0x3a, + 0x66, 0x61, 0x3a, 0x62, 0x61, 0x3a, 0x33, 0x35, 0x3a, 0x35, 0x61, 0x3a, + 0x38, 0x39, 0x3a, 0x62, 0x63, 0x3a, 0x66, 0x31, 0x3a, 0x64, 0x66, 0x3a, + 0x36, 0x39, 0x3a, 0x35, 0x36, 0x3a, 0x31, 0x65, 0x3a, 0x33, 0x64, 0x3a, + 0x63, 0x36, 0x3a, 0x33, 0x32, 0x3a, 0x35, 0x63, 0x0a, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, + 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x0a, 0x4d, 0x49, 0x49, 0x44, 0x74, 0x7a, 0x43, 0x43, 0x41, 0x70, 0x2b, + 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x51, 0x44, 0x4f, 0x66, + 0x67, 0x35, 0x52, 0x66, 0x59, 0x52, 0x76, 0x36, 0x50, 0x35, 0x57, 0x44, + 0x38, 0x47, 0x2f, 0x41, 0x77, 0x4f, 0x54, 0x41, 0x4e, 0x42, 0x67, 0x6b, + 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, + 0x46, 0x41, 0x44, 0x42, 0x6c, 0x0a, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, + 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, 0x55, 0x7a, + 0x45, 0x56, 0x4d, 0x42, 0x4d, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, + 0x4d, 0x4d, 0x52, 0x47, 0x6c, 0x6e, 0x61, 0x55, 0x4e, 0x6c, 0x63, 0x6e, + 0x51, 0x67, 0x53, 0x57, 0x35, 0x6a, 0x4d, 0x52, 0x6b, 0x77, 0x46, 0x77, + 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, 0x78, 0x42, 0x33, 0x0a, 0x64, + 0x33, 0x63, 0x75, 0x5a, 0x47, 0x6c, 0x6e, 0x61, 0x57, 0x4e, 0x6c, 0x63, + 0x6e, 0x51, 0x75, 0x59, 0x32, 0x39, 0x74, 0x4d, 0x53, 0x51, 0x77, 0x49, + 0x67, 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, 0x78, 0x74, 0x45, 0x61, + 0x57, 0x64, 0x70, 0x51, 0x32, 0x56, 0x79, 0x64, 0x43, 0x42, 0x42, 0x63, + 0x33, 0x4e, 0x31, 0x63, 0x6d, 0x56, 0x6b, 0x49, 0x45, 0x6c, 0x45, 0x49, + 0x46, 0x4a, 0x76, 0x0a, 0x62, 0x33, 0x51, 0x67, 0x51, 0x30, 0x45, 0x77, + 0x48, 0x68, 0x63, 0x4e, 0x4d, 0x44, 0x59, 0x78, 0x4d, 0x54, 0x45, 0x77, + 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x57, 0x68, 0x63, 0x4e, + 0x4d, 0x7a, 0x45, 0x78, 0x4d, 0x54, 0x45, 0x77, 0x4d, 0x44, 0x41, 0x77, + 0x4d, 0x44, 0x41, 0x77, 0x57, 0x6a, 0x42, 0x6c, 0x4d, 0x51, 0x73, 0x77, + 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x0a, 0x45, 0x77, 0x4a, + 0x56, 0x55, 0x7a, 0x45, 0x56, 0x4d, 0x42, 0x4d, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x43, 0x68, 0x4d, 0x4d, 0x52, 0x47, 0x6c, 0x6e, 0x61, 0x55, 0x4e, + 0x6c, 0x63, 0x6e, 0x51, 0x67, 0x53, 0x57, 0x35, 0x6a, 0x4d, 0x52, 0x6b, + 0x77, 0x46, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, 0x78, 0x42, + 0x33, 0x64, 0x33, 0x63, 0x75, 0x5a, 0x47, 0x6c, 0x6e, 0x61, 0x57, 0x4e, + 0x6c, 0x0a, 0x63, 0x6e, 0x51, 0x75, 0x59, 0x32, 0x39, 0x74, 0x4d, 0x53, + 0x51, 0x77, 0x49, 0x67, 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, 0x78, + 0x74, 0x45, 0x61, 0x57, 0x64, 0x70, 0x51, 0x32, 0x56, 0x79, 0x64, 0x43, + 0x42, 0x42, 0x63, 0x33, 0x4e, 0x31, 0x63, 0x6d, 0x56, 0x6b, 0x49, 0x45, + 0x6c, 0x45, 0x49, 0x46, 0x4a, 0x76, 0x62, 0x33, 0x51, 0x67, 0x51, 0x30, + 0x45, 0x77, 0x67, 0x67, 0x45, 0x69, 0x0a, 0x4d, 0x41, 0x30, 0x47, 0x43, + 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x41, + 0x51, 0x55, 0x41, 0x41, 0x34, 0x49, 0x42, 0x44, 0x77, 0x41, 0x77, 0x67, + 0x67, 0x45, 0x4b, 0x41, 0x6f, 0x49, 0x42, 0x41, 0x51, 0x43, 0x74, 0x44, + 0x68, 0x58, 0x4f, 0x35, 0x45, 0x4f, 0x41, 0x58, 0x4c, 0x47, 0x48, 0x38, + 0x37, 0x64, 0x67, 0x2b, 0x58, 0x45, 0x53, 0x70, 0x61, 0x37, 0x63, 0x0a, + 0x4a, 0x70, 0x53, 0x49, 0x71, 0x76, 0x54, 0x4f, 0x39, 0x53, 0x41, 0x35, + 0x4b, 0x46, 0x68, 0x67, 0x44, 0x50, 0x69, 0x41, 0x32, 0x71, 0x6b, 0x56, + 0x6c, 0x54, 0x4a, 0x68, 0x50, 0x4c, 0x57, 0x78, 0x4b, 0x49, 0x53, 0x4b, + 0x69, 0x74, 0x79, 0x66, 0x43, 0x67, 0x79, 0x44, 0x46, 0x33, 0x71, 0x50, + 0x6b, 0x4b, 0x79, 0x4b, 0x35, 0x33, 0x6c, 0x54, 0x58, 0x44, 0x47, 0x45, + 0x4b, 0x76, 0x59, 0x50, 0x0a, 0x6d, 0x44, 0x49, 0x32, 0x64, 0x73, 0x7a, + 0x65, 0x33, 0x54, 0x79, 0x6f, 0x6f, 0x75, 0x39, 0x71, 0x2b, 0x79, 0x48, + 0x79, 0x55, 0x6d, 0x48, 0x66, 0x6e, 0x79, 0x44, 0x58, 0x48, 0x2b, 0x4b, + 0x78, 0x32, 0x66, 0x34, 0x59, 0x5a, 0x4e, 0x49, 0x53, 0x57, 0x31, 0x2f, + 0x35, 0x57, 0x42, 0x67, 0x31, 0x76, 0x45, 0x66, 0x4e, 0x6f, 0x54, 0x62, + 0x35, 0x61, 0x33, 0x2f, 0x55, 0x73, 0x44, 0x67, 0x2b, 0x0a, 0x77, 0x52, + 0x76, 0x44, 0x6a, 0x44, 0x50, 0x5a, 0x32, 0x43, 0x38, 0x59, 0x2f, 0x69, + 0x67, 0x50, 0x73, 0x36, 0x65, 0x44, 0x31, 0x73, 0x4e, 0x75, 0x52, 0x4d, + 0x42, 0x68, 0x4e, 0x5a, 0x59, 0x57, 0x2f, 0x6c, 0x6d, 0x63, 0x69, 0x33, + 0x5a, 0x74, 0x31, 0x2f, 0x47, 0x69, 0x53, 0x77, 0x30, 0x72, 0x2f, 0x77, + 0x74, 0x79, 0x32, 0x70, 0x35, 0x67, 0x30, 0x49, 0x36, 0x51, 0x4e, 0x63, + 0x5a, 0x34, 0x0a, 0x56, 0x59, 0x63, 0x67, 0x6f, 0x63, 0x2f, 0x6c, 0x62, + 0x51, 0x72, 0x49, 0x53, 0x58, 0x77, 0x78, 0x6d, 0x44, 0x4e, 0x73, 0x49, + 0x75, 0x6d, 0x48, 0x30, 0x44, 0x4a, 0x61, 0x6f, 0x72, 0x6f, 0x54, 0x67, + 0x68, 0x48, 0x74, 0x4f, 0x52, 0x65, 0x64, 0x6d, 0x54, 0x70, 0x79, 0x6f, + 0x65, 0x62, 0x36, 0x70, 0x4e, 0x6e, 0x56, 0x46, 0x7a, 0x46, 0x31, 0x72, + 0x6f, 0x56, 0x39, 0x49, 0x71, 0x34, 0x2f, 0x0a, 0x41, 0x55, 0x61, 0x47, + 0x39, 0x69, 0x68, 0x35, 0x79, 0x4c, 0x48, 0x61, 0x35, 0x46, 0x63, 0x58, + 0x78, 0x48, 0x34, 0x63, 0x44, 0x72, 0x43, 0x30, 0x6b, 0x71, 0x5a, 0x57, + 0x73, 0x37, 0x32, 0x79, 0x6c, 0x2b, 0x32, 0x71, 0x70, 0x2f, 0x43, 0x33, + 0x78, 0x61, 0x67, 0x2f, 0x6c, 0x52, 0x62, 0x51, 0x2f, 0x36, 0x47, 0x57, + 0x36, 0x77, 0x68, 0x66, 0x47, 0x48, 0x64, 0x50, 0x41, 0x67, 0x4d, 0x42, + 0x0a, 0x41, 0x41, 0x47, 0x6a, 0x59, 0x7a, 0x42, 0x68, 0x4d, 0x41, 0x34, + 0x47, 0x41, 0x31, 0x55, 0x64, 0x44, 0x77, 0x45, 0x42, 0x2f, 0x77, 0x51, + 0x45, 0x41, 0x77, 0x49, 0x42, 0x68, 0x6a, 0x41, 0x50, 0x42, 0x67, 0x4e, + 0x56, 0x48, 0x52, 0x4d, 0x42, 0x41, 0x66, 0x38, 0x45, 0x42, 0x54, 0x41, + 0x44, 0x41, 0x51, 0x48, 0x2f, 0x4d, 0x42, 0x30, 0x47, 0x41, 0x31, 0x55, + 0x64, 0x44, 0x67, 0x51, 0x57, 0x0a, 0x42, 0x42, 0x52, 0x46, 0x36, 0x36, + 0x4b, 0x76, 0x39, 0x4a, 0x4c, 0x4c, 0x67, 0x6a, 0x45, 0x74, 0x55, 0x59, + 0x75, 0x6e, 0x70, 0x79, 0x47, 0x64, 0x38, 0x32, 0x33, 0x49, 0x44, 0x7a, + 0x41, 0x66, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x53, 0x4d, 0x45, 0x47, 0x44, + 0x41, 0x57, 0x67, 0x42, 0x52, 0x46, 0x36, 0x36, 0x4b, 0x76, 0x39, 0x4a, + 0x4c, 0x4c, 0x67, 0x6a, 0x45, 0x74, 0x55, 0x59, 0x75, 0x6e, 0x0a, 0x70, + 0x79, 0x47, 0x64, 0x38, 0x32, 0x33, 0x49, 0x44, 0x7a, 0x41, 0x4e, 0x42, + 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, + 0x51, 0x55, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x51, 0x45, 0x41, 0x6f, + 0x67, 0x36, 0x38, 0x33, 0x2b, 0x4c, 0x74, 0x38, 0x4f, 0x4e, 0x79, 0x63, + 0x33, 0x70, 0x6b, 0x6c, 0x4c, 0x2f, 0x33, 0x63, 0x6d, 0x62, 0x59, 0x4d, + 0x75, 0x52, 0x43, 0x0a, 0x64, 0x57, 0x4b, 0x75, 0x68, 0x2b, 0x76, 0x79, + 0x31, 0x64, 0x6e, 0x65, 0x56, 0x72, 0x4f, 0x66, 0x7a, 0x4d, 0x34, 0x55, + 0x4b, 0x4c, 0x6b, 0x4e, 0x6c, 0x32, 0x42, 0x63, 0x45, 0x6b, 0x78, 0x59, + 0x35, 0x4e, 0x4d, 0x39, 0x67, 0x30, 0x6c, 0x46, 0x57, 0x4a, 0x63, 0x31, + 0x61, 0x52, 0x71, 0x6f, 0x52, 0x2b, 0x70, 0x57, 0x78, 0x6e, 0x6d, 0x72, + 0x45, 0x74, 0x68, 0x6e, 0x67, 0x59, 0x54, 0x66, 0x0a, 0x66, 0x77, 0x6b, + 0x38, 0x6c, 0x4f, 0x61, 0x34, 0x4a, 0x69, 0x77, 0x67, 0x76, 0x54, 0x32, + 0x7a, 0x4b, 0x49, 0x6e, 0x33, 0x58, 0x2f, 0x38, 0x69, 0x34, 0x70, 0x65, + 0x45, 0x48, 0x2b, 0x6c, 0x6c, 0x37, 0x34, 0x66, 0x67, 0x33, 0x38, 0x46, + 0x6e, 0x53, 0x62, 0x4e, 0x64, 0x36, 0x37, 0x49, 0x4a, 0x4b, 0x75, 0x73, + 0x6d, 0x37, 0x58, 0x69, 0x2b, 0x66, 0x54, 0x38, 0x72, 0x38, 0x37, 0x63, + 0x6d, 0x0a, 0x4e, 0x57, 0x31, 0x66, 0x69, 0x51, 0x47, 0x32, 0x53, 0x56, + 0x75, 0x66, 0x41, 0x51, 0x57, 0x62, 0x71, 0x7a, 0x30, 0x6c, 0x77, 0x63, + 0x79, 0x32, 0x66, 0x38, 0x4c, 0x78, 0x62, 0x34, 0x62, 0x47, 0x2b, 0x6d, + 0x52, 0x6f, 0x36, 0x34, 0x45, 0x74, 0x6c, 0x4f, 0x74, 0x43, 0x74, 0x2f, + 0x71, 0x4d, 0x48, 0x74, 0x31, 0x69, 0x38, 0x62, 0x35, 0x51, 0x5a, 0x37, + 0x64, 0x73, 0x76, 0x66, 0x50, 0x78, 0x0a, 0x48, 0x32, 0x73, 0x4d, 0x4e, + 0x67, 0x63, 0x57, 0x66, 0x7a, 0x64, 0x38, 0x71, 0x56, 0x74, 0x74, 0x65, + 0x76, 0x45, 0x53, 0x52, 0x6d, 0x43, 0x44, 0x31, 0x79, 0x63, 0x45, 0x76, + 0x6b, 0x76, 0x4f, 0x6c, 0x37, 0x37, 0x44, 0x5a, 0x79, 0x70, 0x6f, 0x45, + 0x64, 0x2b, 0x41, 0x35, 0x77, 0x77, 0x7a, 0x5a, 0x72, 0x38, 0x54, 0x44, + 0x52, 0x52, 0x75, 0x38, 0x33, 0x38, 0x66, 0x59, 0x78, 0x41, 0x65, 0x0a, + 0x2b, 0x6f, 0x30, 0x62, 0x4a, 0x57, 0x31, 0x73, 0x6a, 0x36, 0x57, 0x33, + 0x59, 0x51, 0x47, 0x78, 0x30, 0x71, 0x4d, 0x6d, 0x6f, 0x52, 0x42, 0x78, + 0x6e, 0x61, 0x33, 0x69, 0x77, 0x2f, 0x6e, 0x44, 0x6d, 0x56, 0x47, 0x33, + 0x4b, 0x77, 0x63, 0x49, 0x7a, 0x69, 0x37, 0x6d, 0x55, 0x4c, 0x4b, 0x6e, + 0x2b, 0x67, 0x70, 0x46, 0x4c, 0x36, 0x4c, 0x77, 0x38, 0x67, 0x3d, 0x3d, + 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, + 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, + 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, + 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, + 0x74, 0x20, 0x43, 0x41, 0x20, 0x4f, 0x3d, 0x44, 0x69, 0x67, 0x69, 0x43, + 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x20, 0x4f, 0x55, 0x3d, 0x77, + 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, + 0x63, 0x6f, 0x6d, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, + 0x72, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, + 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x4f, 0x3d, 0x44, 0x69, 0x67, 0x69, + 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x20, 0x4f, 0x55, 0x3d, + 0x77, 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, + 0x2e, 0x63, 0x6f, 0x6d, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, + 0x3a, 0x20, 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, + 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, + 0x43, 0x41, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, + 0x3a, 0x20, 0x31, 0x30, 0x39, 0x34, 0x34, 0x37, 0x31, 0x39, 0x35, 0x39, + 0x38, 0x39, 0x35, 0x32, 0x30, 0x34, 0x30, 0x33, 0x37, 0x34, 0x39, 0x35, + 0x31, 0x38, 0x33, 0x32, 0x39, 0x36, 0x33, 0x37, 0x39, 0x34, 0x34, 0x35, + 0x34, 0x33, 0x34, 0x36, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x37, 0x39, 0x3a, 0x65, 0x34, 0x3a, 0x61, 0x39, 0x3a, 0x38, 0x34, 0x3a, + 0x30, 0x64, 0x3a, 0x37, 0x64, 0x3a, 0x33, 0x61, 0x3a, 0x39, 0x36, 0x3a, + 0x64, 0x37, 0x3a, 0x63, 0x30, 0x3a, 0x34, 0x66, 0x3a, 0x65, 0x32, 0x3a, + 0x34, 0x33, 0x3a, 0x34, 0x63, 0x3a, 0x38, 0x39, 0x3a, 0x32, 0x65, 0x0a, + 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x61, 0x38, 0x3a, 0x39, + 0x38, 0x3a, 0x35, 0x64, 0x3a, 0x33, 0x61, 0x3a, 0x36, 0x35, 0x3a, 0x65, + 0x35, 0x3a, 0x65, 0x35, 0x3a, 0x63, 0x34, 0x3a, 0x62, 0x32, 0x3a, 0x64, + 0x37, 0x3a, 0x64, 0x36, 0x3a, 0x36, 0x64, 0x3a, 0x34, 0x30, 0x3a, 0x63, + 0x36, 0x3a, 0x64, 0x64, 0x3a, 0x32, 0x66, 0x3a, 0x62, 0x31, 0x3a, 0x39, + 0x63, 0x3a, 0x35, 0x34, 0x3a, 0x33, 0x36, 0x0a, 0x23, 0x20, 0x53, 0x48, + 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, + 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x34, 0x33, 0x3a, 0x34, 0x38, 0x3a, + 0x61, 0x30, 0x3a, 0x65, 0x39, 0x3a, 0x34, 0x34, 0x3a, 0x34, 0x63, 0x3a, + 0x37, 0x38, 0x3a, 0x63, 0x62, 0x3a, 0x32, 0x36, 0x3a, 0x35, 0x65, 0x3a, + 0x30, 0x35, 0x3a, 0x38, 0x64, 0x3a, 0x35, 0x65, 0x3a, 0x38, 0x39, 0x3a, + 0x34, 0x34, 0x3a, 0x62, 0x34, 0x3a, 0x64, 0x38, 0x3a, 0x34, 0x66, 0x3a, + 0x39, 0x36, 0x3a, 0x36, 0x32, 0x3a, 0x62, 0x64, 0x3a, 0x32, 0x36, 0x3a, + 0x64, 0x62, 0x3a, 0x32, 0x35, 0x3a, 0x37, 0x66, 0x3a, 0x38, 0x39, 0x3a, + 0x33, 0x34, 0x3a, 0x61, 0x34, 0x3a, 0x34, 0x33, 0x3a, 0x63, 0x37, 0x3a, + 0x30, 0x31, 0x3a, 0x36, 0x31, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, + 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, + 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, + 0x49, 0x44, 0x72, 0x7a, 0x43, 0x43, 0x41, 0x70, 0x65, 0x67, 0x41, 0x77, + 0x49, 0x42, 0x41, 0x67, 0x49, 0x51, 0x43, 0x44, 0x76, 0x67, 0x56, 0x70, + 0x42, 0x43, 0x52, 0x72, 0x47, 0x68, 0x64, 0x57, 0x72, 0x4a, 0x57, 0x5a, + 0x48, 0x48, 0x53, 0x6a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, + 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, + 0x42, 0x68, 0x0a, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, + 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, 0x56, 0x4d, + 0x42, 0x4d, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4d, 0x52, + 0x47, 0x6c, 0x6e, 0x61, 0x55, 0x4e, 0x6c, 0x63, 0x6e, 0x51, 0x67, 0x53, + 0x57, 0x35, 0x6a, 0x4d, 0x52, 0x6b, 0x77, 0x46, 0x77, 0x59, 0x44, 0x56, + 0x51, 0x51, 0x4c, 0x45, 0x78, 0x42, 0x33, 0x0a, 0x64, 0x33, 0x63, 0x75, + 0x5a, 0x47, 0x6c, 0x6e, 0x61, 0x57, 0x4e, 0x6c, 0x63, 0x6e, 0x51, 0x75, + 0x59, 0x32, 0x39, 0x74, 0x4d, 0x53, 0x41, 0x77, 0x48, 0x67, 0x59, 0x44, + 0x56, 0x51, 0x51, 0x44, 0x45, 0x78, 0x64, 0x45, 0x61, 0x57, 0x64, 0x70, + 0x51, 0x32, 0x56, 0x79, 0x64, 0x43, 0x42, 0x48, 0x62, 0x47, 0x39, 0x69, + 0x59, 0x57, 0x77, 0x67, 0x55, 0x6d, 0x39, 0x76, 0x64, 0x43, 0x42, 0x44, + 0x0a, 0x51, 0x54, 0x41, 0x65, 0x46, 0x77, 0x30, 0x77, 0x4e, 0x6a, 0x45, + 0x78, 0x4d, 0x54, 0x41, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x44, 0x42, + 0x61, 0x46, 0x77, 0x30, 0x7a, 0x4d, 0x54, 0x45, 0x78, 0x4d, 0x54, 0x41, + 0x77, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x44, 0x42, 0x61, 0x4d, 0x47, 0x45, + 0x78, 0x43, 0x7a, 0x41, 0x4a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x59, + 0x54, 0x41, 0x6c, 0x56, 0x54, 0x0a, 0x4d, 0x52, 0x55, 0x77, 0x45, 0x77, + 0x59, 0x44, 0x56, 0x51, 0x51, 0x4b, 0x45, 0x77, 0x78, 0x45, 0x61, 0x57, + 0x64, 0x70, 0x51, 0x32, 0x56, 0x79, 0x64, 0x43, 0x42, 0x4a, 0x62, 0x6d, + 0x4d, 0x78, 0x47, 0x54, 0x41, 0x58, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, + 0x73, 0x54, 0x45, 0x48, 0x64, 0x33, 0x64, 0x79, 0x35, 0x6b, 0x61, 0x57, + 0x64, 0x70, 0x59, 0x32, 0x56, 0x79, 0x64, 0x43, 0x35, 0x6a, 0x0a, 0x62, + 0x32, 0x30, 0x78, 0x49, 0x44, 0x41, 0x65, 0x42, 0x67, 0x4e, 0x56, 0x42, + 0x41, 0x4d, 0x54, 0x46, 0x30, 0x52, 0x70, 0x5a, 0x32, 0x6c, 0x44, 0x5a, + 0x58, 0x4a, 0x30, 0x49, 0x45, 0x64, 0x73, 0x62, 0x32, 0x4a, 0x68, 0x62, + 0x43, 0x42, 0x53, 0x62, 0x32, 0x39, 0x30, 0x49, 0x45, 0x4e, 0x42, 0x4d, + 0x49, 0x49, 0x42, 0x49, 0x6a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, + 0x6b, 0x69, 0x47, 0x0a, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x45, 0x46, + 0x41, 0x41, 0x4f, 0x43, 0x41, 0x51, 0x38, 0x41, 0x4d, 0x49, 0x49, 0x42, + 0x43, 0x67, 0x4b, 0x43, 0x41, 0x51, 0x45, 0x41, 0x34, 0x6a, 0x76, 0x68, + 0x45, 0x58, 0x4c, 0x65, 0x71, 0x4b, 0x54, 0x54, 0x6f, 0x31, 0x65, 0x71, + 0x55, 0x4b, 0x4b, 0x50, 0x43, 0x33, 0x65, 0x51, 0x79, 0x61, 0x4b, 0x6c, + 0x37, 0x68, 0x4c, 0x4f, 0x6c, 0x6c, 0x73, 0x42, 0x0a, 0x43, 0x53, 0x44, + 0x4d, 0x41, 0x5a, 0x4f, 0x6e, 0x54, 0x6a, 0x43, 0x33, 0x55, 0x2f, 0x64, + 0x44, 0x78, 0x47, 0x6b, 0x41, 0x56, 0x35, 0x33, 0x69, 0x6a, 0x53, 0x4c, + 0x64, 0x68, 0x77, 0x5a, 0x41, 0x41, 0x49, 0x45, 0x4a, 0x7a, 0x73, 0x34, + 0x62, 0x67, 0x37, 0x2f, 0x66, 0x7a, 0x54, 0x74, 0x78, 0x52, 0x75, 0x4c, + 0x57, 0x5a, 0x73, 0x63, 0x46, 0x73, 0x33, 0x59, 0x6e, 0x46, 0x6f, 0x39, + 0x37, 0x0a, 0x6e, 0x68, 0x36, 0x56, 0x66, 0x65, 0x36, 0x33, 0x53, 0x4b, + 0x4d, 0x49, 0x32, 0x74, 0x61, 0x76, 0x65, 0x67, 0x77, 0x35, 0x42, 0x6d, + 0x56, 0x2f, 0x53, 0x6c, 0x30, 0x66, 0x76, 0x42, 0x66, 0x34, 0x71, 0x37, + 0x37, 0x75, 0x4b, 0x4e, 0x64, 0x30, 0x66, 0x33, 0x70, 0x34, 0x6d, 0x56, + 0x6d, 0x46, 0x61, 0x47, 0x35, 0x63, 0x49, 0x7a, 0x4a, 0x4c, 0x76, 0x30, + 0x37, 0x41, 0x36, 0x46, 0x70, 0x74, 0x0a, 0x34, 0x33, 0x43, 0x2f, 0x64, + 0x78, 0x43, 0x2f, 0x2f, 0x41, 0x48, 0x32, 0x68, 0x64, 0x6d, 0x6f, 0x52, + 0x42, 0x42, 0x59, 0x4d, 0x71, 0x6c, 0x31, 0x47, 0x4e, 0x58, 0x52, 0x6f, + 0x72, 0x35, 0x48, 0x34, 0x69, 0x64, 0x71, 0x39, 0x4a, 0x6f, 0x7a, 0x2b, + 0x45, 0x6b, 0x49, 0x59, 0x49, 0x76, 0x55, 0x58, 0x37, 0x51, 0x36, 0x68, + 0x4c, 0x2b, 0x68, 0x71, 0x6b, 0x70, 0x4d, 0x66, 0x54, 0x37, 0x50, 0x0a, + 0x54, 0x31, 0x39, 0x73, 0x64, 0x6c, 0x36, 0x67, 0x53, 0x7a, 0x65, 0x52, + 0x6e, 0x74, 0x77, 0x69, 0x35, 0x6d, 0x33, 0x4f, 0x46, 0x42, 0x71, 0x4f, + 0x61, 0x73, 0x76, 0x2b, 0x7a, 0x62, 0x4d, 0x55, 0x5a, 0x42, 0x66, 0x48, + 0x57, 0x79, 0x6d, 0x65, 0x4d, 0x72, 0x2f, 0x79, 0x37, 0x76, 0x72, 0x54, + 0x43, 0x30, 0x4c, 0x55, 0x71, 0x37, 0x64, 0x42, 0x4d, 0x74, 0x6f, 0x4d, + 0x31, 0x4f, 0x2f, 0x34, 0x0a, 0x67, 0x64, 0x57, 0x37, 0x6a, 0x56, 0x67, + 0x2f, 0x74, 0x52, 0x76, 0x6f, 0x53, 0x53, 0x69, 0x69, 0x63, 0x4e, 0x6f, + 0x78, 0x42, 0x4e, 0x33, 0x33, 0x73, 0x68, 0x62, 0x79, 0x54, 0x41, 0x70, + 0x4f, 0x42, 0x36, 0x6a, 0x74, 0x53, 0x6a, 0x31, 0x65, 0x74, 0x58, 0x2b, + 0x6a, 0x6b, 0x4d, 0x4f, 0x76, 0x4a, 0x77, 0x49, 0x44, 0x41, 0x51, 0x41, + 0x42, 0x6f, 0x32, 0x4d, 0x77, 0x59, 0x54, 0x41, 0x4f, 0x0a, 0x42, 0x67, + 0x4e, 0x56, 0x48, 0x51, 0x38, 0x42, 0x41, 0x66, 0x38, 0x45, 0x42, 0x41, + 0x4d, 0x43, 0x41, 0x59, 0x59, 0x77, 0x44, 0x77, 0x59, 0x44, 0x56, 0x52, + 0x30, 0x54, 0x41, 0x51, 0x48, 0x2f, 0x42, 0x41, 0x55, 0x77, 0x41, 0x77, + 0x45, 0x42, 0x2f, 0x7a, 0x41, 0x64, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, + 0x34, 0x45, 0x46, 0x67, 0x51, 0x55, 0x41, 0x39, 0x35, 0x51, 0x4e, 0x56, + 0x62, 0x52, 0x0a, 0x54, 0x4c, 0x74, 0x6d, 0x38, 0x4b, 0x50, 0x69, 0x47, + 0x78, 0x76, 0x44, 0x6c, 0x37, 0x49, 0x39, 0x30, 0x56, 0x55, 0x77, 0x48, + 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, 0x6a, 0x42, 0x42, 0x67, 0x77, 0x46, + 0x6f, 0x41, 0x55, 0x41, 0x39, 0x35, 0x51, 0x4e, 0x56, 0x62, 0x52, 0x54, + 0x4c, 0x74, 0x6d, 0x38, 0x4b, 0x50, 0x69, 0x47, 0x78, 0x76, 0x44, 0x6c, + 0x37, 0x49, 0x39, 0x30, 0x56, 0x55, 0x77, 0x0a, 0x44, 0x51, 0x59, 0x4a, + 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x46, + 0x42, 0x51, 0x41, 0x44, 0x67, 0x67, 0x45, 0x42, 0x41, 0x4d, 0x75, 0x63, + 0x4e, 0x36, 0x70, 0x49, 0x45, 0x78, 0x49, 0x4b, 0x2b, 0x74, 0x31, 0x45, + 0x6e, 0x45, 0x39, 0x53, 0x73, 0x50, 0x54, 0x66, 0x72, 0x67, 0x54, 0x31, + 0x65, 0x58, 0x6b, 0x49, 0x6f, 0x79, 0x51, 0x59, 0x2f, 0x45, 0x73, 0x72, + 0x0a, 0x68, 0x4d, 0x41, 0x74, 0x75, 0x64, 0x58, 0x48, 0x2f, 0x76, 0x54, + 0x42, 0x48, 0x31, 0x6a, 0x4c, 0x75, 0x47, 0x32, 0x63, 0x65, 0x6e, 0x54, + 0x6e, 0x6d, 0x43, 0x6d, 0x72, 0x45, 0x62, 0x58, 0x6a, 0x63, 0x4b, 0x43, + 0x68, 0x7a, 0x55, 0x79, 0x49, 0x6d, 0x5a, 0x4f, 0x4d, 0x6b, 0x58, 0x44, + 0x69, 0x71, 0x77, 0x38, 0x63, 0x76, 0x70, 0x4f, 0x70, 0x2f, 0x32, 0x50, + 0x56, 0x35, 0x41, 0x64, 0x67, 0x0a, 0x30, 0x36, 0x4f, 0x2f, 0x6e, 0x56, + 0x73, 0x4a, 0x38, 0x64, 0x57, 0x4f, 0x34, 0x31, 0x50, 0x30, 0x6a, 0x6d, + 0x50, 0x36, 0x50, 0x36, 0x66, 0x62, 0x74, 0x47, 0x62, 0x66, 0x59, 0x6d, + 0x62, 0x57, 0x30, 0x57, 0x35, 0x42, 0x6a, 0x66, 0x49, 0x74, 0x74, 0x65, + 0x70, 0x33, 0x53, 0x70, 0x2b, 0x64, 0x57, 0x4f, 0x49, 0x72, 0x57, 0x63, + 0x42, 0x41, 0x49, 0x2b, 0x30, 0x74, 0x4b, 0x49, 0x4a, 0x46, 0x0a, 0x50, + 0x6e, 0x6c, 0x55, 0x6b, 0x69, 0x61, 0x59, 0x34, 0x49, 0x42, 0x49, 0x71, + 0x44, 0x66, 0x76, 0x38, 0x4e, 0x5a, 0x35, 0x59, 0x42, 0x62, 0x65, 0x72, + 0x4f, 0x67, 0x4f, 0x7a, 0x57, 0x36, 0x73, 0x52, 0x42, 0x63, 0x34, 0x4c, + 0x30, 0x6e, 0x61, 0x34, 0x55, 0x55, 0x2b, 0x4b, 0x72, 0x6b, 0x32, 0x55, + 0x38, 0x38, 0x36, 0x55, 0x41, 0x62, 0x33, 0x4c, 0x75, 0x6a, 0x45, 0x56, + 0x30, 0x6c, 0x73, 0x0a, 0x59, 0x53, 0x45, 0x59, 0x31, 0x51, 0x53, 0x74, + 0x65, 0x44, 0x77, 0x73, 0x4f, 0x6f, 0x42, 0x72, 0x70, 0x2b, 0x75, 0x76, + 0x46, 0x52, 0x54, 0x70, 0x32, 0x49, 0x6e, 0x42, 0x75, 0x54, 0x68, 0x73, + 0x34, 0x70, 0x46, 0x73, 0x69, 0x76, 0x39, 0x6b, 0x75, 0x58, 0x63, 0x6c, + 0x56, 0x7a, 0x44, 0x41, 0x47, 0x79, 0x53, 0x6a, 0x34, 0x64, 0x7a, 0x70, + 0x33, 0x30, 0x64, 0x38, 0x74, 0x62, 0x51, 0x6b, 0x0a, 0x43, 0x41, 0x55, + 0x77, 0x37, 0x43, 0x32, 0x39, 0x43, 0x37, 0x39, 0x46, 0x76, 0x31, 0x43, + 0x35, 0x71, 0x66, 0x50, 0x72, 0x6d, 0x41, 0x45, 0x53, 0x72, 0x63, 0x69, + 0x49, 0x78, 0x70, 0x67, 0x30, 0x58, 0x34, 0x30, 0x4b, 0x50, 0x4d, 0x62, + 0x70, 0x31, 0x5a, 0x57, 0x56, 0x62, 0x64, 0x34, 0x3d, 0x0a, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, + 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, + 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, + 0x4e, 0x3d, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, + 0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, + 0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, + 0x20, 0x4f, 0x3d, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, + 0x49, 0x6e, 0x63, 0x20, 0x4f, 0x55, 0x3d, 0x77, 0x77, 0x77, 0x2e, 0x64, + 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, + 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, + 0x4e, 0x3d, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, + 0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, + 0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, + 0x20, 0x4f, 0x3d, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, + 0x49, 0x6e, 0x63, 0x20, 0x4f, 0x55, 0x3d, 0x77, 0x77, 0x77, 0x2e, 0x64, + 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, + 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x44, 0x69, + 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, 0x69, 0x67, 0x68, 0x20, + 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x45, 0x56, + 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x22, 0x0a, 0x23, 0x20, + 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x33, 0x35, 0x35, 0x33, + 0x34, 0x30, 0x30, 0x30, 0x37, 0x36, 0x34, 0x31, 0x30, 0x35, 0x34, 0x37, + 0x39, 0x31, 0x39, 0x37, 0x32, 0x34, 0x37, 0x33, 0x30, 0x37, 0x33, 0x34, + 0x33, 0x37, 0x38, 0x31, 0x30, 0x30, 0x30, 0x38, 0x37, 0x0a, 0x23, 0x20, + 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, + 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x64, 0x34, 0x3a, 0x37, 0x34, 0x3a, 0x64, + 0x65, 0x3a, 0x35, 0x37, 0x3a, 0x35, 0x63, 0x3a, 0x33, 0x39, 0x3a, 0x62, + 0x32, 0x3a, 0x64, 0x33, 0x3a, 0x39, 0x63, 0x3a, 0x38, 0x35, 0x3a, 0x38, + 0x33, 0x3a, 0x63, 0x35, 0x3a, 0x63, 0x30, 0x3a, 0x36, 0x35, 0x3a, 0x34, + 0x39, 0x3a, 0x38, 0x61, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, + 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, + 0x20, 0x35, 0x66, 0x3a, 0x62, 0x37, 0x3a, 0x65, 0x65, 0x3a, 0x30, 0x36, + 0x3a, 0x33, 0x33, 0x3a, 0x65, 0x32, 0x3a, 0x35, 0x39, 0x3a, 0x64, 0x62, + 0x3a, 0x61, 0x64, 0x3a, 0x30, 0x63, 0x3a, 0x34, 0x63, 0x3a, 0x39, 0x61, + 0x3a, 0x65, 0x36, 0x3a, 0x64, 0x33, 0x3a, 0x38, 0x66, 0x3a, 0x31, 0x61, + 0x3a, 0x36, 0x31, 0x3a, 0x63, 0x37, 0x3a, 0x64, 0x63, 0x3a, 0x32, 0x35, + 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, + 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x37, + 0x34, 0x3a, 0x33, 0x31, 0x3a, 0x65, 0x35, 0x3a, 0x66, 0x34, 0x3a, 0x63, + 0x33, 0x3a, 0x63, 0x31, 0x3a, 0x63, 0x65, 0x3a, 0x34, 0x36, 0x3a, 0x39, + 0x30, 0x3a, 0x37, 0x37, 0x3a, 0x34, 0x66, 0x3a, 0x30, 0x62, 0x3a, 0x36, + 0x31, 0x3a, 0x65, 0x30, 0x3a, 0x35, 0x34, 0x3a, 0x34, 0x30, 0x3a, 0x38, + 0x38, 0x3a, 0x33, 0x62, 0x3a, 0x61, 0x39, 0x3a, 0x61, 0x30, 0x3a, 0x31, + 0x65, 0x3a, 0x64, 0x30, 0x3a, 0x30, 0x62, 0x3a, 0x61, 0x36, 0x3a, 0x61, + 0x62, 0x3a, 0x64, 0x37, 0x3a, 0x38, 0x30, 0x3a, 0x36, 0x65, 0x3a, 0x64, + 0x33, 0x3a, 0x62, 0x31, 0x3a, 0x31, 0x38, 0x3a, 0x63, 0x66, 0x0a, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, + 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x44, 0x78, 0x54, 0x43, 0x43, 0x41, + 0x71, 0x32, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x51, 0x41, + 0x71, 0x78, 0x63, 0x4a, 0x6d, 0x6f, 0x4c, 0x51, 0x4a, 0x75, 0x50, 0x43, + 0x33, 0x6e, 0x79, 0x72, 0x6b, 0x59, 0x6c, 0x64, 0x7a, 0x41, 0x4e, 0x42, + 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, + 0x51, 0x55, 0x46, 0x41, 0x44, 0x42, 0x73, 0x0a, 0x4d, 0x51, 0x73, 0x77, + 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, + 0x55, 0x7a, 0x45, 0x56, 0x4d, 0x42, 0x4d, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x43, 0x68, 0x4d, 0x4d, 0x52, 0x47, 0x6c, 0x6e, 0x61, 0x55, 0x4e, 0x6c, + 0x63, 0x6e, 0x51, 0x67, 0x53, 0x57, 0x35, 0x6a, 0x4d, 0x52, 0x6b, 0x77, + 0x46, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, 0x78, 0x42, 0x33, + 0x0a, 0x64, 0x33, 0x63, 0x75, 0x5a, 0x47, 0x6c, 0x6e, 0x61, 0x57, 0x4e, + 0x6c, 0x63, 0x6e, 0x51, 0x75, 0x59, 0x32, 0x39, 0x74, 0x4d, 0x53, 0x73, + 0x77, 0x4b, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, 0x79, 0x4a, + 0x45, 0x61, 0x57, 0x64, 0x70, 0x51, 0x32, 0x56, 0x79, 0x64, 0x43, 0x42, + 0x49, 0x61, 0x57, 0x64, 0x6f, 0x49, 0x45, 0x46, 0x7a, 0x63, 0x33, 0x56, + 0x79, 0x59, 0x57, 0x35, 0x6a, 0x0a, 0x5a, 0x53, 0x42, 0x46, 0x56, 0x69, + 0x42, 0x53, 0x62, 0x32, 0x39, 0x30, 0x49, 0x45, 0x4e, 0x42, 0x4d, 0x42, + 0x34, 0x58, 0x44, 0x54, 0x41, 0x32, 0x4d, 0x54, 0x45, 0x78, 0x4d, 0x44, + 0x41, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x46, 0x6f, 0x58, 0x44, 0x54, + 0x4d, 0x78, 0x4d, 0x54, 0x45, 0x78, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x44, + 0x41, 0x77, 0x4d, 0x46, 0x6f, 0x77, 0x62, 0x44, 0x45, 0x4c, 0x0a, 0x4d, + 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x56, + 0x56, 0x4d, 0x78, 0x46, 0x54, 0x41, 0x54, 0x42, 0x67, 0x4e, 0x56, 0x42, + 0x41, 0x6f, 0x54, 0x44, 0x45, 0x52, 0x70, 0x5a, 0x32, 0x6c, 0x44, 0x5a, + 0x58, 0x4a, 0x30, 0x49, 0x45, 0x6c, 0x75, 0x59, 0x7a, 0x45, 0x5a, 0x4d, + 0x42, 0x63, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x78, 0x4d, 0x51, 0x64, + 0x33, 0x64, 0x33, 0x0a, 0x4c, 0x6d, 0x52, 0x70, 0x5a, 0x32, 0x6c, 0x6a, + 0x5a, 0x58, 0x4a, 0x30, 0x4c, 0x6d, 0x4e, 0x76, 0x62, 0x54, 0x45, 0x72, + 0x4d, 0x43, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x69, + 0x52, 0x47, 0x6c, 0x6e, 0x61, 0x55, 0x4e, 0x6c, 0x63, 0x6e, 0x51, 0x67, + 0x53, 0x47, 0x6c, 0x6e, 0x61, 0x43, 0x42, 0x42, 0x63, 0x33, 0x4e, 0x31, + 0x63, 0x6d, 0x46, 0x75, 0x59, 0x32, 0x55, 0x67, 0x0a, 0x52, 0x56, 0x59, + 0x67, 0x55, 0x6d, 0x39, 0x76, 0x64, 0x43, 0x42, 0x44, 0x51, 0x54, 0x43, + 0x43, 0x41, 0x53, 0x49, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, + 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x42, 0x42, 0x51, 0x41, + 0x44, 0x67, 0x67, 0x45, 0x50, 0x41, 0x44, 0x43, 0x43, 0x41, 0x51, 0x6f, + 0x43, 0x67, 0x67, 0x45, 0x42, 0x41, 0x4d, 0x62, 0x4d, 0x35, 0x58, 0x50, + 0x6d, 0x0a, 0x2b, 0x39, 0x53, 0x37, 0x35, 0x53, 0x30, 0x74, 0x4d, 0x71, + 0x62, 0x66, 0x35, 0x59, 0x45, 0x2f, 0x79, 0x63, 0x30, 0x6c, 0x53, 0x62, + 0x5a, 0x78, 0x4b, 0x73, 0x50, 0x56, 0x6c, 0x44, 0x52, 0x6e, 0x6f, 0x67, + 0x6f, 0x63, 0x73, 0x46, 0x39, 0x70, 0x70, 0x6b, 0x43, 0x78, 0x78, 0x4c, + 0x65, 0x79, 0x6a, 0x39, 0x43, 0x59, 0x70, 0x4b, 0x6c, 0x42, 0x57, 0x54, + 0x72, 0x54, 0x33, 0x4a, 0x54, 0x57, 0x0a, 0x50, 0x4e, 0x74, 0x30, 0x4f, + 0x4b, 0x52, 0x4b, 0x7a, 0x45, 0x30, 0x6c, 0x67, 0x76, 0x64, 0x4b, 0x70, + 0x56, 0x4d, 0x53, 0x4f, 0x4f, 0x37, 0x7a, 0x53, 0x57, 0x31, 0x78, 0x6b, + 0x58, 0x35, 0x6a, 0x74, 0x71, 0x75, 0x6d, 0x58, 0x38, 0x4f, 0x6b, 0x68, + 0x50, 0x68, 0x50, 0x59, 0x6c, 0x47, 0x2b, 0x2b, 0x4d, 0x58, 0x73, 0x32, + 0x7a, 0x69, 0x53, 0x34, 0x77, 0x62, 0x6c, 0x43, 0x4a, 0x45, 0x4d, 0x0a, + 0x78, 0x43, 0x68, 0x42, 0x56, 0x66, 0x76, 0x4c, 0x57, 0x6f, 0x6b, 0x56, + 0x66, 0x6e, 0x48, 0x6f, 0x4e, 0x62, 0x39, 0x4e, 0x63, 0x67, 0x6b, 0x39, + 0x76, 0x6a, 0x6f, 0x34, 0x55, 0x46, 0x74, 0x33, 0x4d, 0x52, 0x75, 0x4e, + 0x73, 0x38, 0x63, 0x6b, 0x52, 0x5a, 0x71, 0x6e, 0x72, 0x47, 0x30, 0x41, + 0x46, 0x46, 0x6f, 0x45, 0x74, 0x37, 0x6f, 0x54, 0x36, 0x31, 0x45, 0x4b, + 0x6d, 0x45, 0x46, 0x42, 0x0a, 0x49, 0x6b, 0x35, 0x6c, 0x59, 0x59, 0x65, + 0x42, 0x51, 0x56, 0x43, 0x6d, 0x65, 0x56, 0x79, 0x4a, 0x33, 0x68, 0x6c, + 0x4b, 0x56, 0x39, 0x55, 0x75, 0x35, 0x6c, 0x30, 0x63, 0x55, 0x79, 0x78, + 0x2b, 0x6d, 0x4d, 0x30, 0x61, 0x42, 0x68, 0x61, 0x6b, 0x61, 0x48, 0x50, + 0x51, 0x4e, 0x41, 0x51, 0x54, 0x58, 0x4b, 0x46, 0x78, 0x30, 0x31, 0x70, + 0x38, 0x56, 0x64, 0x74, 0x65, 0x5a, 0x4f, 0x45, 0x33, 0x0a, 0x68, 0x7a, + 0x42, 0x57, 0x42, 0x4f, 0x55, 0x52, 0x74, 0x43, 0x6d, 0x41, 0x45, 0x76, + 0x46, 0x35, 0x4f, 0x59, 0x69, 0x69, 0x41, 0x68, 0x46, 0x38, 0x4a, 0x32, + 0x61, 0x33, 0x69, 0x4c, 0x64, 0x34, 0x38, 0x73, 0x6f, 0x4b, 0x71, 0x44, + 0x69, 0x72, 0x43, 0x6d, 0x54, 0x43, 0x76, 0x32, 0x5a, 0x64, 0x6c, 0x59, + 0x54, 0x42, 0x6f, 0x53, 0x55, 0x65, 0x68, 0x31, 0x30, 0x61, 0x55, 0x41, + 0x73, 0x67, 0x0a, 0x45, 0x73, 0x78, 0x42, 0x75, 0x32, 0x34, 0x4c, 0x55, + 0x54, 0x69, 0x34, 0x53, 0x38, 0x73, 0x43, 0x41, 0x77, 0x45, 0x41, 0x41, + 0x61, 0x4e, 0x6a, 0x4d, 0x47, 0x45, 0x77, 0x44, 0x67, 0x59, 0x44, 0x56, + 0x52, 0x30, 0x50, 0x41, 0x51, 0x48, 0x2f, 0x42, 0x41, 0x51, 0x44, 0x41, + 0x67, 0x47, 0x47, 0x4d, 0x41, 0x38, 0x47, 0x41, 0x31, 0x55, 0x64, 0x45, + 0x77, 0x45, 0x42, 0x2f, 0x77, 0x51, 0x46, 0x0a, 0x4d, 0x41, 0x4d, 0x42, + 0x41, 0x66, 0x38, 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, 0x52, 0x30, 0x4f, + 0x42, 0x42, 0x59, 0x45, 0x46, 0x4c, 0x45, 0x2b, 0x77, 0x32, 0x6b, 0x44, + 0x2b, 0x4c, 0x39, 0x48, 0x41, 0x64, 0x53, 0x59, 0x4a, 0x68, 0x6f, 0x49, + 0x41, 0x75, 0x39, 0x6a, 0x5a, 0x43, 0x76, 0x44, 0x4d, 0x42, 0x38, 0x47, + 0x41, 0x31, 0x55, 0x64, 0x49, 0x77, 0x51, 0x59, 0x4d, 0x42, 0x61, 0x41, + 0x0a, 0x46, 0x4c, 0x45, 0x2b, 0x77, 0x32, 0x6b, 0x44, 0x2b, 0x4c, 0x39, + 0x48, 0x41, 0x64, 0x53, 0x59, 0x4a, 0x68, 0x6f, 0x49, 0x41, 0x75, 0x39, + 0x6a, 0x5a, 0x43, 0x76, 0x44, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, + 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x42, 0x51, 0x55, + 0x41, 0x41, 0x34, 0x49, 0x42, 0x41, 0x51, 0x41, 0x63, 0x47, 0x67, 0x61, + 0x58, 0x33, 0x4e, 0x65, 0x63, 0x0a, 0x6e, 0x7a, 0x79, 0x49, 0x5a, 0x67, + 0x59, 0x49, 0x56, 0x79, 0x48, 0x62, 0x49, 0x55, 0x66, 0x34, 0x4b, 0x6d, + 0x65, 0x71, 0x76, 0x78, 0x67, 0x79, 0x64, 0x6b, 0x41, 0x51, 0x56, 0x38, + 0x47, 0x4b, 0x38, 0x33, 0x72, 0x5a, 0x45, 0x57, 0x57, 0x4f, 0x4e, 0x66, + 0x71, 0x65, 0x2f, 0x45, 0x57, 0x31, 0x6e, 0x74, 0x6c, 0x4d, 0x4d, 0x55, + 0x75, 0x34, 0x6b, 0x65, 0x68, 0x44, 0x4c, 0x49, 0x36, 0x7a, 0x0a, 0x65, + 0x4d, 0x37, 0x62, 0x34, 0x31, 0x4e, 0x35, 0x63, 0x64, 0x62, 0x6c, 0x49, + 0x5a, 0x51, 0x42, 0x32, 0x6c, 0x57, 0x48, 0x6d, 0x69, 0x52, 0x6b, 0x39, + 0x6f, 0x70, 0x6d, 0x7a, 0x4e, 0x36, 0x63, 0x4e, 0x38, 0x32, 0x6f, 0x4e, + 0x4c, 0x46, 0x70, 0x6d, 0x79, 0x50, 0x49, 0x6e, 0x6e, 0x67, 0x69, 0x4b, + 0x33, 0x42, 0x44, 0x34, 0x31, 0x56, 0x48, 0x4d, 0x57, 0x45, 0x5a, 0x37, + 0x31, 0x6a, 0x46, 0x0a, 0x68, 0x53, 0x39, 0x4f, 0x4d, 0x50, 0x61, 0x67, + 0x4d, 0x52, 0x59, 0x6a, 0x79, 0x4f, 0x66, 0x69, 0x5a, 0x52, 0x59, 0x7a, + 0x79, 0x37, 0x38, 0x61, 0x47, 0x36, 0x41, 0x39, 0x2b, 0x4d, 0x70, 0x65, + 0x69, 0x7a, 0x47, 0x4c, 0x59, 0x41, 0x69, 0x4a, 0x4c, 0x51, 0x77, 0x47, + 0x58, 0x46, 0x4b, 0x33, 0x78, 0x50, 0x6b, 0x4b, 0x6d, 0x4e, 0x45, 0x56, + 0x58, 0x35, 0x38, 0x53, 0x76, 0x6e, 0x77, 0x32, 0x0a, 0x59, 0x7a, 0x69, + 0x39, 0x52, 0x4b, 0x52, 0x2f, 0x35, 0x43, 0x59, 0x72, 0x43, 0x73, 0x53, + 0x58, 0x61, 0x51, 0x33, 0x70, 0x6a, 0x4f, 0x4c, 0x41, 0x45, 0x46, 0x65, + 0x34, 0x79, 0x48, 0x59, 0x53, 0x6b, 0x56, 0x58, 0x79, 0x53, 0x47, 0x6e, + 0x59, 0x76, 0x43, 0x6f, 0x43, 0x57, 0x77, 0x39, 0x45, 0x31, 0x43, 0x41, + 0x78, 0x32, 0x2f, 0x53, 0x36, 0x63, 0x43, 0x5a, 0x64, 0x6b, 0x47, 0x43, + 0x65, 0x0a, 0x76, 0x45, 0x73, 0x58, 0x43, 0x53, 0x2b, 0x30, 0x79, 0x78, + 0x35, 0x44, 0x61, 0x4d, 0x6b, 0x48, 0x4a, 0x38, 0x48, 0x53, 0x58, 0x50, + 0x66, 0x71, 0x49, 0x62, 0x6c, 0x6f, 0x45, 0x70, 0x77, 0x38, 0x6e, 0x4c, + 0x2b, 0x65, 0x2f, 0x49, 0x42, 0x63, 0x6d, 0x32, 0x50, 0x4e, 0x37, 0x45, + 0x65, 0x71, 0x4a, 0x53, 0x64, 0x6e, 0x6f, 0x44, 0x66, 0x7a, 0x41, 0x49, + 0x4a, 0x39, 0x56, 0x4e, 0x65, 0x70, 0x0a, 0x2b, 0x4f, 0x6b, 0x75, 0x45, + 0x36, 0x4e, 0x33, 0x36, 0x42, 0x39, 0x4b, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, + 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, + 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, + 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x50, 0x72, 0x69, + 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x74, 0x79, 0x20, 0x4f, 0x3d, 0x47, 0x65, 0x6f, 0x54, 0x72, + 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x53, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x47, + 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x50, 0x72, 0x69, 0x6d, + 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x74, 0x79, 0x20, 0x4f, 0x3d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, + 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x4c, 0x61, + 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, + 0x73, 0x74, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x0a, + 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x33, 0x32, + 0x37, 0x39, 0x38, 0x32, 0x32, 0x36, 0x35, 0x35, 0x31, 0x32, 0x35, 0x36, + 0x39, 0x36, 0x33, 0x33, 0x32, 0x34, 0x33, 0x31, 0x33, 0x38, 0x30, 0x36, + 0x34, 0x33, 0x36, 0x39, 0x38, 0x31, 0x39, 0x38, 0x32, 0x33, 0x36, 0x39, + 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x30, 0x32, 0x3a, 0x32, + 0x36, 0x3a, 0x63, 0x33, 0x3a, 0x30, 0x31, 0x3a, 0x35, 0x65, 0x3a, 0x30, + 0x38, 0x3a, 0x33, 0x30, 0x3a, 0x33, 0x37, 0x3a, 0x34, 0x33, 0x3a, 0x61, + 0x39, 0x3a, 0x64, 0x30, 0x3a, 0x37, 0x64, 0x3a, 0x63, 0x66, 0x3a, 0x33, + 0x37, 0x3a, 0x65, 0x36, 0x3a, 0x62, 0x66, 0x0a, 0x23, 0x20, 0x53, 0x48, + 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x3a, 0x20, 0x33, 0x32, 0x3a, 0x33, 0x63, 0x3a, 0x31, 0x31, + 0x3a, 0x38, 0x65, 0x3a, 0x31, 0x62, 0x3a, 0x66, 0x37, 0x3a, 0x62, 0x38, + 0x3a, 0x62, 0x36, 0x3a, 0x35, 0x32, 0x3a, 0x35, 0x34, 0x3a, 0x65, 0x32, + 0x3a, 0x65, 0x32, 0x3a, 0x31, 0x30, 0x3a, 0x30, 0x64, 0x3a, 0x64, 0x36, + 0x3a, 0x30, 0x32, 0x3a, 0x39, 0x30, 0x3a, 0x33, 0x37, 0x3a, 0x66, 0x30, + 0x3a, 0x39, 0x36, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, + 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, + 0x3a, 0x20, 0x33, 0x37, 0x3a, 0x64, 0x35, 0x3a, 0x31, 0x30, 0x3a, 0x30, + 0x36, 0x3a, 0x63, 0x35, 0x3a, 0x31, 0x32, 0x3a, 0x65, 0x61, 0x3a, 0x61, + 0x62, 0x3a, 0x36, 0x32, 0x3a, 0x36, 0x34, 0x3a, 0x32, 0x31, 0x3a, 0x66, + 0x31, 0x3a, 0x65, 0x63, 0x3a, 0x38, 0x63, 0x3a, 0x39, 0x32, 0x3a, 0x30, + 0x31, 0x3a, 0x33, 0x66, 0x3a, 0x63, 0x35, 0x3a, 0x66, 0x38, 0x3a, 0x32, + 0x61, 0x3a, 0x65, 0x39, 0x3a, 0x38, 0x65, 0x3a, 0x65, 0x35, 0x3a, 0x33, + 0x33, 0x3a, 0x65, 0x62, 0x3a, 0x34, 0x36, 0x3a, 0x31, 0x39, 0x3a, 0x62, + 0x38, 0x3a, 0x64, 0x65, 0x3a, 0x62, 0x34, 0x3a, 0x64, 0x30, 0x3a, 0x36, + 0x63, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, + 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x44, 0x66, 0x44, + 0x43, 0x43, 0x41, 0x6d, 0x53, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, + 0x49, 0x51, 0x47, 0x4b, 0x79, 0x31, 0x61, 0x76, 0x31, 0x70, 0x74, 0x68, + 0x55, 0x36, 0x59, 0x32, 0x79, 0x76, 0x32, 0x76, 0x72, 0x45, 0x6f, 0x54, + 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, + 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x42, 0x59, 0x0a, 0x4d, + 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, + 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, 0x57, 0x4d, 0x42, 0x51, 0x47, 0x41, + 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4e, 0x52, 0x32, 0x56, 0x76, 0x56, + 0x48, 0x4a, 0x31, 0x63, 0x33, 0x51, 0x67, 0x53, 0x57, 0x35, 0x6a, 0x4c, + 0x6a, 0x45, 0x78, 0x4d, 0x43, 0x38, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, + 0x78, 0x4d, 0x6f, 0x0a, 0x52, 0x32, 0x56, 0x76, 0x56, 0x48, 0x4a, 0x31, + 0x63, 0x33, 0x51, 0x67, 0x55, 0x48, 0x4a, 0x70, 0x62, 0x57, 0x46, 0x79, + 0x65, 0x53, 0x42, 0x44, 0x5a, 0x58, 0x4a, 0x30, 0x61, 0x57, 0x5a, 0x70, + 0x59, 0x32, 0x46, 0x30, 0x61, 0x57, 0x39, 0x75, 0x49, 0x45, 0x46, 0x31, + 0x64, 0x47, 0x68, 0x76, 0x63, 0x6d, 0x6c, 0x30, 0x65, 0x54, 0x41, 0x65, + 0x46, 0x77, 0x30, 0x77, 0x4e, 0x6a, 0x45, 0x78, 0x0a, 0x4d, 0x6a, 0x63, + 0x77, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x44, 0x42, 0x61, 0x46, 0x77, 0x30, + 0x7a, 0x4e, 0x6a, 0x41, 0x33, 0x4d, 0x54, 0x59, 0x79, 0x4d, 0x7a, 0x55, + 0x35, 0x4e, 0x54, 0x6c, 0x61, 0x4d, 0x46, 0x67, 0x78, 0x43, 0x7a, 0x41, + 0x4a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x59, 0x54, 0x41, 0x6c, 0x56, + 0x54, 0x4d, 0x52, 0x59, 0x77, 0x46, 0x41, 0x59, 0x44, 0x56, 0x51, 0x51, + 0x4b, 0x0a, 0x45, 0x77, 0x31, 0x48, 0x5a, 0x57, 0x39, 0x55, 0x63, 0x6e, + 0x56, 0x7a, 0x64, 0x43, 0x42, 0x4a, 0x62, 0x6d, 0x4d, 0x75, 0x4d, 0x54, + 0x45, 0x77, 0x4c, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, 0x79, + 0x68, 0x48, 0x5a, 0x57, 0x39, 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, + 0x42, 0x51, 0x63, 0x6d, 0x6c, 0x74, 0x59, 0x58, 0x4a, 0x35, 0x49, 0x45, + 0x4e, 0x6c, 0x63, 0x6e, 0x52, 0x70, 0x0a, 0x5a, 0x6d, 0x6c, 0x6a, 0x59, + 0x58, 0x52, 0x70, 0x62, 0x32, 0x34, 0x67, 0x51, 0x58, 0x56, 0x30, 0x61, + 0x47, 0x39, 0x79, 0x61, 0x58, 0x52, 0x35, 0x4d, 0x49, 0x49, 0x42, 0x49, + 0x6a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, + 0x77, 0x30, 0x42, 0x41, 0x51, 0x45, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, + 0x51, 0x38, 0x41, 0x4d, 0x49, 0x49, 0x42, 0x43, 0x67, 0x4b, 0x43, 0x0a, + 0x41, 0x51, 0x45, 0x41, 0x76, 0x72, 0x67, 0x56, 0x65, 0x2f, 0x2f, 0x55, + 0x66, 0x48, 0x31, 0x6e, 0x72, 0x59, 0x4e, 0x6b, 0x65, 0x38, 0x68, 0x43, + 0x55, 0x79, 0x33, 0x66, 0x39, 0x6f, 0x51, 0x49, 0x49, 0x47, 0x48, 0x57, + 0x41, 0x56, 0x6c, 0x71, 0x6e, 0x45, 0x51, 0x52, 0x72, 0x2b, 0x39, 0x32, + 0x2f, 0x5a, 0x56, 0x2b, 0x7a, 0x6d, 0x45, 0x77, 0x75, 0x33, 0x71, 0x44, + 0x58, 0x77, 0x4b, 0x39, 0x0a, 0x41, 0x57, 0x62, 0x4b, 0x37, 0x68, 0x57, + 0x4e, 0x62, 0x36, 0x45, 0x77, 0x6e, 0x4c, 0x32, 0x68, 0x68, 0x5a, 0x36, + 0x55, 0x4f, 0x76, 0x4e, 0x57, 0x69, 0x41, 0x41, 0x78, 0x7a, 0x39, 0x6a, + 0x75, 0x61, 0x70, 0x59, 0x43, 0x32, 0x65, 0x30, 0x44, 0x6a, 0x50, 0x74, + 0x31, 0x62, 0x65, 0x66, 0x71, 0x75, 0x46, 0x55, 0x57, 0x42, 0x52, 0x61, + 0x61, 0x39, 0x4f, 0x42, 0x65, 0x73, 0x59, 0x6a, 0x41, 0x0a, 0x5a, 0x49, + 0x56, 0x63, 0x46, 0x55, 0x32, 0x49, 0x78, 0x37, 0x65, 0x36, 0x34, 0x48, + 0x58, 0x70, 0x72, 0x51, 0x55, 0x39, 0x6e, 0x63, 0x65, 0x4a, 0x53, 0x4f, + 0x43, 0x37, 0x4b, 0x4d, 0x67, 0x44, 0x34, 0x54, 0x43, 0x54, 0x5a, 0x46, + 0x35, 0x53, 0x77, 0x46, 0x6c, 0x77, 0x49, 0x6a, 0x56, 0x58, 0x69, 0x49, + 0x72, 0x78, 0x6c, 0x51, 0x71, 0x44, 0x31, 0x37, 0x77, 0x78, 0x63, 0x77, + 0x45, 0x30, 0x0a, 0x37, 0x65, 0x39, 0x47, 0x63, 0x65, 0x42, 0x72, 0x41, + 0x71, 0x67, 0x31, 0x63, 0x6d, 0x75, 0x58, 0x6d, 0x32, 0x62, 0x67, 0x79, + 0x78, 0x78, 0x35, 0x58, 0x39, 0x67, 0x61, 0x42, 0x47, 0x67, 0x65, 0x52, + 0x77, 0x4c, 0x6d, 0x6e, 0x57, 0x44, 0x69, 0x4e, 0x70, 0x63, 0x42, 0x33, + 0x38, 0x34, 0x31, 0x6b, 0x74, 0x2b, 0x2b, 0x5a, 0x38, 0x64, 0x74, 0x64, + 0x31, 0x6b, 0x37, 0x6a, 0x35, 0x33, 0x57, 0x0a, 0x6b, 0x42, 0x57, 0x55, + 0x76, 0x45, 0x49, 0x30, 0x45, 0x4d, 0x45, 0x35, 0x2b, 0x62, 0x45, 0x6e, + 0x50, 0x6e, 0x37, 0x57, 0x69, 0x6e, 0x58, 0x46, 0x73, 0x71, 0x2b, 0x57, + 0x30, 0x36, 0x4c, 0x65, 0x6d, 0x2b, 0x53, 0x59, 0x76, 0x6e, 0x33, 0x68, + 0x36, 0x59, 0x47, 0x74, 0x74, 0x6d, 0x2f, 0x38, 0x31, 0x77, 0x37, 0x61, + 0x34, 0x44, 0x53, 0x77, 0x44, 0x52, 0x70, 0x33, 0x35, 0x2b, 0x4d, 0x49, + 0x0a, 0x6d, 0x4f, 0x39, 0x59, 0x2b, 0x70, 0x79, 0x45, 0x74, 0x7a, 0x61, + 0x76, 0x77, 0x74, 0x2b, 0x73, 0x30, 0x76, 0x51, 0x51, 0x42, 0x6e, 0x42, + 0x78, 0x4e, 0x51, 0x49, 0x44, 0x41, 0x51, 0x41, 0x42, 0x6f, 0x30, 0x49, + 0x77, 0x51, 0x44, 0x41, 0x50, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x52, 0x4d, + 0x42, 0x41, 0x66, 0x38, 0x45, 0x42, 0x54, 0x41, 0x44, 0x41, 0x51, 0x48, + 0x2f, 0x4d, 0x41, 0x34, 0x47, 0x0a, 0x41, 0x31, 0x55, 0x64, 0x44, 0x77, + 0x45, 0x42, 0x2f, 0x77, 0x51, 0x45, 0x41, 0x77, 0x49, 0x42, 0x42, 0x6a, + 0x41, 0x64, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x34, 0x45, 0x46, 0x67, + 0x51, 0x55, 0x4c, 0x4e, 0x56, 0x51, 0x51, 0x5a, 0x63, 0x56, 0x69, 0x2f, + 0x43, 0x50, 0x4e, 0x6d, 0x46, 0x62, 0x53, 0x76, 0x74, 0x72, 0x32, 0x5a, + 0x6e, 0x4a, 0x4d, 0x35, 0x49, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x0a, 0x4b, + 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x46, 0x42, + 0x51, 0x41, 0x44, 0x67, 0x67, 0x45, 0x42, 0x41, 0x46, 0x70, 0x77, 0x66, + 0x79, 0x7a, 0x64, 0x74, 0x7a, 0x52, 0x50, 0x39, 0x59, 0x5a, 0x52, 0x71, + 0x53, 0x61, 0x2b, 0x53, 0x37, 0x69, 0x71, 0x38, 0x58, 0x45, 0x4e, 0x33, + 0x47, 0x48, 0x48, 0x6f, 0x4f, 0x6f, 0x30, 0x48, 0x6e, 0x70, 0x33, 0x44, + 0x77, 0x51, 0x31, 0x0a, 0x36, 0x43, 0x65, 0x50, 0x62, 0x4a, 0x43, 0x2f, + 0x6b, 0x52, 0x59, 0x6b, 0x52, 0x6a, 0x35, 0x4b, 0x54, 0x73, 0x34, 0x72, + 0x46, 0x74, 0x55, 0x4c, 0x55, 0x68, 0x33, 0x38, 0x48, 0x32, 0x65, 0x69, + 0x41, 0x6b, 0x55, 0x78, 0x54, 0x38, 0x37, 0x7a, 0x2b, 0x67, 0x4f, 0x6e, + 0x65, 0x5a, 0x31, 0x54, 0x61, 0x74, 0x6e, 0x61, 0x59, 0x7a, 0x72, 0x34, + 0x67, 0x4e, 0x66, 0x54, 0x6d, 0x65, 0x47, 0x6c, 0x0a, 0x34, 0x62, 0x37, + 0x55, 0x56, 0x58, 0x47, 0x59, 0x4e, 0x54, 0x71, 0x2b, 0x6b, 0x2b, 0x71, + 0x75, 0x72, 0x55, 0x4b, 0x79, 0x6b, 0x47, 0x2f, 0x67, 0x2f, 0x43, 0x46, + 0x4e, 0x4e, 0x57, 0x4d, 0x7a, 0x69, 0x55, 0x6e, 0x57, 0x6d, 0x30, 0x37, + 0x4b, 0x78, 0x2b, 0x64, 0x4f, 0x43, 0x51, 0x44, 0x33, 0x32, 0x73, 0x66, + 0x76, 0x6d, 0x57, 0x4b, 0x5a, 0x64, 0x37, 0x61, 0x56, 0x49, 0x6c, 0x36, + 0x4b, 0x0a, 0x6f, 0x4b, 0x76, 0x30, 0x75, 0x48, 0x69, 0x59, 0x79, 0x6a, + 0x67, 0x5a, 0x6d, 0x63, 0x6c, 0x79, 0x6e, 0x6e, 0x6a, 0x4e, 0x53, 0x36, + 0x79, 0x76, 0x47, 0x61, 0x42, 0x7a, 0x45, 0x69, 0x33, 0x38, 0x77, 0x6b, + 0x47, 0x36, 0x67, 0x5a, 0x48, 0x61, 0x46, 0x6c, 0x6f, 0x78, 0x74, 0x2f, + 0x6d, 0x30, 0x63, 0x59, 0x41, 0x53, 0x53, 0x4a, 0x6c, 0x79, 0x63, 0x31, + 0x70, 0x5a, 0x55, 0x38, 0x46, 0x6a, 0x0a, 0x55, 0x6a, 0x50, 0x74, 0x70, + 0x38, 0x6e, 0x53, 0x4f, 0x51, 0x4a, 0x77, 0x2b, 0x75, 0x43, 0x78, 0x51, + 0x6d, 0x59, 0x70, 0x71, 0x70, 0x74, 0x52, 0x37, 0x54, 0x42, 0x55, 0x49, + 0x68, 0x52, 0x66, 0x32, 0x61, 0x73, 0x64, 0x77, 0x65, 0x53, 0x55, 0x38, + 0x50, 0x6a, 0x31, 0x4b, 0x2f, 0x66, 0x71, 0x79, 0x6e, 0x68, 0x47, 0x31, + 0x72, 0x69, 0x52, 0x2f, 0x61, 0x59, 0x4e, 0x4b, 0x78, 0x6f, 0x55, 0x0a, + 0x41, 0x54, 0x36, 0x41, 0x38, 0x45, 0x4b, 0x67, 0x6c, 0x51, 0x64, 0x65, + 0x62, 0x63, 0x33, 0x4d, 0x53, 0x36, 0x52, 0x46, 0x6a, 0x61, 0x73, 0x53, + 0x36, 0x4c, 0x50, 0x65, 0x57, 0x75, 0x57, 0x67, 0x66, 0x4f, 0x67, 0x50, + 0x49, 0x68, 0x31, 0x61, 0x36, 0x56, 0x6b, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, + 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, + 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, + 0x3d, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50, 0x72, 0x69, 0x6d, + 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, + 0x4f, 0x3d, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, + 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x20, 0x44, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x74, + 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, + 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, + 0x79, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, + 0x20, 0x43, 0x4e, 0x3d, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50, + 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, + 0x43, 0x41, 0x20, 0x4f, 0x3d, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, + 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x43, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x44, 0x69, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, + 0x36, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, + 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, + 0x6f, 0x6e, 0x6c, 0x79, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, + 0x3a, 0x20, 0x22, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50, 0x72, + 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, + 0x41, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, + 0x20, 0x36, 0x39, 0x35, 0x32, 0x39, 0x31, 0x38, 0x31, 0x39, 0x39, 0x32, + 0x30, 0x33, 0x39, 0x32, 0x30, 0x33, 0x35, 0x36, 0x36, 0x32, 0x39, 0x38, + 0x39, 0x35, 0x33, 0x37, 0x38, 0x37, 0x37, 0x31, 0x32, 0x39, 0x34, 0x30, + 0x39, 0x30, 0x39, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, + 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x38, + 0x63, 0x3a, 0x63, 0x61, 0x3a, 0x64, 0x63, 0x3a, 0x30, 0x62, 0x3a, 0x32, + 0x32, 0x3a, 0x63, 0x65, 0x3a, 0x66, 0x35, 0x3a, 0x62, 0x65, 0x3a, 0x37, + 0x32, 0x3a, 0x61, 0x63, 0x3a, 0x34, 0x31, 0x3a, 0x31, 0x61, 0x3a, 0x31, + 0x31, 0x3a, 0x61, 0x38, 0x3a, 0x64, 0x38, 0x3a, 0x31, 0x32, 0x0a, 0x23, + 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x39, 0x31, 0x3a, 0x63, 0x36, + 0x3a, 0x64, 0x36, 0x3a, 0x65, 0x65, 0x3a, 0x33, 0x65, 0x3a, 0x38, 0x61, + 0x3a, 0x63, 0x38, 0x3a, 0x36, 0x33, 0x3a, 0x38, 0x34, 0x3a, 0x65, 0x35, + 0x3a, 0x34, 0x38, 0x3a, 0x63, 0x32, 0x3a, 0x39, 0x39, 0x3a, 0x32, 0x39, + 0x3a, 0x35, 0x63, 0x3a, 0x37, 0x35, 0x3a, 0x36, 0x63, 0x3a, 0x38, 0x31, + 0x3a, 0x37, 0x62, 0x3a, 0x38, 0x31, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, + 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, + 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x38, 0x64, 0x3a, 0x37, 0x32, 0x3a, 0x32, + 0x66, 0x3a, 0x38, 0x31, 0x3a, 0x61, 0x39, 0x3a, 0x63, 0x31, 0x3a, 0x31, + 0x33, 0x3a, 0x63, 0x30, 0x3a, 0x37, 0x39, 0x3a, 0x31, 0x64, 0x3a, 0x66, + 0x31, 0x3a, 0x33, 0x36, 0x3a, 0x61, 0x32, 0x3a, 0x39, 0x36, 0x3a, 0x36, + 0x64, 0x3a, 0x62, 0x32, 0x3a, 0x36, 0x63, 0x3a, 0x39, 0x35, 0x3a, 0x30, + 0x61, 0x3a, 0x39, 0x37, 0x3a, 0x31, 0x64, 0x3a, 0x62, 0x34, 0x3a, 0x36, + 0x62, 0x3a, 0x34, 0x31, 0x3a, 0x39, 0x39, 0x3a, 0x66, 0x34, 0x3a, 0x65, + 0x61, 0x3a, 0x35, 0x34, 0x3a, 0x62, 0x37, 0x3a, 0x38, 0x62, 0x3a, 0x66, + 0x62, 0x3a, 0x39, 0x66, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, + 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, + 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, + 0x45, 0x49, 0x44, 0x43, 0x43, 0x41, 0x77, 0x69, 0x67, 0x41, 0x77, 0x49, + 0x42, 0x41, 0x67, 0x49, 0x51, 0x4e, 0x45, 0x37, 0x56, 0x56, 0x79, 0x44, + 0x56, 0x37, 0x65, 0x78, 0x4a, 0x39, 0x43, 0x2f, 0x4f, 0x4e, 0x39, 0x73, + 0x72, 0x62, 0x54, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, + 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x43, + 0x42, 0x0a, 0x71, 0x54, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, + 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, 0x46, 0x54, + 0x41, 0x54, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, 0x44, 0x48, + 0x52, 0x6f, 0x59, 0x58, 0x64, 0x30, 0x5a, 0x53, 0x77, 0x67, 0x53, 0x57, + 0x35, 0x6a, 0x4c, 0x6a, 0x45, 0x6f, 0x4d, 0x43, 0x59, 0x47, 0x41, 0x31, + 0x55, 0x45, 0x43, 0x78, 0x4d, 0x66, 0x0a, 0x51, 0x32, 0x56, 0x79, 0x64, + 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, 0x6c, 0x76, 0x62, + 0x69, 0x42, 0x54, 0x5a, 0x58, 0x4a, 0x32, 0x61, 0x57, 0x4e, 0x6c, 0x63, + 0x79, 0x42, 0x45, 0x61, 0x58, 0x5a, 0x70, 0x63, 0x32, 0x6c, 0x76, 0x62, + 0x6a, 0x45, 0x34, 0x4d, 0x44, 0x59, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, + 0x78, 0x4d, 0x76, 0x4b, 0x47, 0x4d, 0x70, 0x49, 0x44, 0x49, 0x77, 0x0a, + 0x4d, 0x44, 0x59, 0x67, 0x64, 0x47, 0x68, 0x68, 0x64, 0x33, 0x52, 0x6c, + 0x4c, 0x43, 0x42, 0x4a, 0x62, 0x6d, 0x4d, 0x75, 0x49, 0x43, 0x30, 0x67, + 0x52, 0x6d, 0x39, 0x79, 0x49, 0x47, 0x46, 0x31, 0x64, 0x47, 0x68, 0x76, + 0x63, 0x6d, 0x6c, 0x36, 0x5a, 0x57, 0x51, 0x67, 0x64, 0x58, 0x4e, 0x6c, + 0x49, 0x47, 0x39, 0x75, 0x62, 0x48, 0x6b, 0x78, 0x48, 0x7a, 0x41, 0x64, + 0x42, 0x67, 0x4e, 0x56, 0x0a, 0x42, 0x41, 0x4d, 0x54, 0x46, 0x6e, 0x52, + 0x6f, 0x59, 0x58, 0x64, 0x30, 0x5a, 0x53, 0x42, 0x51, 0x63, 0x6d, 0x6c, + 0x74, 0x59, 0x58, 0x4a, 0x35, 0x49, 0x46, 0x4a, 0x76, 0x62, 0x33, 0x51, + 0x67, 0x51, 0x30, 0x45, 0x77, 0x48, 0x68, 0x63, 0x4e, 0x4d, 0x44, 0x59, + 0x78, 0x4d, 0x54, 0x45, 0x33, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x44, 0x41, + 0x77, 0x57, 0x68, 0x63, 0x4e, 0x4d, 0x7a, 0x59, 0x77, 0x0a, 0x4e, 0x7a, + 0x45, 0x32, 0x4d, 0x6a, 0x4d, 0x31, 0x4f, 0x54, 0x55, 0x35, 0x57, 0x6a, + 0x43, 0x42, 0x71, 0x54, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, + 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, 0x46, 0x54, + 0x41, 0x54, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, 0x44, 0x48, + 0x52, 0x6f, 0x59, 0x58, 0x64, 0x30, 0x5a, 0x53, 0x77, 0x67, 0x53, 0x57, + 0x35, 0x6a, 0x0a, 0x4c, 0x6a, 0x45, 0x6f, 0x4d, 0x43, 0x59, 0x47, 0x41, + 0x31, 0x55, 0x45, 0x43, 0x78, 0x4d, 0x66, 0x51, 0x32, 0x56, 0x79, 0x64, + 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, 0x6c, 0x76, 0x62, + 0x69, 0x42, 0x54, 0x5a, 0x58, 0x4a, 0x32, 0x61, 0x57, 0x4e, 0x6c, 0x63, + 0x79, 0x42, 0x45, 0x61, 0x58, 0x5a, 0x70, 0x63, 0x32, 0x6c, 0x76, 0x62, + 0x6a, 0x45, 0x34, 0x4d, 0x44, 0x59, 0x47, 0x0a, 0x41, 0x31, 0x55, 0x45, + 0x43, 0x78, 0x4d, 0x76, 0x4b, 0x47, 0x4d, 0x70, 0x49, 0x44, 0x49, 0x77, + 0x4d, 0x44, 0x59, 0x67, 0x64, 0x47, 0x68, 0x68, 0x64, 0x33, 0x52, 0x6c, + 0x4c, 0x43, 0x42, 0x4a, 0x62, 0x6d, 0x4d, 0x75, 0x49, 0x43, 0x30, 0x67, + 0x52, 0x6d, 0x39, 0x79, 0x49, 0x47, 0x46, 0x31, 0x64, 0x47, 0x68, 0x76, + 0x63, 0x6d, 0x6c, 0x36, 0x5a, 0x57, 0x51, 0x67, 0x64, 0x58, 0x4e, 0x6c, + 0x0a, 0x49, 0x47, 0x39, 0x75, 0x62, 0x48, 0x6b, 0x78, 0x48, 0x7a, 0x41, + 0x64, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x4d, 0x54, 0x46, 0x6e, 0x52, + 0x6f, 0x59, 0x58, 0x64, 0x30, 0x5a, 0x53, 0x42, 0x51, 0x63, 0x6d, 0x6c, + 0x74, 0x59, 0x58, 0x4a, 0x35, 0x49, 0x46, 0x4a, 0x76, 0x62, 0x33, 0x51, + 0x67, 0x51, 0x30, 0x45, 0x77, 0x67, 0x67, 0x45, 0x69, 0x4d, 0x41, 0x30, + 0x47, 0x43, 0x53, 0x71, 0x47, 0x0a, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, + 0x45, 0x42, 0x41, 0x51, 0x55, 0x41, 0x41, 0x34, 0x49, 0x42, 0x44, 0x77, + 0x41, 0x77, 0x67, 0x67, 0x45, 0x4b, 0x41, 0x6f, 0x49, 0x42, 0x41, 0x51, + 0x43, 0x73, 0x6f, 0x50, 0x44, 0x37, 0x67, 0x46, 0x6e, 0x55, 0x6e, 0x4d, + 0x65, 0x6b, 0x7a, 0x35, 0x32, 0x68, 0x57, 0x58, 0x4d, 0x4a, 0x45, 0x45, + 0x55, 0x4d, 0x44, 0x53, 0x78, 0x75, 0x61, 0x50, 0x46, 0x73, 0x0a, 0x57, + 0x30, 0x68, 0x6f, 0x53, 0x56, 0x6b, 0x33, 0x2f, 0x41, 0x73, 0x7a, 0x47, + 0x63, 0x4a, 0x33, 0x66, 0x38, 0x77, 0x51, 0x4c, 0x5a, 0x55, 0x30, 0x48, + 0x4f, 0x62, 0x72, 0x54, 0x51, 0x6d, 0x6e, 0x48, 0x4e, 0x4b, 0x34, 0x79, + 0x5a, 0x63, 0x32, 0x41, 0x72, 0x65, 0x4a, 0x31, 0x43, 0x52, 0x66, 0x42, + 0x73, 0x44, 0x4d, 0x52, 0x4a, 0x53, 0x55, 0x6a, 0x51, 0x4a, 0x69, 0x62, + 0x2b, 0x74, 0x61, 0x0a, 0x33, 0x52, 0x47, 0x4e, 0x4b, 0x4a, 0x70, 0x63, + 0x68, 0x4a, 0x41, 0x51, 0x65, 0x67, 0x32, 0x39, 0x64, 0x47, 0x59, 0x76, + 0x61, 0x6a, 0x69, 0x67, 0x34, 0x74, 0x56, 0x55, 0x52, 0x4f, 0x73, 0x64, + 0x42, 0x35, 0x38, 0x48, 0x75, 0x6d, 0x2f, 0x75, 0x36, 0x66, 0x31, 0x4f, + 0x43, 0x79, 0x6e, 0x31, 0x50, 0x6f, 0x53, 0x67, 0x41, 0x66, 0x47, 0x63, + 0x71, 0x2f, 0x67, 0x63, 0x66, 0x6f, 0x6d, 0x6b, 0x0a, 0x36, 0x4b, 0x48, + 0x59, 0x63, 0x57, 0x55, 0x4e, 0x6f, 0x31, 0x46, 0x37, 0x37, 0x72, 0x7a, + 0x53, 0x49, 0x6d, 0x41, 0x4e, 0x75, 0x56, 0x75, 0x64, 0x33, 0x37, 0x72, + 0x38, 0x55, 0x56, 0x73, 0x4c, 0x72, 0x35, 0x69, 0x79, 0x36, 0x53, 0x37, + 0x70, 0x42, 0x4f, 0x68, 0x69, 0x68, 0x39, 0x34, 0x72, 0x79, 0x4e, 0x64, + 0x4f, 0x77, 0x55, 0x78, 0x6b, 0x48, 0x74, 0x33, 0x50, 0x68, 0x31, 0x69, + 0x36, 0x0a, 0x53, 0x6b, 0x2f, 0x4b, 0x61, 0x41, 0x63, 0x64, 0x48, 0x4a, + 0x31, 0x4b, 0x78, 0x74, 0x55, 0x76, 0x6b, 0x63, 0x78, 0x38, 0x63, 0x58, + 0x49, 0x63, 0x78, 0x63, 0x42, 0x6e, 0x36, 0x7a, 0x4c, 0x39, 0x79, 0x5a, + 0x4a, 0x63, 0x6c, 0x4e, 0x71, 0x46, 0x77, 0x4a, 0x75, 0x2f, 0x55, 0x33, + 0x30, 0x72, 0x43, 0x66, 0x53, 0x4d, 0x6e, 0x5a, 0x45, 0x66, 0x6c, 0x32, + 0x70, 0x53, 0x79, 0x39, 0x34, 0x4a, 0x0a, 0x4e, 0x71, 0x52, 0x33, 0x32, + 0x48, 0x75, 0x48, 0x55, 0x45, 0x54, 0x56, 0x50, 0x6d, 0x34, 0x70, 0x61, + 0x66, 0x73, 0x35, 0x53, 0x53, 0x59, 0x65, 0x43, 0x61, 0x57, 0x41, 0x65, + 0x30, 0x41, 0x74, 0x36, 0x2b, 0x67, 0x6e, 0x68, 0x63, 0x6e, 0x2b, 0x59, + 0x66, 0x31, 0x2b, 0x35, 0x6e, 0x79, 0x58, 0x48, 0x64, 0x57, 0x64, 0x41, + 0x67, 0x4d, 0x42, 0x41, 0x41, 0x47, 0x6a, 0x51, 0x6a, 0x42, 0x41, 0x0a, + 0x4d, 0x41, 0x38, 0x47, 0x41, 0x31, 0x55, 0x64, 0x45, 0x77, 0x45, 0x42, + 0x2f, 0x77, 0x51, 0x46, 0x4d, 0x41, 0x4d, 0x42, 0x41, 0x66, 0x38, 0x77, + 0x44, 0x67, 0x59, 0x44, 0x56, 0x52, 0x30, 0x50, 0x41, 0x51, 0x48, 0x2f, + 0x42, 0x41, 0x51, 0x44, 0x41, 0x67, 0x45, 0x47, 0x4d, 0x42, 0x30, 0x47, + 0x41, 0x31, 0x55, 0x64, 0x44, 0x67, 0x51, 0x57, 0x42, 0x42, 0x52, 0x37, + 0x57, 0x30, 0x58, 0x50, 0x0a, 0x72, 0x38, 0x37, 0x4c, 0x65, 0x76, 0x30, + 0x78, 0x6b, 0x68, 0x70, 0x71, 0x74, 0x76, 0x4e, 0x47, 0x36, 0x31, 0x64, + 0x49, 0x55, 0x44, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, + 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x41, 0x4f, + 0x43, 0x41, 0x51, 0x45, 0x41, 0x65, 0x52, 0x48, 0x41, 0x53, 0x37, 0x4f, + 0x52, 0x74, 0x76, 0x7a, 0x77, 0x36, 0x57, 0x66, 0x55, 0x0a, 0x44, 0x57, + 0x35, 0x46, 0x76, 0x6c, 0x58, 0x6f, 0x6b, 0x39, 0x4c, 0x4f, 0x41, 0x7a, + 0x2f, 0x74, 0x32, 0x69, 0x57, 0x77, 0x48, 0x56, 0x66, 0x4c, 0x48, 0x6a, + 0x70, 0x32, 0x6f, 0x45, 0x7a, 0x73, 0x55, 0x48, 0x62, 0x6f, 0x5a, 0x48, + 0x49, 0x4d, 0x70, 0x4b, 0x6e, 0x78, 0x75, 0x49, 0x76, 0x57, 0x31, 0x6f, + 0x65, 0x45, 0x75, 0x7a, 0x4c, 0x6c, 0x51, 0x52, 0x48, 0x41, 0x64, 0x39, + 0x6d, 0x7a, 0x0a, 0x59, 0x4a, 0x33, 0x72, 0x47, 0x39, 0x58, 0x52, 0x62, + 0x6b, 0x52, 0x45, 0x71, 0x61, 0x59, 0x42, 0x37, 0x46, 0x56, 0x69, 0x48, + 0x58, 0x65, 0x34, 0x58, 0x49, 0x35, 0x49, 0x53, 0x58, 0x79, 0x63, 0x4f, + 0x31, 0x63, 0x52, 0x72, 0x4b, 0x31, 0x7a, 0x4e, 0x34, 0x34, 0x76, 0x65, + 0x46, 0x79, 0x51, 0x61, 0x45, 0x66, 0x5a, 0x59, 0x47, 0x44, 0x6d, 0x2f, + 0x41, 0x63, 0x39, 0x49, 0x69, 0x41, 0x58, 0x0a, 0x78, 0x50, 0x63, 0x57, + 0x36, 0x63, 0x54, 0x59, 0x63, 0x76, 0x6e, 0x49, 0x63, 0x33, 0x7a, 0x66, + 0x46, 0x69, 0x38, 0x56, 0x71, 0x54, 0x37, 0x39, 0x61, 0x69, 0x65, 0x32, + 0x6f, 0x65, 0x74, 0x61, 0x75, 0x70, 0x67, 0x66, 0x31, 0x65, 0x4e, 0x4e, + 0x5a, 0x41, 0x71, 0x64, 0x45, 0x38, 0x68, 0x68, 0x75, 0x76, 0x55, 0x35, + 0x48, 0x49, 0x65, 0x36, 0x75, 0x4c, 0x31, 0x37, 0x49, 0x6e, 0x2f, 0x32, + 0x0a, 0x2f, 0x71, 0x78, 0x41, 0x65, 0x65, 0x57, 0x73, 0x45, 0x47, 0x38, + 0x39, 0x6a, 0x78, 0x74, 0x35, 0x64, 0x6f, 0x76, 0x45, 0x4e, 0x37, 0x4d, + 0x68, 0x47, 0x49, 0x54, 0x6c, 0x4e, 0x67, 0x44, 0x72, 0x59, 0x79, 0x43, + 0x5a, 0x75, 0x65, 0x6e, 0x2b, 0x4d, 0x77, 0x53, 0x37, 0x51, 0x63, 0x6a, + 0x42, 0x41, 0x76, 0x6c, 0x45, 0x59, 0x79, 0x43, 0x65, 0x67, 0x63, 0x35, + 0x43, 0x30, 0x39, 0x59, 0x2f, 0x0a, 0x4c, 0x48, 0x62, 0x54, 0x59, 0x35, + 0x78, 0x5a, 0x33, 0x59, 0x2b, 0x6d, 0x34, 0x51, 0x36, 0x67, 0x4c, 0x6b, + 0x48, 0x33, 0x4c, 0x70, 0x56, 0x48, 0x7a, 0x37, 0x7a, 0x39, 0x4d, 0x2f, + 0x50, 0x32, 0x43, 0x32, 0x46, 0x2b, 0x66, 0x70, 0x45, 0x72, 0x67, 0x55, + 0x66, 0x43, 0x4a, 0x7a, 0x44, 0x75, 0x70, 0x78, 0x42, 0x64, 0x4e, 0x34, + 0x39, 0x63, 0x4f, 0x53, 0x76, 0x6b, 0x42, 0x50, 0x42, 0x37, 0x0a, 0x6a, + 0x56, 0x61, 0x4d, 0x61, 0x41, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, + 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, + 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, + 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, + 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, + 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, + 0x20, 0x4f, 0x3d, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, + 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x56, 0x65, 0x72, + 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, + 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x28, 0x63, 0x29, 0x20, + 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, + 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, + 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, + 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x0a, 0x23, 0x20, + 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, + 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, + 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, + 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, + 0x20, 0x4f, 0x3d, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, + 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x56, 0x65, 0x72, + 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, + 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x28, 0x63, 0x29, 0x20, + 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, + 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, + 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, + 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x0a, 0x23, 0x20, + 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x56, 0x65, 0x72, 0x69, + 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, + 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, + 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x22, 0x0a, 0x23, 0x20, + 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x33, 0x33, 0x30, 0x33, + 0x37, 0x36, 0x34, 0x34, 0x31, 0x36, 0x37, 0x35, 0x36, 0x38, 0x30, 0x35, + 0x38, 0x39, 0x37, 0x30, 0x31, 0x36, 0x34, 0x37, 0x31, 0x39, 0x34, 0x37, + 0x35, 0x36, 0x37, 0x36, 0x31, 0x30, 0x31, 0x34, 0x35, 0x30, 0x0a, 0x23, + 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, + 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x63, 0x62, 0x3a, 0x31, 0x37, 0x3a, + 0x65, 0x34, 0x3a, 0x33, 0x31, 0x3a, 0x36, 0x37, 0x3a, 0x33, 0x65, 0x3a, + 0x65, 0x32, 0x3a, 0x30, 0x39, 0x3a, 0x66, 0x65, 0x3a, 0x34, 0x35, 0x3a, + 0x35, 0x37, 0x3a, 0x39, 0x33, 0x3a, 0x66, 0x33, 0x3a, 0x30, 0x61, 0x3a, + 0x66, 0x61, 0x3a, 0x31, 0x63, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, + 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, + 0x3a, 0x20, 0x34, 0x65, 0x3a, 0x62, 0x36, 0x3a, 0x64, 0x35, 0x3a, 0x37, + 0x38, 0x3a, 0x34, 0x39, 0x3a, 0x39, 0x62, 0x3a, 0x31, 0x63, 0x3a, 0x63, + 0x66, 0x3a, 0x35, 0x66, 0x3a, 0x35, 0x38, 0x3a, 0x31, 0x65, 0x3a, 0x61, + 0x64, 0x3a, 0x35, 0x36, 0x3a, 0x62, 0x65, 0x3a, 0x33, 0x64, 0x3a, 0x39, + 0x62, 0x3a, 0x36, 0x37, 0x3a, 0x34, 0x34, 0x3a, 0x61, 0x35, 0x3a, 0x65, + 0x35, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x39, 0x61, 0x3a, 0x63, 0x66, 0x3a, 0x61, 0x62, 0x3a, 0x37, 0x65, 0x3a, + 0x34, 0x33, 0x3a, 0x63, 0x38, 0x3a, 0x64, 0x38, 0x3a, 0x38, 0x30, 0x3a, + 0x64, 0x30, 0x3a, 0x36, 0x62, 0x3a, 0x32, 0x36, 0x3a, 0x32, 0x61, 0x3a, + 0x39, 0x34, 0x3a, 0x64, 0x65, 0x3a, 0x65, 0x65, 0x3a, 0x65, 0x34, 0x3a, + 0x62, 0x34, 0x3a, 0x36, 0x35, 0x3a, 0x39, 0x39, 0x3a, 0x38, 0x39, 0x3a, + 0x63, 0x33, 0x3a, 0x64, 0x30, 0x3a, 0x63, 0x61, 0x3a, 0x66, 0x31, 0x3a, + 0x39, 0x62, 0x3a, 0x61, 0x66, 0x3a, 0x36, 0x34, 0x3a, 0x30, 0x35, 0x3a, + 0x65, 0x34, 0x3a, 0x31, 0x61, 0x3a, 0x62, 0x37, 0x3a, 0x64, 0x66, 0x0a, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, + 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x45, 0x30, 0x7a, 0x43, 0x43, + 0x41, 0x37, 0x75, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x51, + 0x47, 0x4e, 0x72, 0x52, 0x6e, 0x69, 0x5a, 0x39, 0x36, 0x4c, 0x74, 0x4b, + 0x49, 0x56, 0x6a, 0x4e, 0x7a, 0x47, 0x73, 0x37, 0x53, 0x6a, 0x41, 0x4e, + 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, + 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x43, 0x42, 0x0a, 0x79, 0x6a, 0x45, + 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, + 0x43, 0x56, 0x56, 0x4d, 0x78, 0x46, 0x7a, 0x41, 0x56, 0x42, 0x67, 0x4e, + 0x56, 0x42, 0x41, 0x6f, 0x54, 0x44, 0x6c, 0x5a, 0x6c, 0x63, 0x6d, 0x6c, + 0x54, 0x61, 0x57, 0x64, 0x75, 0x4c, 0x43, 0x42, 0x4a, 0x62, 0x6d, 0x4d, + 0x75, 0x4d, 0x52, 0x38, 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, + 0x4c, 0x0a, 0x45, 0x78, 0x5a, 0x57, 0x5a, 0x58, 0x4a, 0x70, 0x55, 0x32, + 0x6c, 0x6e, 0x62, 0x69, 0x42, 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, + 0x42, 0x4f, 0x5a, 0x58, 0x52, 0x33, 0x62, 0x33, 0x4a, 0x72, 0x4d, 0x54, + 0x6f, 0x77, 0x4f, 0x41, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, 0x7a, + 0x45, 0x6f, 0x59, 0x79, 0x6b, 0x67, 0x4d, 0x6a, 0x41, 0x77, 0x4e, 0x69, + 0x42, 0x57, 0x5a, 0x58, 0x4a, 0x70, 0x0a, 0x55, 0x32, 0x6c, 0x6e, 0x62, + 0x69, 0x77, 0x67, 0x53, 0x57, 0x35, 0x6a, 0x4c, 0x69, 0x41, 0x74, 0x49, + 0x45, 0x5a, 0x76, 0x63, 0x69, 0x42, 0x68, 0x64, 0x58, 0x52, 0x6f, 0x62, + 0x33, 0x4a, 0x70, 0x65, 0x6d, 0x56, 0x6b, 0x49, 0x48, 0x56, 0x7a, 0x5a, + 0x53, 0x42, 0x76, 0x62, 0x6d, 0x78, 0x35, 0x4d, 0x55, 0x55, 0x77, 0x51, + 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, 0x7a, 0x78, 0x57, 0x0a, + 0x5a, 0x58, 0x4a, 0x70, 0x55, 0x32, 0x6c, 0x6e, 0x62, 0x69, 0x42, 0x44, + 0x62, 0x47, 0x46, 0x7a, 0x63, 0x79, 0x41, 0x7a, 0x49, 0x46, 0x42, 0x31, + 0x59, 0x6d, 0x78, 0x70, 0x59, 0x79, 0x42, 0x51, 0x63, 0x6d, 0x6c, 0x74, + 0x59, 0x58, 0x4a, 0x35, 0x49, 0x45, 0x4e, 0x6c, 0x63, 0x6e, 0x52, 0x70, + 0x5a, 0x6d, 0x6c, 0x6a, 0x59, 0x58, 0x52, 0x70, 0x62, 0x32, 0x34, 0x67, + 0x51, 0x58, 0x56, 0x30, 0x0a, 0x61, 0x47, 0x39, 0x79, 0x61, 0x58, 0x52, + 0x35, 0x49, 0x43, 0x30, 0x67, 0x52, 0x7a, 0x55, 0x77, 0x48, 0x68, 0x63, + 0x4e, 0x4d, 0x44, 0x59, 0x78, 0x4d, 0x54, 0x41, 0x34, 0x4d, 0x44, 0x41, + 0x77, 0x4d, 0x44, 0x41, 0x77, 0x57, 0x68, 0x63, 0x4e, 0x4d, 0x7a, 0x59, + 0x77, 0x4e, 0x7a, 0x45, 0x32, 0x4d, 0x6a, 0x4d, 0x31, 0x4f, 0x54, 0x55, + 0x35, 0x57, 0x6a, 0x43, 0x42, 0x79, 0x6a, 0x45, 0x4c, 0x0a, 0x4d, 0x41, + 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x56, 0x56, + 0x4d, 0x78, 0x46, 0x7a, 0x41, 0x56, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, + 0x6f, 0x54, 0x44, 0x6c, 0x5a, 0x6c, 0x63, 0x6d, 0x6c, 0x54, 0x61, 0x57, + 0x64, 0x75, 0x4c, 0x43, 0x42, 0x4a, 0x62, 0x6d, 0x4d, 0x75, 0x4d, 0x52, + 0x38, 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, 0x78, + 0x5a, 0x57, 0x0a, 0x5a, 0x58, 0x4a, 0x70, 0x55, 0x32, 0x6c, 0x6e, 0x62, + 0x69, 0x42, 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x4f, 0x5a, + 0x58, 0x52, 0x33, 0x62, 0x33, 0x4a, 0x72, 0x4d, 0x54, 0x6f, 0x77, 0x4f, + 0x41, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, 0x7a, 0x45, 0x6f, 0x59, + 0x79, 0x6b, 0x67, 0x4d, 0x6a, 0x41, 0x77, 0x4e, 0x69, 0x42, 0x57, 0x5a, + 0x58, 0x4a, 0x70, 0x55, 0x32, 0x6c, 0x6e, 0x0a, 0x62, 0x69, 0x77, 0x67, + 0x53, 0x57, 0x35, 0x6a, 0x4c, 0x69, 0x41, 0x74, 0x49, 0x45, 0x5a, 0x76, + 0x63, 0x69, 0x42, 0x68, 0x64, 0x58, 0x52, 0x6f, 0x62, 0x33, 0x4a, 0x70, + 0x65, 0x6d, 0x56, 0x6b, 0x49, 0x48, 0x56, 0x7a, 0x5a, 0x53, 0x42, 0x76, + 0x62, 0x6d, 0x78, 0x35, 0x4d, 0x55, 0x55, 0x77, 0x51, 0x77, 0x59, 0x44, + 0x56, 0x51, 0x51, 0x44, 0x45, 0x7a, 0x78, 0x57, 0x5a, 0x58, 0x4a, 0x70, + 0x0a, 0x55, 0x32, 0x6c, 0x6e, 0x62, 0x69, 0x42, 0x44, 0x62, 0x47, 0x46, + 0x7a, 0x63, 0x79, 0x41, 0x7a, 0x49, 0x46, 0x42, 0x31, 0x59, 0x6d, 0x78, + 0x70, 0x59, 0x79, 0x42, 0x51, 0x63, 0x6d, 0x6c, 0x74, 0x59, 0x58, 0x4a, + 0x35, 0x49, 0x45, 0x4e, 0x6c, 0x63, 0x6e, 0x52, 0x70, 0x5a, 0x6d, 0x6c, + 0x6a, 0x59, 0x58, 0x52, 0x70, 0x62, 0x32, 0x34, 0x67, 0x51, 0x58, 0x56, + 0x30, 0x61, 0x47, 0x39, 0x79, 0x0a, 0x61, 0x58, 0x52, 0x35, 0x49, 0x43, + 0x30, 0x67, 0x52, 0x7a, 0x55, 0x77, 0x67, 0x67, 0x45, 0x69, 0x4d, 0x41, + 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, + 0x45, 0x42, 0x41, 0x51, 0x55, 0x41, 0x41, 0x34, 0x49, 0x42, 0x44, 0x77, + 0x41, 0x77, 0x67, 0x67, 0x45, 0x4b, 0x41, 0x6f, 0x49, 0x42, 0x41, 0x51, + 0x43, 0x76, 0x4a, 0x41, 0x67, 0x49, 0x4b, 0x58, 0x6f, 0x31, 0x0a, 0x6e, + 0x6d, 0x41, 0x4d, 0x71, 0x75, 0x64, 0x4c, 0x4f, 0x30, 0x37, 0x63, 0x66, + 0x4c, 0x77, 0x38, 0x52, 0x52, 0x79, 0x37, 0x4b, 0x2b, 0x44, 0x2b, 0x4b, + 0x51, 0x4c, 0x35, 0x56, 0x77, 0x69, 0x6a, 0x5a, 0x49, 0x55, 0x56, 0x4a, + 0x2f, 0x58, 0x78, 0x72, 0x63, 0x67, 0x78, 0x69, 0x56, 0x30, 0x69, 0x36, + 0x43, 0x71, 0x71, 0x70, 0x6b, 0x4b, 0x7a, 0x6a, 0x2f, 0x69, 0x35, 0x56, + 0x62, 0x65, 0x78, 0x0a, 0x74, 0x30, 0x75, 0x7a, 0x2f, 0x6f, 0x39, 0x2b, + 0x42, 0x31, 0x66, 0x73, 0x37, 0x30, 0x50, 0x62, 0x5a, 0x6d, 0x49, 0x56, + 0x59, 0x63, 0x39, 0x67, 0x44, 0x61, 0x54, 0x59, 0x33, 0x76, 0x6a, 0x67, + 0x77, 0x32, 0x49, 0x49, 0x50, 0x56, 0x51, 0x54, 0x36, 0x30, 0x6e, 0x4b, + 0x57, 0x56, 0x53, 0x46, 0x4a, 0x75, 0x55, 0x72, 0x6a, 0x78, 0x75, 0x66, + 0x36, 0x2f, 0x57, 0x68, 0x6b, 0x63, 0x49, 0x7a, 0x0a, 0x53, 0x64, 0x68, + 0x44, 0x59, 0x32, 0x70, 0x53, 0x53, 0x39, 0x4b, 0x50, 0x36, 0x48, 0x42, + 0x52, 0x54, 0x64, 0x47, 0x4a, 0x61, 0x58, 0x76, 0x48, 0x63, 0x50, 0x61, + 0x7a, 0x33, 0x42, 0x4a, 0x30, 0x32, 0x33, 0x74, 0x64, 0x53, 0x31, 0x62, + 0x54, 0x6c, 0x72, 0x38, 0x56, 0x64, 0x36, 0x47, 0x77, 0x39, 0x4b, 0x49, + 0x6c, 0x38, 0x71, 0x38, 0x63, 0x6b, 0x6d, 0x63, 0x59, 0x35, 0x66, 0x51, + 0x47, 0x0a, 0x42, 0x4f, 0x2b, 0x51, 0x75, 0x65, 0x51, 0x41, 0x35, 0x4e, + 0x30, 0x36, 0x74, 0x52, 0x6e, 0x2f, 0x41, 0x72, 0x72, 0x30, 0x50, 0x4f, + 0x37, 0x67, 0x69, 0x2b, 0x73, 0x33, 0x69, 0x2b, 0x7a, 0x30, 0x31, 0x36, + 0x7a, 0x79, 0x39, 0x76, 0x41, 0x39, 0x72, 0x39, 0x31, 0x31, 0x6b, 0x54, + 0x4d, 0x5a, 0x48, 0x52, 0x78, 0x41, 0x79, 0x33, 0x51, 0x6b, 0x47, 0x53, + 0x47, 0x54, 0x32, 0x52, 0x54, 0x2b, 0x0a, 0x72, 0x43, 0x70, 0x53, 0x78, + 0x34, 0x2f, 0x56, 0x42, 0x45, 0x6e, 0x6b, 0x6a, 0x57, 0x4e, 0x48, 0x69, + 0x44, 0x78, 0x70, 0x67, 0x38, 0x76, 0x2b, 0x52, 0x37, 0x30, 0x72, 0x66, + 0x6b, 0x2f, 0x46, 0x6c, 0x61, 0x34, 0x4f, 0x6e, 0x64, 0x54, 0x52, 0x51, + 0x38, 0x42, 0x6e, 0x63, 0x2b, 0x4d, 0x55, 0x43, 0x48, 0x37, 0x6c, 0x50, + 0x35, 0x39, 0x7a, 0x75, 0x44, 0x4d, 0x4b, 0x7a, 0x31, 0x30, 0x2f, 0x0a, + 0x4e, 0x49, 0x65, 0x57, 0x69, 0x75, 0x35, 0x54, 0x36, 0x43, 0x55, 0x56, + 0x41, 0x67, 0x4d, 0x42, 0x41, 0x41, 0x47, 0x6a, 0x67, 0x62, 0x49, 0x77, + 0x67, 0x61, 0x38, 0x77, 0x44, 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, 0x54, + 0x41, 0x51, 0x48, 0x2f, 0x42, 0x41, 0x55, 0x77, 0x41, 0x77, 0x45, 0x42, + 0x2f, 0x7a, 0x41, 0x4f, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x38, 0x42, + 0x41, 0x66, 0x38, 0x45, 0x0a, 0x42, 0x41, 0x4d, 0x43, 0x41, 0x51, 0x59, + 0x77, 0x62, 0x51, 0x59, 0x49, 0x4b, 0x77, 0x59, 0x42, 0x42, 0x51, 0x55, + 0x48, 0x41, 0x51, 0x77, 0x45, 0x59, 0x54, 0x42, 0x66, 0x6f, 0x56, 0x32, + 0x67, 0x57, 0x7a, 0x42, 0x5a, 0x4d, 0x46, 0x63, 0x77, 0x56, 0x52, 0x59, + 0x4a, 0x61, 0x57, 0x31, 0x68, 0x5a, 0x32, 0x55, 0x76, 0x5a, 0x32, 0x6c, + 0x6d, 0x4d, 0x43, 0x45, 0x77, 0x48, 0x7a, 0x41, 0x48, 0x0a, 0x42, 0x67, + 0x55, 0x72, 0x44, 0x67, 0x4d, 0x43, 0x47, 0x67, 0x51, 0x55, 0x6a, 0x2b, + 0x58, 0x54, 0x47, 0x6f, 0x61, 0x73, 0x6a, 0x59, 0x35, 0x72, 0x77, 0x38, + 0x2b, 0x41, 0x61, 0x74, 0x52, 0x49, 0x47, 0x43, 0x78, 0x37, 0x47, 0x53, + 0x34, 0x77, 0x4a, 0x52, 0x59, 0x6a, 0x61, 0x48, 0x52, 0x30, 0x63, 0x44, + 0x6f, 0x76, 0x4c, 0x32, 0x78, 0x76, 0x5a, 0x32, 0x38, 0x75, 0x64, 0x6d, + 0x56, 0x79, 0x0a, 0x61, 0x58, 0x4e, 0x70, 0x5a, 0x32, 0x34, 0x75, 0x59, + 0x32, 0x39, 0x74, 0x4c, 0x33, 0x5a, 0x7a, 0x62, 0x47, 0x39, 0x6e, 0x62, + 0x79, 0x35, 0x6e, 0x61, 0x57, 0x59, 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, + 0x52, 0x30, 0x4f, 0x42, 0x42, 0x59, 0x45, 0x46, 0x48, 0x2f, 0x54, 0x5a, + 0x61, 0x66, 0x43, 0x33, 0x65, 0x79, 0x37, 0x38, 0x44, 0x41, 0x4a, 0x38, + 0x30, 0x4d, 0x35, 0x2b, 0x67, 0x4b, 0x76, 0x0a, 0x4d, 0x7a, 0x45, 0x7a, + 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, + 0x44, 0x51, 0x45, 0x42, 0x42, 0x51, 0x55, 0x41, 0x41, 0x34, 0x49, 0x42, + 0x41, 0x51, 0x43, 0x54, 0x4a, 0x45, 0x6f, 0x77, 0x58, 0x32, 0x4c, 0x50, + 0x32, 0x42, 0x71, 0x59, 0x4c, 0x7a, 0x33, 0x71, 0x33, 0x4a, 0x6b, 0x74, + 0x76, 0x58, 0x66, 0x32, 0x70, 0x58, 0x6b, 0x69, 0x4f, 0x4f, 0x7a, 0x45, + 0x0a, 0x70, 0x36, 0x42, 0x34, 0x45, 0x71, 0x31, 0x69, 0x44, 0x6b, 0x56, + 0x77, 0x5a, 0x4d, 0x58, 0x6e, 0x6c, 0x32, 0x59, 0x74, 0x6d, 0x41, 0x6c, + 0x2b, 0x58, 0x36, 0x2f, 0x57, 0x7a, 0x43, 0x68, 0x6c, 0x38, 0x67, 0x47, + 0x71, 0x43, 0x42, 0x70, 0x48, 0x33, 0x76, 0x6e, 0x35, 0x66, 0x4a, 0x4a, + 0x61, 0x43, 0x47, 0x6b, 0x67, 0x44, 0x64, 0x6b, 0x2b, 0x62, 0x57, 0x34, + 0x38, 0x44, 0x57, 0x37, 0x59, 0x0a, 0x35, 0x67, 0x61, 0x52, 0x51, 0x42, + 0x69, 0x35, 0x2b, 0x4d, 0x48, 0x74, 0x33, 0x39, 0x74, 0x42, 0x71, 0x75, + 0x43, 0x57, 0x49, 0x4d, 0x6e, 0x4e, 0x5a, 0x42, 0x55, 0x34, 0x67, 0x63, + 0x6d, 0x55, 0x37, 0x71, 0x4b, 0x45, 0x4b, 0x51, 0x73, 0x54, 0x62, 0x34, + 0x37, 0x62, 0x44, 0x4e, 0x30, 0x6c, 0x41, 0x74, 0x75, 0x6b, 0x69, 0x78, + 0x6c, 0x45, 0x30, 0x6b, 0x46, 0x36, 0x42, 0x57, 0x6c, 0x4b, 0x0a, 0x57, + 0x45, 0x39, 0x67, 0x79, 0x6e, 0x36, 0x43, 0x61, 0x67, 0x73, 0x43, 0x71, + 0x69, 0x55, 0x58, 0x4f, 0x62, 0x58, 0x62, 0x66, 0x2b, 0x65, 0x45, 0x5a, + 0x53, 0x71, 0x56, 0x69, 0x72, 0x32, 0x47, 0x33, 0x6c, 0x36, 0x42, 0x46, + 0x6f, 0x4d, 0x74, 0x45, 0x4d, 0x7a, 0x65, 0x2f, 0x61, 0x69, 0x43, 0x4b, + 0x6d, 0x30, 0x6f, 0x48, 0x77, 0x30, 0x4c, 0x78, 0x4f, 0x58, 0x6e, 0x47, + 0x69, 0x59, 0x5a, 0x0a, 0x34, 0x66, 0x51, 0x52, 0x62, 0x78, 0x43, 0x31, + 0x6c, 0x66, 0x7a, 0x6e, 0x51, 0x67, 0x55, 0x79, 0x32, 0x38, 0x36, 0x64, + 0x55, 0x56, 0x34, 0x6f, 0x74, 0x70, 0x36, 0x46, 0x30, 0x31, 0x76, 0x76, + 0x70, 0x58, 0x31, 0x46, 0x51, 0x48, 0x4b, 0x4f, 0x74, 0x77, 0x35, 0x72, + 0x44, 0x67, 0x62, 0x37, 0x4d, 0x7a, 0x56, 0x49, 0x63, 0x62, 0x69, 0x64, + 0x4a, 0x34, 0x76, 0x45, 0x5a, 0x56, 0x38, 0x4e, 0x0a, 0x68, 0x6e, 0x61, + 0x63, 0x52, 0x48, 0x72, 0x32, 0x6c, 0x56, 0x7a, 0x32, 0x58, 0x54, 0x49, + 0x49, 0x4d, 0x36, 0x52, 0x55, 0x74, 0x68, 0x67, 0x2f, 0x61, 0x46, 0x7a, + 0x79, 0x51, 0x6b, 0x71, 0x46, 0x4f, 0x46, 0x53, 0x44, 0x58, 0x39, 0x48, + 0x6f, 0x4c, 0x50, 0x4b, 0x73, 0x45, 0x64, 0x61, 0x6f, 0x37, 0x57, 0x4e, + 0x71, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, + 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, + 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, + 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, + 0x20, 0x4f, 0x3d, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, + 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x0a, 0x23, 0x20, 0x53, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x43, + 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x4f, 0x3d, 0x43, 0x4f, 0x4d, 0x4f, + 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, + 0x64, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, + 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, + 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x31, 0x30, 0x34, 0x33, 0x35, 0x30, + 0x35, 0x31, 0x33, 0x36, 0x34, 0x38, 0x32, 0x34, 0x39, 0x32, 0x33, 0x32, + 0x39, 0x34, 0x31, 0x39, 0x39, 0x38, 0x35, 0x30, 0x38, 0x39, 0x38, 0x35, + 0x38, 0x33, 0x34, 0x34, 0x36, 0x34, 0x35, 0x37, 0x33, 0x0a, 0x23, 0x20, + 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, + 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x35, 0x63, 0x3a, 0x34, 0x38, 0x3a, 0x64, + 0x63, 0x3a, 0x66, 0x37, 0x3a, 0x34, 0x32, 0x3a, 0x37, 0x32, 0x3a, 0x65, + 0x63, 0x3a, 0x35, 0x36, 0x3a, 0x39, 0x34, 0x3a, 0x36, 0x64, 0x3a, 0x31, + 0x63, 0x3a, 0x63, 0x63, 0x3a, 0x37, 0x31, 0x3a, 0x33, 0x35, 0x3a, 0x38, + 0x30, 0x3a, 0x37, 0x35, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, + 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, + 0x20, 0x36, 0x36, 0x3a, 0x33, 0x31, 0x3a, 0x62, 0x66, 0x3a, 0x39, 0x65, + 0x3a, 0x66, 0x37, 0x3a, 0x34, 0x66, 0x3a, 0x39, 0x65, 0x3a, 0x62, 0x36, + 0x3a, 0x63, 0x39, 0x3a, 0x64, 0x35, 0x3a, 0x61, 0x36, 0x3a, 0x30, 0x63, + 0x3a, 0x62, 0x61, 0x3a, 0x36, 0x61, 0x3a, 0x62, 0x65, 0x3a, 0x64, 0x31, + 0x3a, 0x66, 0x37, 0x3a, 0x62, 0x64, 0x3a, 0x65, 0x66, 0x3a, 0x37, 0x62, + 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, + 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x30, + 0x63, 0x3a, 0x32, 0x63, 0x3a, 0x64, 0x36, 0x3a, 0x33, 0x64, 0x3a, 0x66, + 0x37, 0x3a, 0x38, 0x30, 0x3a, 0x36, 0x66, 0x3a, 0x61, 0x33, 0x3a, 0x39, + 0x39, 0x3a, 0x65, 0x64, 0x3a, 0x65, 0x38, 0x3a, 0x30, 0x39, 0x3a, 0x31, + 0x31, 0x3a, 0x36, 0x62, 0x3a, 0x35, 0x37, 0x3a, 0x35, 0x62, 0x3a, 0x66, + 0x38, 0x3a, 0x37, 0x39, 0x3a, 0x38, 0x39, 0x3a, 0x66, 0x30, 0x3a, 0x36, + 0x35, 0x3a, 0x31, 0x38, 0x3a, 0x66, 0x39, 0x3a, 0x38, 0x30, 0x3a, 0x38, + 0x63, 0x3a, 0x38, 0x36, 0x3a, 0x30, 0x35, 0x3a, 0x30, 0x33, 0x3a, 0x31, + 0x37, 0x3a, 0x38, 0x62, 0x3a, 0x61, 0x66, 0x3a, 0x36, 0x36, 0x0a, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, + 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x45, 0x48, 0x54, 0x43, 0x43, 0x41, + 0x77, 0x57, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x51, 0x54, + 0x6f, 0x45, 0x74, 0x69, 0x6f, 0x4a, 0x6c, 0x34, 0x41, 0x73, 0x43, 0x37, + 0x6a, 0x34, 0x31, 0x41, 0x6b, 0x62, 0x6c, 0x50, 0x54, 0x41, 0x4e, 0x42, + 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, + 0x51, 0x55, 0x46, 0x41, 0x44, 0x43, 0x42, 0x0a, 0x67, 0x54, 0x45, 0x4c, + 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, + 0x52, 0x30, 0x49, 0x78, 0x47, 0x7a, 0x41, 0x5a, 0x42, 0x67, 0x4e, 0x56, + 0x42, 0x41, 0x67, 0x54, 0x45, 0x6b, 0x64, 0x79, 0x5a, 0x57, 0x46, 0x30, + 0x5a, 0x58, 0x49, 0x67, 0x54, 0x57, 0x46, 0x75, 0x59, 0x32, 0x68, 0x6c, + 0x63, 0x33, 0x52, 0x6c, 0x63, 0x6a, 0x45, 0x51, 0x4d, 0x41, 0x34, 0x47, + 0x0a, 0x41, 0x31, 0x55, 0x45, 0x42, 0x78, 0x4d, 0x48, 0x55, 0x32, 0x46, + 0x73, 0x5a, 0x6d, 0x39, 0x79, 0x5a, 0x44, 0x45, 0x61, 0x4d, 0x42, 0x67, + 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x52, 0x51, 0x30, 0x39, + 0x4e, 0x54, 0x30, 0x52, 0x50, 0x49, 0x45, 0x4e, 0x42, 0x49, 0x45, 0x78, + 0x70, 0x62, 0x57, 0x6c, 0x30, 0x5a, 0x57, 0x51, 0x78, 0x4a, 0x7a, 0x41, + 0x6c, 0x42, 0x67, 0x4e, 0x56, 0x0a, 0x42, 0x41, 0x4d, 0x54, 0x48, 0x6b, + 0x4e, 0x50, 0x54, 0x55, 0x39, 0x45, 0x54, 0x79, 0x42, 0x44, 0x5a, 0x58, + 0x4a, 0x30, 0x61, 0x57, 0x5a, 0x70, 0x59, 0x32, 0x46, 0x30, 0x61, 0x57, + 0x39, 0x75, 0x49, 0x45, 0x46, 0x31, 0x64, 0x47, 0x68, 0x76, 0x63, 0x6d, + 0x6c, 0x30, 0x65, 0x54, 0x41, 0x65, 0x46, 0x77, 0x30, 0x77, 0x4e, 0x6a, + 0x45, 0x79, 0x4d, 0x44, 0x45, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x0a, 0x4d, + 0x44, 0x42, 0x61, 0x46, 0x77, 0x30, 0x79, 0x4f, 0x54, 0x45, 0x79, 0x4d, + 0x7a, 0x45, 0x79, 0x4d, 0x7a, 0x55, 0x35, 0x4e, 0x54, 0x6c, 0x61, 0x4d, + 0x49, 0x47, 0x42, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, + 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x48, 0x51, 0x6a, 0x45, 0x62, 0x4d, + 0x42, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x42, 0x4d, 0x53, 0x52, + 0x33, 0x4a, 0x6c, 0x0a, 0x59, 0x58, 0x52, 0x6c, 0x63, 0x69, 0x42, 0x4e, + 0x59, 0x57, 0x35, 0x6a, 0x61, 0x47, 0x56, 0x7a, 0x64, 0x47, 0x56, 0x79, + 0x4d, 0x52, 0x41, 0x77, 0x44, 0x67, 0x59, 0x44, 0x56, 0x51, 0x51, 0x48, + 0x45, 0x77, 0x64, 0x54, 0x59, 0x57, 0x78, 0x6d, 0x62, 0x33, 0x4a, 0x6b, + 0x4d, 0x52, 0x6f, 0x77, 0x47, 0x41, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4b, + 0x45, 0x78, 0x46, 0x44, 0x54, 0x30, 0x31, 0x50, 0x0a, 0x52, 0x45, 0x38, + 0x67, 0x51, 0x30, 0x45, 0x67, 0x54, 0x47, 0x6c, 0x74, 0x61, 0x58, 0x52, + 0x6c, 0x5a, 0x44, 0x45, 0x6e, 0x4d, 0x43, 0x55, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x41, 0x78, 0x4d, 0x65, 0x51, 0x30, 0x39, 0x4e, 0x54, 0x30, 0x52, + 0x50, 0x49, 0x45, 0x4e, 0x6c, 0x63, 0x6e, 0x52, 0x70, 0x5a, 0x6d, 0x6c, + 0x6a, 0x59, 0x58, 0x52, 0x70, 0x62, 0x32, 0x34, 0x67, 0x51, 0x58, 0x56, + 0x30, 0x0a, 0x61, 0x47, 0x39, 0x79, 0x61, 0x58, 0x52, 0x35, 0x4d, 0x49, + 0x49, 0x42, 0x49, 0x6a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, + 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x45, 0x46, 0x41, 0x41, + 0x4f, 0x43, 0x41, 0x51, 0x38, 0x41, 0x4d, 0x49, 0x49, 0x42, 0x43, 0x67, + 0x4b, 0x43, 0x41, 0x51, 0x45, 0x41, 0x30, 0x45, 0x43, 0x4c, 0x69, 0x33, + 0x4c, 0x6a, 0x6b, 0x52, 0x76, 0x33, 0x0a, 0x55, 0x63, 0x45, 0x62, 0x56, + 0x41, 0x53, 0x59, 0x30, 0x36, 0x6d, 0x2f, 0x77, 0x65, 0x61, 0x4b, 0x58, + 0x54, 0x75, 0x48, 0x2b, 0x37, 0x75, 0x49, 0x7a, 0x67, 0x33, 0x6a, 0x4c, + 0x7a, 0x38, 0x47, 0x6c, 0x76, 0x43, 0x69, 0x4b, 0x56, 0x43, 0x5a, 0x72, + 0x74, 0x73, 0x37, 0x6f, 0x56, 0x65, 0x77, 0x64, 0x46, 0x46, 0x78, 0x7a, + 0x65, 0x31, 0x43, 0x6b, 0x55, 0x31, 0x42, 0x2f, 0x71, 0x6e, 0x49, 0x0a, + 0x32, 0x47, 0x71, 0x47, 0x64, 0x30, 0x53, 0x37, 0x57, 0x57, 0x61, 0x58, + 0x55, 0x46, 0x36, 0x30, 0x31, 0x43, 0x78, 0x77, 0x52, 0x4d, 0x2f, 0x61, + 0x4e, 0x35, 0x56, 0x43, 0x61, 0x54, 0x77, 0x77, 0x78, 0x48, 0x47, 0x7a, + 0x55, 0x76, 0x41, 0x68, 0x54, 0x61, 0x48, 0x59, 0x75, 0x6a, 0x6c, 0x38, + 0x48, 0x4a, 0x36, 0x6a, 0x4a, 0x4a, 0x33, 0x79, 0x67, 0x78, 0x61, 0x59, + 0x71, 0x68, 0x5a, 0x38, 0x0a, 0x51, 0x35, 0x73, 0x56, 0x57, 0x37, 0x65, + 0x75, 0x4e, 0x4a, 0x48, 0x2b, 0x31, 0x47, 0x49, 0x6d, 0x47, 0x45, 0x61, + 0x61, 0x50, 0x2b, 0x76, 0x42, 0x2b, 0x66, 0x47, 0x51, 0x56, 0x2b, 0x75, + 0x73, 0x65, 0x67, 0x32, 0x4c, 0x32, 0x33, 0x49, 0x77, 0x61, 0x6d, 0x62, + 0x56, 0x34, 0x45, 0x61, 0x6a, 0x63, 0x4e, 0x78, 0x6f, 0x32, 0x66, 0x38, + 0x45, 0x53, 0x49, 0x6c, 0x33, 0x33, 0x72, 0x58, 0x70, 0x0a, 0x2b, 0x32, + 0x64, 0x74, 0x51, 0x65, 0x6d, 0x38, 0x4f, 0x62, 0x30, 0x79, 0x32, 0x57, + 0x49, 0x43, 0x38, 0x62, 0x47, 0x6f, 0x50, 0x57, 0x34, 0x33, 0x6e, 0x4f, + 0x49, 0x76, 0x34, 0x74, 0x4f, 0x69, 0x4a, 0x6f, 0x76, 0x47, 0x75, 0x46, + 0x56, 0x44, 0x69, 0x4f, 0x45, 0x6a, 0x50, 0x71, 0x58, 0x53, 0x4a, 0x44, + 0x6c, 0x71, 0x52, 0x36, 0x73, 0x41, 0x31, 0x4b, 0x47, 0x7a, 0x71, 0x53, + 0x58, 0x2b, 0x0a, 0x44, 0x54, 0x2b, 0x6e, 0x48, 0x62, 0x72, 0x54, 0x55, + 0x63, 0x45, 0x4c, 0x70, 0x4e, 0x71, 0x73, 0x4f, 0x4f, 0x39, 0x56, 0x55, + 0x43, 0x51, 0x46, 0x5a, 0x55, 0x61, 0x54, 0x4e, 0x45, 0x38, 0x74, 0x6a, + 0x61, 0x33, 0x47, 0x31, 0x43, 0x45, 0x5a, 0x30, 0x6f, 0x37, 0x4b, 0x42, + 0x57, 0x46, 0x78, 0x42, 0x33, 0x4e, 0x48, 0x35, 0x59, 0x6f, 0x5a, 0x45, + 0x72, 0x30, 0x45, 0x54, 0x63, 0x35, 0x4f, 0x0a, 0x6e, 0x4b, 0x56, 0x49, + 0x72, 0x4c, 0x73, 0x6d, 0x39, 0x77, 0x49, 0x44, 0x41, 0x51, 0x41, 0x42, + 0x6f, 0x34, 0x47, 0x4f, 0x4d, 0x49, 0x47, 0x4c, 0x4d, 0x42, 0x30, 0x47, + 0x41, 0x31, 0x55, 0x64, 0x44, 0x67, 0x51, 0x57, 0x42, 0x42, 0x51, 0x4c, + 0x57, 0x4f, 0x57, 0x4c, 0x78, 0x6b, 0x77, 0x56, 0x4e, 0x36, 0x52, 0x41, + 0x71, 0x54, 0x43, 0x70, 0x49, 0x62, 0x35, 0x48, 0x4e, 0x6c, 0x70, 0x57, + 0x0a, 0x2f, 0x7a, 0x41, 0x4f, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x38, + 0x42, 0x41, 0x66, 0x38, 0x45, 0x42, 0x41, 0x4d, 0x43, 0x41, 0x51, 0x59, + 0x77, 0x44, 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, 0x54, 0x41, 0x51, 0x48, + 0x2f, 0x42, 0x41, 0x55, 0x77, 0x41, 0x77, 0x45, 0x42, 0x2f, 0x7a, 0x42, + 0x4a, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x52, 0x38, 0x45, 0x51, 0x6a, 0x42, + 0x41, 0x4d, 0x44, 0x36, 0x67, 0x0a, 0x50, 0x4b, 0x41, 0x36, 0x68, 0x6a, + 0x68, 0x6f, 0x64, 0x48, 0x52, 0x77, 0x4f, 0x69, 0x38, 0x76, 0x59, 0x33, + 0x4a, 0x73, 0x4c, 0x6d, 0x4e, 0x76, 0x62, 0x57, 0x39, 0x6b, 0x62, 0x32, + 0x4e, 0x68, 0x4c, 0x6d, 0x4e, 0x76, 0x62, 0x53, 0x39, 0x44, 0x54, 0x30, + 0x31, 0x50, 0x52, 0x45, 0x39, 0x44, 0x5a, 0x58, 0x4a, 0x30, 0x61, 0x57, + 0x5a, 0x70, 0x59, 0x32, 0x46, 0x30, 0x61, 0x57, 0x39, 0x75, 0x0a, 0x51, + 0x58, 0x56, 0x30, 0x61, 0x47, 0x39, 0x79, 0x61, 0x58, 0x52, 0x35, 0x4c, + 0x6d, 0x4e, 0x79, 0x62, 0x44, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, + 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, + 0x41, 0x4f, 0x43, 0x41, 0x51, 0x45, 0x41, 0x50, 0x70, 0x69, 0x65, 0x6d, + 0x2f, 0x59, 0x62, 0x36, 0x64, 0x63, 0x35, 0x74, 0x33, 0x69, 0x75, 0x48, + 0x58, 0x49, 0x59, 0x0a, 0x53, 0x64, 0x4f, 0x48, 0x35, 0x45, 0x4f, 0x43, + 0x36, 0x7a, 0x2f, 0x4a, 0x71, 0x76, 0x57, 0x6f, 0x74, 0x65, 0x39, 0x56, + 0x66, 0x43, 0x46, 0x53, 0x5a, 0x66, 0x6e, 0x56, 0x44, 0x65, 0x46, 0x73, + 0x39, 0x44, 0x36, 0x4d, 0x6b, 0x33, 0x4f, 0x52, 0x4c, 0x67, 0x4c, 0x45, + 0x54, 0x67, 0x64, 0x78, 0x62, 0x38, 0x43, 0x50, 0x4f, 0x47, 0x45, 0x49, + 0x71, 0x42, 0x36, 0x42, 0x43, 0x73, 0x41, 0x76, 0x0a, 0x49, 0x43, 0x39, + 0x42, 0x69, 0x35, 0x48, 0x63, 0x53, 0x45, 0x57, 0x38, 0x38, 0x63, 0x62, + 0x65, 0x75, 0x6e, 0x5a, 0x72, 0x4d, 0x38, 0x67, 0x41, 0x4c, 0x54, 0x46, + 0x47, 0x54, 0x4f, 0x33, 0x6e, 0x6e, 0x63, 0x2b, 0x49, 0x6c, 0x50, 0x38, + 0x7a, 0x77, 0x46, 0x62, 0x6f, 0x4a, 0x49, 0x59, 0x6d, 0x75, 0x4e, 0x67, + 0x34, 0x4f, 0x4e, 0x38, 0x71, 0x61, 0x39, 0x30, 0x53, 0x7a, 0x4d, 0x63, + 0x2f, 0x0a, 0x52, 0x78, 0x64, 0x4d, 0x6f, 0x73, 0x49, 0x47, 0x6c, 0x67, + 0x6e, 0x57, 0x32, 0x2f, 0x34, 0x2f, 0x50, 0x45, 0x5a, 0x42, 0x33, 0x31, + 0x6a, 0x69, 0x56, 0x67, 0x38, 0x38, 0x4f, 0x38, 0x45, 0x63, 0x6b, 0x7a, + 0x58, 0x5a, 0x4f, 0x46, 0x4b, 0x73, 0x37, 0x73, 0x6a, 0x73, 0x4c, 0x6a, + 0x42, 0x4f, 0x6c, 0x44, 0x57, 0x30, 0x4a, 0x42, 0x39, 0x4c, 0x65, 0x47, + 0x6e, 0x61, 0x38, 0x67, 0x49, 0x34, 0x0a, 0x7a, 0x4a, 0x56, 0x53, 0x6b, + 0x2f, 0x42, 0x77, 0x4a, 0x56, 0x6d, 0x63, 0x49, 0x47, 0x66, 0x45, 0x37, + 0x76, 0x6d, 0x4c, 0x56, 0x32, 0x48, 0x30, 0x6b, 0x6e, 0x5a, 0x39, 0x50, + 0x34, 0x53, 0x4e, 0x56, 0x62, 0x66, 0x6f, 0x35, 0x61, 0x7a, 0x56, 0x38, + 0x66, 0x55, 0x5a, 0x56, 0x71, 0x5a, 0x61, 0x2b, 0x35, 0x41, 0x63, 0x72, + 0x35, 0x50, 0x72, 0x35, 0x52, 0x7a, 0x55, 0x5a, 0x35, 0x64, 0x64, 0x0a, + 0x42, 0x41, 0x36, 0x2b, 0x43, 0x34, 0x4f, 0x6d, 0x46, 0x34, 0x4f, 0x35, + 0x4d, 0x42, 0x4b, 0x67, 0x78, 0x54, 0x4d, 0x56, 0x42, 0x62, 0x6b, 0x4e, + 0x2b, 0x38, 0x63, 0x46, 0x64, 0x75, 0x50, 0x59, 0x53, 0x6f, 0x33, 0x38, + 0x4e, 0x42, 0x65, 0x6a, 0x78, 0x69, 0x45, 0x6f, 0x76, 0x6a, 0x42, 0x46, + 0x4d, 0x52, 0x37, 0x48, 0x65, 0x4c, 0x35, 0x59, 0x59, 0x54, 0x69, 0x73, + 0x4f, 0x2b, 0x49, 0x42, 0x0a, 0x5a, 0x51, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, + 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, + 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, + 0x4e, 0x3d, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, + 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x4f, 0x3d, 0x4e, 0x65, 0x74, 0x77, + 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x20, 0x4c, 0x2e, 0x4c, 0x2e, 0x43, 0x2e, 0x0a, 0x23, 0x20, 0x53, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x4e, + 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x74, 0x79, 0x20, 0x4f, 0x3d, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, + 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x4c, + 0x2e, 0x4c, 0x2e, 0x43, 0x2e, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, + 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x0a, 0x23, 0x20, 0x53, + 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x31, 0x31, 0x36, 0x36, 0x39, + 0x37, 0x39, 0x31, 0x35, 0x31, 0x35, 0x32, 0x39, 0x33, 0x37, 0x34, 0x39, + 0x37, 0x34, 0x39, 0x30, 0x34, 0x33, 0x37, 0x35, 0x35, 0x36, 0x33, 0x38, + 0x36, 0x38, 0x31, 0x32, 0x34, 0x38, 0x37, 0x39, 0x30, 0x34, 0x0a, 0x23, + 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, + 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x64, 0x33, 0x3a, 0x66, 0x33, 0x3a, + 0x61, 0x36, 0x3a, 0x31, 0x36, 0x3a, 0x63, 0x30, 0x3a, 0x66, 0x61, 0x3a, + 0x36, 0x62, 0x3a, 0x31, 0x64, 0x3a, 0x35, 0x39, 0x3a, 0x62, 0x31, 0x3a, + 0x32, 0x64, 0x3a, 0x39, 0x36, 0x3a, 0x34, 0x64, 0x3a, 0x30, 0x65, 0x3a, + 0x31, 0x31, 0x3a, 0x32, 0x65, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, + 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, + 0x3a, 0x20, 0x37, 0x34, 0x3a, 0x66, 0x38, 0x3a, 0x61, 0x33, 0x3a, 0x63, + 0x33, 0x3a, 0x65, 0x66, 0x3a, 0x65, 0x37, 0x3a, 0x62, 0x33, 0x3a, 0x39, + 0x30, 0x3a, 0x30, 0x36, 0x3a, 0x34, 0x62, 0x3a, 0x38, 0x33, 0x3a, 0x39, + 0x30, 0x3a, 0x33, 0x63, 0x3a, 0x32, 0x31, 0x3a, 0x36, 0x34, 0x3a, 0x36, + 0x30, 0x3a, 0x32, 0x30, 0x3a, 0x65, 0x35, 0x3a, 0x64, 0x66, 0x3a, 0x63, + 0x65, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x31, 0x35, 0x3a, 0x66, 0x30, 0x3a, 0x62, 0x61, 0x3a, 0x30, 0x30, 0x3a, + 0x61, 0x33, 0x3a, 0x61, 0x63, 0x3a, 0x37, 0x61, 0x3a, 0x66, 0x33, 0x3a, + 0x61, 0x63, 0x3a, 0x38, 0x38, 0x3a, 0x34, 0x63, 0x3a, 0x30, 0x37, 0x3a, + 0x32, 0x62, 0x3a, 0x31, 0x30, 0x3a, 0x31, 0x31, 0x3a, 0x61, 0x30, 0x3a, + 0x37, 0x37, 0x3a, 0x62, 0x64, 0x3a, 0x37, 0x37, 0x3a, 0x63, 0x30, 0x3a, + 0x39, 0x37, 0x3a, 0x66, 0x34, 0x3a, 0x30, 0x31, 0x3a, 0x36, 0x34, 0x3a, + 0x62, 0x32, 0x3a, 0x66, 0x38, 0x3a, 0x35, 0x39, 0x3a, 0x38, 0x61, 0x3a, + 0x62, 0x64, 0x3a, 0x38, 0x33, 0x3a, 0x38, 0x36, 0x3a, 0x30, 0x63, 0x0a, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, + 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x44, 0x35, 0x6a, 0x43, 0x43, + 0x41, 0x73, 0x36, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x51, + 0x56, 0x38, 0x73, 0x7a, 0x62, 0x38, 0x4a, 0x63, 0x46, 0x75, 0x5a, 0x48, + 0x46, 0x68, 0x66, 0x6a, 0x6b, 0x44, 0x46, 0x6f, 0x34, 0x44, 0x41, 0x4e, + 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, + 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x42, 0x69, 0x0a, 0x4d, 0x51, 0x73, + 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, + 0x56, 0x55, 0x7a, 0x45, 0x68, 0x4d, 0x42, 0x38, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x43, 0x68, 0x4d, 0x59, 0x54, 0x6d, 0x56, 0x30, 0x64, 0x32, 0x39, + 0x79, 0x61, 0x79, 0x42, 0x54, 0x62, 0x32, 0x78, 0x31, 0x64, 0x47, 0x6c, + 0x76, 0x62, 0x6e, 0x4d, 0x67, 0x54, 0x43, 0x35, 0x4d, 0x4c, 0x6b, 0x4d, + 0x75, 0x0a, 0x4d, 0x54, 0x41, 0x77, 0x4c, 0x67, 0x59, 0x44, 0x56, 0x51, + 0x51, 0x44, 0x45, 0x79, 0x64, 0x4f, 0x5a, 0x58, 0x52, 0x33, 0x62, 0x33, + 0x4a, 0x72, 0x49, 0x46, 0x4e, 0x76, 0x62, 0x48, 0x56, 0x30, 0x61, 0x57, + 0x39, 0x75, 0x63, 0x79, 0x42, 0x44, 0x5a, 0x58, 0x4a, 0x30, 0x61, 0x57, + 0x5a, 0x70, 0x59, 0x32, 0x46, 0x30, 0x5a, 0x53, 0x42, 0x42, 0x64, 0x58, + 0x52, 0x6f, 0x62, 0x33, 0x4a, 0x70, 0x0a, 0x64, 0x48, 0x6b, 0x77, 0x48, + 0x68, 0x63, 0x4e, 0x4d, 0x44, 0x59, 0x78, 0x4d, 0x6a, 0x41, 0x78, 0x4d, + 0x44, 0x41, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x57, 0x68, 0x63, 0x4e, 0x4d, + 0x6a, 0x6b, 0x78, 0x4d, 0x6a, 0x4d, 0x78, 0x4d, 0x6a, 0x4d, 0x31, 0x4f, + 0x54, 0x55, 0x35, 0x57, 0x6a, 0x42, 0x69, 0x4d, 0x51, 0x73, 0x77, 0x43, + 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, 0x0a, + 0x55, 0x7a, 0x45, 0x68, 0x4d, 0x42, 0x38, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x43, 0x68, 0x4d, 0x59, 0x54, 0x6d, 0x56, 0x30, 0x64, 0x32, 0x39, 0x79, + 0x61, 0x79, 0x42, 0x54, 0x62, 0x32, 0x78, 0x31, 0x64, 0x47, 0x6c, 0x76, + 0x62, 0x6e, 0x4d, 0x67, 0x54, 0x43, 0x35, 0x4d, 0x4c, 0x6b, 0x4d, 0x75, + 0x4d, 0x54, 0x41, 0x77, 0x4c, 0x67, 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, + 0x45, 0x79, 0x64, 0x4f, 0x0a, 0x5a, 0x58, 0x52, 0x33, 0x62, 0x33, 0x4a, + 0x72, 0x49, 0x46, 0x4e, 0x76, 0x62, 0x48, 0x56, 0x30, 0x61, 0x57, 0x39, + 0x75, 0x63, 0x79, 0x42, 0x44, 0x5a, 0x58, 0x4a, 0x30, 0x61, 0x57, 0x5a, + 0x70, 0x59, 0x32, 0x46, 0x30, 0x5a, 0x53, 0x42, 0x42, 0x64, 0x58, 0x52, + 0x6f, 0x62, 0x33, 0x4a, 0x70, 0x64, 0x48, 0x6b, 0x77, 0x67, 0x67, 0x45, + 0x69, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x0a, 0x53, 0x49, + 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x41, 0x51, 0x55, 0x41, 0x41, 0x34, + 0x49, 0x42, 0x44, 0x77, 0x41, 0x77, 0x67, 0x67, 0x45, 0x4b, 0x41, 0x6f, + 0x49, 0x42, 0x41, 0x51, 0x44, 0x6b, 0x76, 0x48, 0x36, 0x53, 0x4d, 0x47, + 0x33, 0x47, 0x32, 0x49, 0x34, 0x72, 0x43, 0x37, 0x78, 0x47, 0x7a, 0x75, + 0x41, 0x6e, 0x6c, 0x74, 0x37, 0x65, 0x2b, 0x66, 0x6f, 0x53, 0x30, 0x7a, + 0x77, 0x7a, 0x0a, 0x63, 0x37, 0x4d, 0x45, 0x4c, 0x37, 0x78, 0x78, 0x6a, + 0x4f, 0x57, 0x66, 0x74, 0x69, 0x4a, 0x67, 0x50, 0x6c, 0x39, 0x64, 0x7a, + 0x67, 0x6e, 0x2f, 0x67, 0x67, 0x77, 0x62, 0x6d, 0x6c, 0x46, 0x51, 0x47, + 0x69, 0x61, 0x4a, 0x33, 0x64, 0x56, 0x68, 0x58, 0x52, 0x6e, 0x63, 0x45, + 0x67, 0x38, 0x74, 0x43, 0x71, 0x4a, 0x44, 0x58, 0x52, 0x66, 0x51, 0x4e, + 0x4a, 0x49, 0x67, 0x36, 0x6e, 0x50, 0x50, 0x0a, 0x4f, 0x43, 0x77, 0x47, + 0x4a, 0x67, 0x6c, 0x36, 0x63, 0x76, 0x66, 0x36, 0x55, 0x44, 0x4c, 0x34, + 0x77, 0x70, 0x50, 0x54, 0x61, 0x61, 0x49, 0x6a, 0x7a, 0x6b, 0x47, 0x78, + 0x7a, 0x4f, 0x54, 0x56, 0x48, 0x7a, 0x62, 0x52, 0x69, 0x6a, 0x72, 0x34, + 0x6a, 0x47, 0x50, 0x69, 0x46, 0x46, 0x6c, 0x70, 0x37, 0x51, 0x33, 0x54, + 0x66, 0x32, 0x76, 0x6f, 0x75, 0x41, 0x50, 0x6c, 0x54, 0x32, 0x72, 0x6c, + 0x0a, 0x6d, 0x47, 0x4e, 0x70, 0x53, 0x41, 0x57, 0x2b, 0x4c, 0x76, 0x38, + 0x7a, 0x74, 0x75, 0x6d, 0x58, 0x57, 0x57, 0x6e, 0x34, 0x5a, 0x78, 0x6d, + 0x75, 0x6b, 0x32, 0x47, 0x57, 0x52, 0x42, 0x58, 0x54, 0x63, 0x72, 0x41, + 0x2f, 0x76, 0x47, 0x70, 0x39, 0x37, 0x45, 0x68, 0x2f, 0x6a, 0x63, 0x4f, + 0x72, 0x71, 0x6e, 0x45, 0x72, 0x55, 0x32, 0x6c, 0x42, 0x55, 0x7a, 0x53, + 0x31, 0x73, 0x4c, 0x6e, 0x46, 0x0a, 0x42, 0x67, 0x72, 0x45, 0x73, 0x45, + 0x58, 0x31, 0x51, 0x56, 0x31, 0x75, 0x69, 0x55, 0x56, 0x37, 0x50, 0x54, + 0x73, 0x6d, 0x6a, 0x48, 0x54, 0x43, 0x35, 0x64, 0x4c, 0x52, 0x66, 0x62, + 0x49, 0x52, 0x31, 0x50, 0x74, 0x59, 0x4d, 0x69, 0x4b, 0x61, 0x67, 0x4d, + 0x6e, 0x63, 0x2f, 0x51, 0x7a, 0x70, 0x66, 0x31, 0x34, 0x44, 0x6c, 0x38, + 0x34, 0x37, 0x41, 0x42, 0x53, 0x48, 0x4a, 0x33, 0x41, 0x34, 0x0a, 0x71, + 0x59, 0x35, 0x75, 0x73, 0x79, 0x64, 0x32, 0x6d, 0x46, 0x48, 0x67, 0x42, + 0x65, 0x4d, 0x68, 0x71, 0x78, 0x72, 0x56, 0x68, 0x53, 0x49, 0x38, 0x4b, + 0x62, 0x57, 0x61, 0x46, 0x73, 0x57, 0x41, 0x71, 0x50, 0x53, 0x37, 0x61, + 0x7a, 0x43, 0x50, 0x4c, 0x30, 0x59, 0x43, 0x6f, 0x72, 0x45, 0x4d, 0x49, + 0x75, 0x44, 0x54, 0x41, 0x67, 0x4d, 0x42, 0x41, 0x41, 0x47, 0x6a, 0x67, + 0x5a, 0x63, 0x77, 0x0a, 0x67, 0x5a, 0x51, 0x77, 0x48, 0x51, 0x59, 0x44, + 0x56, 0x52, 0x30, 0x4f, 0x42, 0x42, 0x59, 0x45, 0x46, 0x43, 0x45, 0x77, + 0x79, 0x66, 0x73, 0x41, 0x31, 0x30, 0x36, 0x59, 0x32, 0x6f, 0x65, 0x71, + 0x4b, 0x74, 0x43, 0x6e, 0x4c, 0x72, 0x46, 0x41, 0x4d, 0x61, 0x64, 0x4d, + 0x4d, 0x41, 0x34, 0x47, 0x41, 0x31, 0x55, 0x64, 0x44, 0x77, 0x45, 0x42, + 0x2f, 0x77, 0x51, 0x45, 0x41, 0x77, 0x49, 0x42, 0x0a, 0x42, 0x6a, 0x41, + 0x50, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x52, 0x4d, 0x42, 0x41, 0x66, 0x38, + 0x45, 0x42, 0x54, 0x41, 0x44, 0x41, 0x51, 0x48, 0x2f, 0x4d, 0x46, 0x49, + 0x47, 0x41, 0x31, 0x55, 0x64, 0x48, 0x77, 0x52, 0x4c, 0x4d, 0x45, 0x6b, + 0x77, 0x52, 0x36, 0x42, 0x46, 0x6f, 0x45, 0x4f, 0x47, 0x51, 0x57, 0x68, + 0x30, 0x64, 0x48, 0x41, 0x36, 0x4c, 0x79, 0x39, 0x6a, 0x63, 0x6d, 0x77, + 0x75, 0x0a, 0x62, 0x6d, 0x56, 0x30, 0x63, 0x32, 0x39, 0x73, 0x63, 0x33, + 0x4e, 0x73, 0x4c, 0x6d, 0x4e, 0x76, 0x62, 0x53, 0x39, 0x4f, 0x5a, 0x58, + 0x52, 0x33, 0x62, 0x33, 0x4a, 0x72, 0x55, 0x32, 0x39, 0x73, 0x64, 0x58, + 0x52, 0x70, 0x62, 0x32, 0x35, 0x7a, 0x51, 0x32, 0x56, 0x79, 0x64, 0x47, + 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, 0x56, 0x42, 0x64, 0x58, + 0x52, 0x6f, 0x62, 0x33, 0x4a, 0x70, 0x0a, 0x64, 0x48, 0x6b, 0x75, 0x59, + 0x33, 0x4a, 0x73, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, + 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x42, 0x51, 0x55, 0x41, 0x41, + 0x34, 0x49, 0x42, 0x41, 0x51, 0x43, 0x37, 0x72, 0x6b, 0x76, 0x6e, 0x74, + 0x31, 0x66, 0x72, 0x66, 0x36, 0x6f, 0x74, 0x74, 0x33, 0x4e, 0x48, 0x68, + 0x57, 0x72, 0x42, 0x35, 0x4b, 0x55, 0x64, 0x35, 0x4f, 0x63, 0x38, 0x0a, + 0x36, 0x66, 0x52, 0x5a, 0x5a, 0x58, 0x65, 0x31, 0x65, 0x6c, 0x74, 0x61, + 0x6a, 0x53, 0x55, 0x32, 0x34, 0x48, 0x71, 0x58, 0x4c, 0x6a, 0x6a, 0x41, + 0x56, 0x32, 0x43, 0x44, 0x6d, 0x41, 0x61, 0x44, 0x6e, 0x37, 0x6c, 0x32, + 0x65, 0x6d, 0x35, 0x51, 0x34, 0x4c, 0x71, 0x49, 0x4c, 0x50, 0x78, 0x46, + 0x7a, 0x42, 0x69, 0x77, 0x6d, 0x5a, 0x56, 0x52, 0x44, 0x75, 0x77, 0x64, + 0x75, 0x49, 0x6a, 0x2f, 0x0a, 0x68, 0x31, 0x41, 0x63, 0x67, 0x73, 0x4c, + 0x6a, 0x34, 0x44, 0x4b, 0x41, 0x76, 0x36, 0x41, 0x4c, 0x52, 0x38, 0x6a, + 0x44, 0x4d, 0x65, 0x2b, 0x5a, 0x5a, 0x7a, 0x4b, 0x41, 0x54, 0x78, 0x63, + 0x68, 0x65, 0x51, 0x78, 0x70, 0x58, 0x4e, 0x35, 0x65, 0x4e, 0x4b, 0x34, + 0x43, 0x74, 0x53, 0x62, 0x71, 0x55, 0x4e, 0x39, 0x2f, 0x47, 0x47, 0x55, + 0x73, 0x79, 0x66, 0x4a, 0x6a, 0x34, 0x61, 0x6b, 0x48, 0x0a, 0x2f, 0x6e, + 0x78, 0x78, 0x48, 0x32, 0x73, 0x7a, 0x4a, 0x47, 0x6f, 0x65, 0x42, 0x66, + 0x63, 0x46, 0x61, 0x4d, 0x42, 0x71, 0x45, 0x73, 0x73, 0x75, 0x58, 0x6d, + 0x48, 0x4c, 0x72, 0x69, 0x6a, 0x54, 0x66, 0x73, 0x4b, 0x30, 0x5a, 0x70, + 0x45, 0x6d, 0x58, 0x7a, 0x77, 0x75, 0x4a, 0x46, 0x2f, 0x4c, 0x57, 0x41, + 0x2f, 0x72, 0x4b, 0x4f, 0x79, 0x76, 0x45, 0x5a, 0x62, 0x7a, 0x33, 0x48, + 0x74, 0x76, 0x0a, 0x77, 0x4b, 0x65, 0x49, 0x38, 0x6c, 0x4e, 0x33, 0x73, + 0x32, 0x42, 0x65, 0x72, 0x71, 0x34, 0x6f, 0x32, 0x6a, 0x55, 0x73, 0x62, + 0x7a, 0x52, 0x46, 0x30, 0x79, 0x62, 0x68, 0x33, 0x75, 0x78, 0x62, 0x54, + 0x79, 0x64, 0x72, 0x46, 0x6e, 0x79, 0x39, 0x52, 0x41, 0x51, 0x59, 0x67, + 0x72, 0x4f, 0x4a, 0x65, 0x52, 0x63, 0x51, 0x63, 0x54, 0x31, 0x36, 0x6f, + 0x68, 0x5a, 0x4f, 0x39, 0x51, 0x48, 0x4e, 0x0a, 0x70, 0x47, 0x78, 0x6c, + 0x61, 0x4b, 0x46, 0x4a, 0x64, 0x6c, 0x78, 0x44, 0x79, 0x64, 0x69, 0x38, + 0x4e, 0x6d, 0x64, 0x73, 0x70, 0x5a, 0x53, 0x31, 0x31, 0x4d, 0x79, 0x35, + 0x76, 0x57, 0x6f, 0x31, 0x56, 0x69, 0x48, 0x65, 0x32, 0x4d, 0x50, 0x72, + 0x2b, 0x38, 0x75, 0x6b, 0x59, 0x45, 0x79, 0x77, 0x56, 0x61, 0x43, 0x67, + 0x65, 0x31, 0x65, 0x79, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, + 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, + 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, + 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x43, 0x4f, 0x4d, + 0x4f, 0x44, 0x4f, 0x20, 0x45, 0x43, 0x43, 0x20, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x4f, 0x3d, 0x43, 0x4f, + 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, + 0x74, 0x65, 0x64, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, + 0x20, 0x45, 0x43, 0x43, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x74, 0x79, 0x20, 0x4f, 0x3d, 0x43, 0x4f, 0x4d, 0x4f, 0x44, + 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, + 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x43, + 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x45, 0x43, 0x43, 0x20, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x0a, 0x23, + 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x34, 0x31, 0x35, + 0x37, 0x38, 0x32, 0x38, 0x33, 0x38, 0x36, 0x37, 0x30, 0x38, 0x36, 0x36, + 0x39, 0x32, 0x36, 0x33, 0x38, 0x32, 0x35, 0x36, 0x39, 0x32, 0x31, 0x35, + 0x38, 0x39, 0x37, 0x30, 0x37, 0x39, 0x33, 0x38, 0x30, 0x39, 0x30, 0x0a, + 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x37, 0x63, 0x3a, 0x36, 0x32, + 0x3a, 0x66, 0x66, 0x3a, 0x37, 0x34, 0x3a, 0x39, 0x64, 0x3a, 0x33, 0x31, + 0x3a, 0x35, 0x33, 0x3a, 0x35, 0x65, 0x3a, 0x36, 0x38, 0x3a, 0x34, 0x61, + 0x3a, 0x64, 0x35, 0x3a, 0x37, 0x38, 0x3a, 0x61, 0x61, 0x3a, 0x31, 0x65, + 0x3a, 0x62, 0x66, 0x3a, 0x32, 0x33, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, + 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, + 0x74, 0x3a, 0x20, 0x39, 0x66, 0x3a, 0x37, 0x34, 0x3a, 0x34, 0x65, 0x3a, + 0x39, 0x66, 0x3a, 0x32, 0x62, 0x3a, 0x34, 0x64, 0x3a, 0x62, 0x61, 0x3a, + 0x65, 0x63, 0x3a, 0x30, 0x66, 0x3a, 0x33, 0x31, 0x3a, 0x32, 0x63, 0x3a, + 0x35, 0x30, 0x3a, 0x62, 0x36, 0x3a, 0x35, 0x36, 0x3a, 0x33, 0x62, 0x3a, + 0x38, 0x65, 0x3a, 0x32, 0x64, 0x3a, 0x39, 0x33, 0x3a, 0x63, 0x33, 0x3a, + 0x31, 0x31, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, + 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, + 0x20, 0x31, 0x37, 0x3a, 0x39, 0x33, 0x3a, 0x39, 0x32, 0x3a, 0x37, 0x61, + 0x3a, 0x30, 0x36, 0x3a, 0x31, 0x34, 0x3a, 0x35, 0x34, 0x3a, 0x39, 0x37, + 0x3a, 0x38, 0x39, 0x3a, 0x61, 0x64, 0x3a, 0x63, 0x65, 0x3a, 0x32, 0x66, + 0x3a, 0x38, 0x66, 0x3a, 0x33, 0x34, 0x3a, 0x66, 0x37, 0x3a, 0x66, 0x30, + 0x3a, 0x62, 0x36, 0x3a, 0x36, 0x64, 0x3a, 0x30, 0x66, 0x3a, 0x33, 0x61, + 0x3a, 0x65, 0x33, 0x3a, 0x61, 0x33, 0x3a, 0x62, 0x38, 0x3a, 0x34, 0x64, + 0x3a, 0x32, 0x31, 0x3a, 0x65, 0x63, 0x3a, 0x31, 0x35, 0x3a, 0x64, 0x62, + 0x3a, 0x62, 0x61, 0x3a, 0x34, 0x66, 0x3a, 0x61, 0x64, 0x3a, 0x63, 0x37, + 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, + 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x43, 0x69, 0x54, 0x43, + 0x43, 0x41, 0x67, 0x2b, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, + 0x51, 0x48, 0x30, 0x65, 0x76, 0x71, 0x6d, 0x49, 0x41, 0x63, 0x46, 0x42, + 0x55, 0x54, 0x41, 0x47, 0x65, 0x6d, 0x32, 0x4f, 0x5a, 0x4b, 0x6a, 0x41, + 0x4b, 0x42, 0x67, 0x67, 0x71, 0x68, 0x6b, 0x6a, 0x4f, 0x50, 0x51, 0x51, + 0x44, 0x41, 0x7a, 0x43, 0x42, 0x68, 0x54, 0x45, 0x4c, 0x0a, 0x4d, 0x41, + 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x52, 0x30, + 0x49, 0x78, 0x47, 0x7a, 0x41, 0x5a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, + 0x67, 0x54, 0x45, 0x6b, 0x64, 0x79, 0x5a, 0x57, 0x46, 0x30, 0x5a, 0x58, + 0x49, 0x67, 0x54, 0x57, 0x46, 0x75, 0x59, 0x32, 0x68, 0x6c, 0x63, 0x33, + 0x52, 0x6c, 0x63, 0x6a, 0x45, 0x51, 0x4d, 0x41, 0x34, 0x47, 0x41, 0x31, + 0x55, 0x45, 0x0a, 0x42, 0x78, 0x4d, 0x48, 0x55, 0x32, 0x46, 0x73, 0x5a, + 0x6d, 0x39, 0x79, 0x5a, 0x44, 0x45, 0x61, 0x4d, 0x42, 0x67, 0x47, 0x41, + 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x52, 0x51, 0x30, 0x39, 0x4e, 0x54, + 0x30, 0x52, 0x50, 0x49, 0x45, 0x4e, 0x42, 0x49, 0x45, 0x78, 0x70, 0x62, + 0x57, 0x6c, 0x30, 0x5a, 0x57, 0x51, 0x78, 0x4b, 0x7a, 0x41, 0x70, 0x42, + 0x67, 0x4e, 0x56, 0x42, 0x41, 0x4d, 0x54, 0x0a, 0x49, 0x6b, 0x4e, 0x50, + 0x54, 0x55, 0x39, 0x45, 0x54, 0x79, 0x42, 0x46, 0x51, 0x30, 0x4d, 0x67, + 0x51, 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, + 0x64, 0x47, 0x6c, 0x76, 0x62, 0x69, 0x42, 0x42, 0x64, 0x58, 0x52, 0x6f, + 0x62, 0x33, 0x4a, 0x70, 0x64, 0x48, 0x6b, 0x77, 0x48, 0x68, 0x63, 0x4e, + 0x4d, 0x44, 0x67, 0x77, 0x4d, 0x7a, 0x41, 0x32, 0x4d, 0x44, 0x41, 0x77, + 0x0a, 0x4d, 0x44, 0x41, 0x77, 0x57, 0x68, 0x63, 0x4e, 0x4d, 0x7a, 0x67, + 0x77, 0x4d, 0x54, 0x45, 0x34, 0x4d, 0x6a, 0x4d, 0x31, 0x4f, 0x54, 0x55, + 0x35, 0x57, 0x6a, 0x43, 0x42, 0x68, 0x54, 0x45, 0x4c, 0x4d, 0x41, 0x6b, + 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x52, 0x30, 0x49, + 0x78, 0x47, 0x7a, 0x41, 0x5a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x67, + 0x54, 0x45, 0x6b, 0x64, 0x79, 0x0a, 0x5a, 0x57, 0x46, 0x30, 0x5a, 0x58, + 0x49, 0x67, 0x54, 0x57, 0x46, 0x75, 0x59, 0x32, 0x68, 0x6c, 0x63, 0x33, + 0x52, 0x6c, 0x63, 0x6a, 0x45, 0x51, 0x4d, 0x41, 0x34, 0x47, 0x41, 0x31, + 0x55, 0x45, 0x42, 0x78, 0x4d, 0x48, 0x55, 0x32, 0x46, 0x73, 0x5a, 0x6d, + 0x39, 0x79, 0x5a, 0x44, 0x45, 0x61, 0x4d, 0x42, 0x67, 0x47, 0x41, 0x31, + 0x55, 0x45, 0x43, 0x68, 0x4d, 0x52, 0x51, 0x30, 0x39, 0x4e, 0x0a, 0x54, + 0x30, 0x52, 0x50, 0x49, 0x45, 0x4e, 0x42, 0x49, 0x45, 0x78, 0x70, 0x62, + 0x57, 0x6c, 0x30, 0x5a, 0x57, 0x51, 0x78, 0x4b, 0x7a, 0x41, 0x70, 0x42, + 0x67, 0x4e, 0x56, 0x42, 0x41, 0x4d, 0x54, 0x49, 0x6b, 0x4e, 0x50, 0x54, + 0x55, 0x39, 0x45, 0x54, 0x79, 0x42, 0x46, 0x51, 0x30, 0x4d, 0x67, 0x51, + 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, 0x64, + 0x47, 0x6c, 0x76, 0x0a, 0x62, 0x69, 0x42, 0x42, 0x64, 0x58, 0x52, 0x6f, + 0x62, 0x33, 0x4a, 0x70, 0x64, 0x48, 0x6b, 0x77, 0x64, 0x6a, 0x41, 0x51, + 0x42, 0x67, 0x63, 0x71, 0x68, 0x6b, 0x6a, 0x4f, 0x50, 0x51, 0x49, 0x42, + 0x42, 0x67, 0x55, 0x72, 0x67, 0x51, 0x51, 0x41, 0x49, 0x67, 0x4e, 0x69, + 0x41, 0x41, 0x51, 0x44, 0x52, 0x33, 0x73, 0x76, 0x64, 0x63, 0x6d, 0x43, + 0x46, 0x59, 0x58, 0x37, 0x64, 0x65, 0x53, 0x52, 0x0a, 0x46, 0x74, 0x53, + 0x72, 0x59, 0x70, 0x6e, 0x31, 0x50, 0x6c, 0x49, 0x4c, 0x42, 0x73, 0x35, + 0x42, 0x41, 0x48, 0x2b, 0x58, 0x34, 0x51, 0x6f, 0x6b, 0x50, 0x42, 0x30, + 0x42, 0x42, 0x4f, 0x34, 0x39, 0x30, 0x6f, 0x30, 0x4a, 0x6c, 0x77, 0x7a, + 0x67, 0x64, 0x65, 0x54, 0x36, 0x2b, 0x33, 0x65, 0x4b, 0x4b, 0x76, 0x55, + 0x44, 0x59, 0x45, 0x73, 0x32, 0x69, 0x78, 0x59, 0x6a, 0x46, 0x71, 0x30, + 0x4a, 0x0a, 0x63, 0x66, 0x52, 0x4b, 0x39, 0x43, 0x68, 0x51, 0x74, 0x50, + 0x36, 0x49, 0x48, 0x47, 0x34, 0x2f, 0x62, 0x43, 0x38, 0x76, 0x43, 0x56, + 0x6c, 0x62, 0x70, 0x56, 0x73, 0x4c, 0x4d, 0x35, 0x6e, 0x69, 0x77, 0x7a, + 0x32, 0x4a, 0x2b, 0x57, 0x6f, 0x73, 0x37, 0x37, 0x4c, 0x54, 0x42, 0x75, + 0x6d, 0x6a, 0x51, 0x6a, 0x42, 0x41, 0x4d, 0x42, 0x30, 0x47, 0x41, 0x31, + 0x55, 0x64, 0x44, 0x67, 0x51, 0x57, 0x0a, 0x42, 0x42, 0x52, 0x31, 0x63, + 0x61, 0x63, 0x5a, 0x53, 0x42, 0x6d, 0x38, 0x6e, 0x5a, 0x33, 0x71, 0x51, + 0x55, 0x66, 0x66, 0x6c, 0x4d, 0x52, 0x49, 0x64, 0x35, 0x6e, 0x54, 0x65, + 0x54, 0x41, 0x4f, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x38, 0x42, 0x41, + 0x66, 0x38, 0x45, 0x42, 0x41, 0x4d, 0x43, 0x41, 0x51, 0x59, 0x77, 0x44, + 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, 0x54, 0x41, 0x51, 0x48, 0x2f, 0x0a, + 0x42, 0x41, 0x55, 0x77, 0x41, 0x77, 0x45, 0x42, 0x2f, 0x7a, 0x41, 0x4b, + 0x42, 0x67, 0x67, 0x71, 0x68, 0x6b, 0x6a, 0x4f, 0x50, 0x51, 0x51, 0x44, + 0x41, 0x77, 0x4e, 0x6f, 0x41, 0x44, 0x42, 0x6c, 0x41, 0x6a, 0x45, 0x41, + 0x37, 0x77, 0x4e, 0x62, 0x65, 0x71, 0x79, 0x33, 0x65, 0x41, 0x70, 0x79, + 0x74, 0x34, 0x6a, 0x66, 0x2f, 0x37, 0x56, 0x47, 0x46, 0x41, 0x6b, 0x4b, + 0x2b, 0x71, 0x44, 0x6d, 0x0a, 0x66, 0x51, 0x6a, 0x47, 0x47, 0x6f, 0x65, + 0x39, 0x47, 0x4b, 0x68, 0x7a, 0x76, 0x53, 0x62, 0x4b, 0x59, 0x41, 0x79, + 0x64, 0x7a, 0x70, 0x6d, 0x66, 0x7a, 0x31, 0x77, 0x50, 0x4d, 0x4f, 0x47, + 0x2b, 0x46, 0x44, 0x48, 0x71, 0x41, 0x6a, 0x41, 0x55, 0x39, 0x4a, 0x4d, + 0x38, 0x53, 0x61, 0x63, 0x7a, 0x65, 0x70, 0x42, 0x47, 0x52, 0x37, 0x4e, + 0x6a, 0x66, 0x52, 0x4f, 0x62, 0x54, 0x72, 0x64, 0x76, 0x0a, 0x47, 0x44, + 0x65, 0x41, 0x55, 0x2f, 0x37, 0x64, 0x49, 0x4f, 0x41, 0x31, 0x6d, 0x6a, + 0x62, 0x52, 0x78, 0x77, 0x47, 0x35, 0x35, 0x74, 0x7a, 0x64, 0x38, 0x2f, + 0x38, 0x64, 0x4c, 0x44, 0x6f, 0x57, 0x56, 0x39, 0x6d, 0x53, 0x4f, 0x64, + 0x59, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, + 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, + 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x54, 0x43, 0x20, 0x54, 0x72, + 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x43, 0x6c, + 0x61, 0x73, 0x73, 0x20, 0x32, 0x20, 0x43, 0x41, 0x20, 0x49, 0x49, 0x20, + 0x4f, 0x3d, 0x54, 0x43, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x43, 0x65, + 0x6e, 0x74, 0x65, 0x72, 0x20, 0x47, 0x6d, 0x62, 0x48, 0x20, 0x4f, 0x55, + 0x3d, 0x54, 0x43, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, + 0x74, 0x65, 0x72, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x32, 0x20, + 0x43, 0x41, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x54, 0x43, 0x20, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x43, 0x6c, 0x61, 0x73, + 0x73, 0x20, 0x32, 0x20, 0x43, 0x41, 0x20, 0x49, 0x49, 0x20, 0x4f, 0x3d, + 0x54, 0x43, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, + 0x65, 0x72, 0x20, 0x47, 0x6d, 0x62, 0x48, 0x20, 0x4f, 0x55, 0x3d, 0x54, + 0x43, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, + 0x72, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x32, 0x20, 0x43, 0x41, + 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x54, + 0x43, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, + 0x72, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x32, 0x20, 0x43, 0x41, + 0x20, 0x49, 0x49, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, + 0x6c, 0x3a, 0x20, 0x39, 0x34, 0x31, 0x33, 0x38, 0x39, 0x30, 0x32, 0x38, + 0x32, 0x30, 0x33, 0x34, 0x35, 0x33, 0x38, 0x36, 0x36, 0x37, 0x38, 0x32, + 0x31, 0x30, 0x33, 0x34, 0x30, 0x36, 0x39, 0x39, 0x32, 0x34, 0x34, 0x33, + 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x63, 0x65, 0x3a, 0x37, + 0x38, 0x3a, 0x33, 0x33, 0x3a, 0x35, 0x63, 0x3a, 0x35, 0x39, 0x3a, 0x37, + 0x38, 0x3a, 0x30, 0x31, 0x3a, 0x36, 0x65, 0x3a, 0x31, 0x38, 0x3a, 0x65, + 0x61, 0x3a, 0x62, 0x39, 0x3a, 0x33, 0x36, 0x3a, 0x61, 0x30, 0x3a, 0x62, + 0x39, 0x3a, 0x32, 0x65, 0x3a, 0x32, 0x33, 0x0a, 0x23, 0x20, 0x53, 0x48, + 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x3a, 0x20, 0x61, 0x65, 0x3a, 0x35, 0x30, 0x3a, 0x38, 0x33, + 0x3a, 0x65, 0x64, 0x3a, 0x37, 0x63, 0x3a, 0x66, 0x34, 0x3a, 0x35, 0x63, + 0x3a, 0x62, 0x63, 0x3a, 0x38, 0x66, 0x3a, 0x36, 0x31, 0x3a, 0x63, 0x36, + 0x3a, 0x32, 0x31, 0x3a, 0x66, 0x65, 0x3a, 0x36, 0x38, 0x3a, 0x35, 0x64, + 0x3a, 0x37, 0x39, 0x3a, 0x34, 0x32, 0x3a, 0x32, 0x31, 0x3a, 0x31, 0x35, + 0x3a, 0x36, 0x65, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, + 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, + 0x3a, 0x20, 0x65, 0x36, 0x3a, 0x62, 0x38, 0x3a, 0x66, 0x38, 0x3a, 0x37, + 0x36, 0x3a, 0x36, 0x34, 0x3a, 0x38, 0x35, 0x3a, 0x66, 0x38, 0x3a, 0x30, + 0x37, 0x3a, 0x61, 0x65, 0x3a, 0x37, 0x66, 0x3a, 0x38, 0x64, 0x3a, 0x61, + 0x63, 0x3a, 0x31, 0x36, 0x3a, 0x37, 0x30, 0x3a, 0x34, 0x36, 0x3a, 0x31, + 0x66, 0x3a, 0x30, 0x37, 0x3a, 0x63, 0x30, 0x3a, 0x61, 0x31, 0x3a, 0x33, + 0x65, 0x3a, 0x65, 0x66, 0x3a, 0x33, 0x61, 0x3a, 0x31, 0x66, 0x3a, 0x66, + 0x37, 0x3a, 0x31, 0x37, 0x3a, 0x35, 0x33, 0x3a, 0x38, 0x64, 0x3a, 0x37, + 0x61, 0x3a, 0x62, 0x61, 0x3a, 0x64, 0x33, 0x3a, 0x39, 0x31, 0x3a, 0x62, + 0x34, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, + 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x45, 0x71, 0x6a, + 0x43, 0x43, 0x41, 0x35, 0x4b, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, + 0x49, 0x4f, 0x4c, 0x6d, 0x6f, 0x41, 0x41, 0x51, 0x41, 0x43, 0x48, 0x39, + 0x64, 0x53, 0x49, 0x53, 0x77, 0x52, 0x58, 0x44, 0x73, 0x77, 0x44, 0x51, + 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, + 0x45, 0x46, 0x42, 0x51, 0x41, 0x77, 0x64, 0x6a, 0x45, 0x4c, 0x0a, 0x4d, + 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x52, + 0x45, 0x55, 0x78, 0x48, 0x44, 0x41, 0x61, 0x42, 0x67, 0x4e, 0x56, 0x42, + 0x41, 0x6f, 0x54, 0x45, 0x31, 0x52, 0x44, 0x49, 0x46, 0x52, 0x79, 0x64, + 0x58, 0x4e, 0x30, 0x51, 0x32, 0x56, 0x75, 0x64, 0x47, 0x56, 0x79, 0x49, + 0x45, 0x64, 0x74, 0x59, 0x6b, 0x67, 0x78, 0x49, 0x6a, 0x41, 0x67, 0x42, + 0x67, 0x4e, 0x56, 0x0a, 0x42, 0x41, 0x73, 0x54, 0x47, 0x56, 0x52, 0x44, + 0x49, 0x46, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x51, 0x32, 0x56, 0x75, + 0x64, 0x47, 0x56, 0x79, 0x49, 0x45, 0x4e, 0x73, 0x59, 0x58, 0x4e, 0x7a, + 0x49, 0x44, 0x49, 0x67, 0x51, 0x30, 0x45, 0x78, 0x4a, 0x54, 0x41, 0x6a, + 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x4d, 0x54, 0x48, 0x46, 0x52, 0x44, + 0x49, 0x46, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x0a, 0x51, 0x32, 0x56, + 0x75, 0x64, 0x47, 0x56, 0x79, 0x49, 0x45, 0x4e, 0x73, 0x59, 0x58, 0x4e, + 0x7a, 0x49, 0x44, 0x49, 0x67, 0x51, 0x30, 0x45, 0x67, 0x53, 0x55, 0x6b, + 0x77, 0x48, 0x68, 0x63, 0x4e, 0x4d, 0x44, 0x59, 0x77, 0x4d, 0x54, 0x45, + 0x79, 0x4d, 0x54, 0x51, 0x7a, 0x4f, 0x44, 0x51, 0x7a, 0x57, 0x68, 0x63, + 0x4e, 0x4d, 0x6a, 0x55, 0x78, 0x4d, 0x6a, 0x4d, 0x78, 0x4d, 0x6a, 0x49, + 0x31, 0x0a, 0x4f, 0x54, 0x55, 0x35, 0x57, 0x6a, 0x42, 0x32, 0x4d, 0x51, + 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, + 0x4a, 0x45, 0x52, 0x54, 0x45, 0x63, 0x4d, 0x42, 0x6f, 0x47, 0x41, 0x31, + 0x55, 0x45, 0x43, 0x68, 0x4d, 0x54, 0x56, 0x45, 0x4d, 0x67, 0x56, 0x48, + 0x4a, 0x31, 0x63, 0x33, 0x52, 0x44, 0x5a, 0x57, 0x35, 0x30, 0x5a, 0x58, + 0x49, 0x67, 0x52, 0x32, 0x31, 0x69, 0x0a, 0x53, 0x44, 0x45, 0x69, 0x4d, + 0x43, 0x41, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x78, 0x4d, 0x5a, 0x56, + 0x45, 0x4d, 0x67, 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x52, 0x44, 0x5a, + 0x57, 0x35, 0x30, 0x5a, 0x58, 0x49, 0x67, 0x51, 0x32, 0x78, 0x68, 0x63, + 0x33, 0x4d, 0x67, 0x4d, 0x69, 0x42, 0x44, 0x51, 0x54, 0x45, 0x6c, 0x4d, + 0x43, 0x4d, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x63, 0x0a, + 0x56, 0x45, 0x4d, 0x67, 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x52, 0x44, + 0x5a, 0x57, 0x35, 0x30, 0x5a, 0x58, 0x49, 0x67, 0x51, 0x32, 0x78, 0x68, + 0x63, 0x33, 0x4d, 0x67, 0x4d, 0x69, 0x42, 0x44, 0x51, 0x53, 0x42, 0x4a, + 0x53, 0x54, 0x43, 0x43, 0x41, 0x53, 0x49, 0x77, 0x44, 0x51, 0x59, 0x4a, + 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x42, + 0x42, 0x51, 0x41, 0x44, 0x0a, 0x67, 0x67, 0x45, 0x50, 0x41, 0x44, 0x43, + 0x43, 0x41, 0x51, 0x6f, 0x43, 0x67, 0x67, 0x45, 0x42, 0x41, 0x4b, 0x75, + 0x41, 0x68, 0x35, 0x75, 0x4f, 0x38, 0x4d, 0x4e, 0x38, 0x68, 0x39, 0x66, + 0x6f, 0x4a, 0x49, 0x49, 0x52, 0x73, 0x7a, 0x7a, 0x64, 0x51, 0x32, 0x4c, + 0x75, 0x2b, 0x4d, 0x4e, 0x46, 0x32, 0x75, 0x6a, 0x68, 0x6f, 0x46, 0x2f, + 0x52, 0x4b, 0x72, 0x4c, 0x71, 0x6b, 0x32, 0x6a, 0x66, 0x0a, 0x74, 0x4d, + 0x6a, 0x57, 0x51, 0x2b, 0x6e, 0x45, 0x64, 0x56, 0x6c, 0x2f, 0x2f, 0x4f, + 0x45, 0x64, 0x2b, 0x44, 0x46, 0x77, 0x49, 0x78, 0x75, 0x49, 0x6e, 0x69, + 0x65, 0x35, 0x65, 0x2f, 0x30, 0x36, 0x30, 0x73, 0x6d, 0x70, 0x36, 0x52, + 0x51, 0x76, 0x6b, 0x4c, 0x34, 0x44, 0x55, 0x73, 0x46, 0x4a, 0x7a, 0x66, + 0x62, 0x39, 0x35, 0x41, 0x68, 0x6d, 0x43, 0x31, 0x65, 0x4b, 0x6f, 0x6b, + 0x4b, 0x67, 0x0a, 0x75, 0x4e, 0x56, 0x2f, 0x61, 0x56, 0x79, 0x51, 0x4d, + 0x72, 0x4b, 0x58, 0x44, 0x63, 0x70, 0x4b, 0x33, 0x45, 0x59, 0x2b, 0x41, + 0x6c, 0x57, 0x4a, 0x55, 0x2b, 0x4d, 0x61, 0x57, 0x73, 0x73, 0x32, 0x78, + 0x67, 0x64, 0x57, 0x39, 0x34, 0x7a, 0x50, 0x45, 0x66, 0x52, 0x4d, 0x75, + 0x7a, 0x42, 0x77, 0x42, 0x4a, 0x57, 0x6c, 0x39, 0x6a, 0x6d, 0x4d, 0x2f, + 0x58, 0x4f, 0x42, 0x43, 0x48, 0x32, 0x4a, 0x0a, 0x58, 0x6a, 0x49, 0x65, + 0x49, 0x71, 0x6b, 0x69, 0x52, 0x55, 0x75, 0x77, 0x5a, 0x69, 0x34, 0x77, + 0x7a, 0x4a, 0x39, 0x6c, 0x2f, 0x66, 0x7a, 0x4c, 0x67, 0x61, 0x6e, 0x78, + 0x34, 0x44, 0x75, 0x76, 0x6f, 0x34, 0x62, 0x52, 0x69, 0x65, 0x72, 0x45, + 0x52, 0x58, 0x6c, 0x51, 0x58, 0x61, 0x37, 0x70, 0x49, 0x58, 0x53, 0x53, + 0x54, 0x59, 0x74, 0x5a, 0x67, 0x6f, 0x2b, 0x55, 0x34, 0x2b, 0x6c, 0x4b, + 0x0a, 0x38, 0x65, 0x64, 0x4a, 0x73, 0x42, 0x54, 0x6a, 0x39, 0x57, 0x4c, + 0x4c, 0x31, 0x58, 0x4b, 0x39, 0x48, 0x37, 0x6e, 0x53, 0x6e, 0x36, 0x44, + 0x4e, 0x71, 0x50, 0x6f, 0x42, 0x79, 0x4e, 0x6b, 0x4e, 0x33, 0x39, 0x72, + 0x38, 0x52, 0x35, 0x32, 0x7a, 0x79, 0x46, 0x54, 0x66, 0x53, 0x55, 0x72, + 0x78, 0x49, 0x61, 0x6e, 0x2b, 0x47, 0x45, 0x37, 0x75, 0x53, 0x4e, 0x51, + 0x5a, 0x75, 0x2b, 0x39, 0x39, 0x0a, 0x35, 0x4f, 0x4b, 0x64, 0x79, 0x31, + 0x75, 0x32, 0x62, 0x76, 0x2f, 0x6a, 0x7a, 0x56, 0x72, 0x6e, 0x64, 0x49, + 0x49, 0x46, 0x75, 0x6f, 0x41, 0x6c, 0x4f, 0x4d, 0x76, 0x6b, 0x61, 0x5a, + 0x36, 0x76, 0x51, 0x61, 0x6f, 0x61, 0x68, 0x50, 0x55, 0x43, 0x41, 0x77, + 0x45, 0x41, 0x41, 0x61, 0x4f, 0x43, 0x41, 0x54, 0x51, 0x77, 0x67, 0x67, + 0x45, 0x77, 0x4d, 0x41, 0x38, 0x47, 0x41, 0x31, 0x55, 0x64, 0x0a, 0x45, + 0x77, 0x45, 0x42, 0x2f, 0x77, 0x51, 0x46, 0x4d, 0x41, 0x4d, 0x42, 0x41, + 0x66, 0x38, 0x77, 0x44, 0x67, 0x59, 0x44, 0x56, 0x52, 0x30, 0x50, 0x41, + 0x51, 0x48, 0x2f, 0x42, 0x41, 0x51, 0x44, 0x41, 0x67, 0x45, 0x47, 0x4d, + 0x42, 0x30, 0x47, 0x41, 0x31, 0x55, 0x64, 0x44, 0x67, 0x51, 0x57, 0x42, + 0x42, 0x54, 0x6a, 0x71, 0x31, 0x52, 0x4d, 0x67, 0x4b, 0x48, 0x62, 0x56, + 0x6b, 0x4f, 0x33, 0x0a, 0x6b, 0x55, 0x72, 0x4c, 0x38, 0x34, 0x4a, 0x36, + 0x45, 0x31, 0x77, 0x49, 0x71, 0x7a, 0x43, 0x42, 0x37, 0x51, 0x59, 0x44, + 0x56, 0x52, 0x30, 0x66, 0x42, 0x49, 0x48, 0x6c, 0x4d, 0x49, 0x48, 0x69, + 0x4d, 0x49, 0x48, 0x66, 0x6f, 0x49, 0x48, 0x63, 0x6f, 0x49, 0x48, 0x5a, + 0x68, 0x6a, 0x56, 0x6f, 0x64, 0x48, 0x52, 0x77, 0x4f, 0x69, 0x38, 0x76, + 0x64, 0x33, 0x64, 0x33, 0x4c, 0x6e, 0x52, 0x79, 0x0a, 0x64, 0x58, 0x4e, + 0x30, 0x59, 0x32, 0x56, 0x75, 0x64, 0x47, 0x56, 0x79, 0x4c, 0x6d, 0x52, + 0x6c, 0x4c, 0x32, 0x4e, 0x79, 0x62, 0x43, 0x39, 0x32, 0x4d, 0x69, 0x39, + 0x30, 0x59, 0x31, 0x39, 0x6a, 0x62, 0x47, 0x46, 0x7a, 0x63, 0x31, 0x38, + 0x79, 0x58, 0x32, 0x4e, 0x68, 0x58, 0x30, 0x6c, 0x4a, 0x4c, 0x6d, 0x4e, + 0x79, 0x62, 0x49, 0x61, 0x42, 0x6e, 0x32, 0x78, 0x6b, 0x59, 0x58, 0x41, + 0x36, 0x0a, 0x4c, 0x79, 0x39, 0x33, 0x64, 0x33, 0x63, 0x75, 0x64, 0x48, + 0x4a, 0x31, 0x63, 0x33, 0x52, 0x6a, 0x5a, 0x57, 0x35, 0x30, 0x5a, 0x58, + 0x49, 0x75, 0x5a, 0x47, 0x55, 0x76, 0x51, 0x30, 0x34, 0x39, 0x56, 0x45, + 0x4d, 0x6c, 0x4d, 0x6a, 0x42, 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x45, + 0x4e, 0x6c, 0x62, 0x6e, 0x52, 0x6c, 0x63, 0x69, 0x55, 0x79, 0x4d, 0x45, + 0x4e, 0x73, 0x59, 0x58, 0x4e, 0x7a, 0x0a, 0x4a, 0x54, 0x49, 0x77, 0x4d, + 0x69, 0x55, 0x79, 0x4d, 0x45, 0x4e, 0x42, 0x4a, 0x54, 0x49, 0x77, 0x53, + 0x55, 0x6b, 0x73, 0x54, 0x7a, 0x31, 0x55, 0x51, 0x79, 0x55, 0x79, 0x4d, + 0x46, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x51, 0x32, 0x56, 0x75, 0x64, + 0x47, 0x56, 0x79, 0x4a, 0x54, 0x49, 0x77, 0x52, 0x32, 0x31, 0x69, 0x53, + 0x43, 0x78, 0x50, 0x56, 0x54, 0x31, 0x79, 0x62, 0x32, 0x39, 0x30, 0x0a, + 0x59, 0x32, 0x56, 0x79, 0x64, 0x48, 0x4d, 0x73, 0x52, 0x45, 0x4d, 0x39, + 0x64, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x52, 0x6a, 0x5a, 0x57, 0x35, 0x30, + 0x5a, 0x58, 0x49, 0x73, 0x52, 0x45, 0x4d, 0x39, 0x5a, 0x47, 0x55, 0x2f, + 0x59, 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, + 0x64, 0x47, 0x56, 0x53, 0x5a, 0x58, 0x5a, 0x76, 0x59, 0x32, 0x46, 0x30, + 0x61, 0x57, 0x39, 0x75, 0x0a, 0x54, 0x47, 0x6c, 0x7a, 0x64, 0x44, 0x39, + 0x69, 0x59, 0x58, 0x4e, 0x6c, 0x50, 0x7a, 0x41, 0x4e, 0x42, 0x67, 0x6b, + 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, + 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x51, 0x45, 0x41, 0x6a, 0x4e, 0x66, + 0x66, 0x66, 0x75, 0x34, 0x62, 0x67, 0x42, 0x43, 0x7a, 0x67, 0x2f, 0x58, + 0x62, 0x45, 0x65, 0x70, 0x72, 0x53, 0x36, 0x69, 0x53, 0x0a, 0x47, 0x4e, + 0x6e, 0x33, 0x42, 0x7a, 0x6e, 0x31, 0x4c, 0x4c, 0x34, 0x47, 0x64, 0x58, + 0x70, 0x6f, 0x55, 0x78, 0x55, 0x63, 0x36, 0x6b, 0x72, 0x74, 0x58, 0x76, + 0x77, 0x6a, 0x73, 0x68, 0x4f, 0x67, 0x30, 0x77, 0x6e, 0x2f, 0x39, 0x76, + 0x59, 0x75, 0x61, 0x30, 0x46, 0x78, 0x65, 0x63, 0x33, 0x69, 0x62, 0x66, + 0x32, 0x75, 0x57, 0x57, 0x75, 0x46, 0x48, 0x62, 0x68, 0x4f, 0x49, 0x70, + 0x72, 0x74, 0x0a, 0x5a, 0x6a, 0x6c, 0x75, 0x53, 0x35, 0x54, 0x6d, 0x56, + 0x66, 0x77, 0x4c, 0x47, 0x34, 0x74, 0x33, 0x77, 0x56, 0x4d, 0x54, 0x5a, + 0x6f, 0x6e, 0x5a, 0x4b, 0x4e, 0x61, 0x4c, 0x38, 0x30, 0x56, 0x4b, 0x59, + 0x37, 0x66, 0x39, 0x65, 0x77, 0x74, 0x68, 0x58, 0x62, 0x68, 0x74, 0x76, + 0x73, 0x50, 0x63, 0x57, 0x33, 0x6e, 0x53, 0x37, 0x59, 0x62, 0x6c, 0x6f, + 0x6b, 0x32, 0x2b, 0x58, 0x6e, 0x52, 0x38, 0x0a, 0x61, 0x75, 0x30, 0x57, + 0x4f, 0x42, 0x39, 0x2f, 0x57, 0x49, 0x46, 0x61, 0x47, 0x75, 0x73, 0x79, + 0x69, 0x43, 0x32, 0x79, 0x38, 0x7a, 0x6c, 0x33, 0x67, 0x4b, 0x39, 0x65, + 0x74, 0x6d, 0x46, 0x31, 0x4b, 0x64, 0x73, 0x6a, 0x54, 0x59, 0x6a, 0x4b, + 0x55, 0x43, 0x6a, 0x4c, 0x68, 0x64, 0x4c, 0x54, 0x45, 0x4b, 0x4a, 0x5a, + 0x62, 0x74, 0x4f, 0x54, 0x56, 0x41, 0x42, 0x36, 0x6f, 0x6b, 0x61, 0x56, + 0x0a, 0x68, 0x67, 0x57, 0x63, 0x71, 0x52, 0x6d, 0x59, 0x35, 0x54, 0x46, + 0x79, 0x44, 0x41, 0x44, 0x69, 0x5a, 0x39, 0x6c, 0x41, 0x34, 0x43, 0x51, + 0x7a, 0x65, 0x32, 0x38, 0x73, 0x75, 0x56, 0x79, 0x72, 0x5a, 0x5a, 0x30, + 0x73, 0x72, 0x48, 0x62, 0x71, 0x4e, 0x5a, 0x6e, 0x31, 0x6c, 0x37, 0x6b, + 0x50, 0x4a, 0x4f, 0x7a, 0x48, 0x64, 0x69, 0x45, 0x6f, 0x5a, 0x61, 0x35, + 0x58, 0x36, 0x41, 0x65, 0x49, 0x0a, 0x64, 0x55, 0x70, 0x57, 0x6f, 0x4e, + 0x49, 0x46, 0x4f, 0x71, 0x54, 0x6d, 0x6a, 0x5a, 0x4b, 0x49, 0x4c, 0x50, + 0x50, 0x79, 0x34, 0x63, 0x48, 0x47, 0x59, 0x64, 0x74, 0x42, 0x78, 0x63, + 0x65, 0x62, 0x39, 0x77, 0x34, 0x61, 0x55, 0x55, 0x58, 0x43, 0x59, 0x57, + 0x76, 0x63, 0x5a, 0x43, 0x63, 0x58, 0x6a, 0x46, 0x71, 0x33, 0x32, 0x6e, + 0x51, 0x6f, 0x7a, 0x5a, 0x66, 0x6b, 0x76, 0x51, 0x3d, 0x3d, 0x0a, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, + 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, + 0x43, 0x4e, 0x3d, 0x54, 0x43, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x43, + 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, + 0x33, 0x20, 0x43, 0x41, 0x20, 0x49, 0x49, 0x20, 0x4f, 0x3d, 0x54, 0x43, + 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, + 0x20, 0x47, 0x6d, 0x62, 0x48, 0x20, 0x4f, 0x55, 0x3d, 0x54, 0x43, 0x20, + 0x54, 0x72, 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, + 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x43, 0x41, 0x0a, 0x23, + 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, + 0x3d, 0x54, 0x43, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, + 0x74, 0x65, 0x72, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, + 0x43, 0x41, 0x20, 0x49, 0x49, 0x20, 0x4f, 0x3d, 0x54, 0x43, 0x20, 0x54, + 0x72, 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x47, + 0x6d, 0x62, 0x48, 0x20, 0x4f, 0x55, 0x3d, 0x54, 0x43, 0x20, 0x54, 0x72, + 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x43, 0x6c, + 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x43, 0x41, 0x0a, 0x23, 0x20, 0x4c, + 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x54, 0x43, 0x20, 0x54, 0x72, + 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x43, 0x6c, + 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x43, 0x41, 0x20, 0x49, 0x49, 0x22, + 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x31, + 0x35, 0x30, 0x36, 0x35, 0x32, 0x33, 0x35, 0x31, 0x31, 0x34, 0x31, 0x37, + 0x37, 0x31, 0x35, 0x36, 0x33, 0x38, 0x37, 0x37, 0x32, 0x32, 0x32, 0x30, + 0x35, 0x33, 0x30, 0x30, 0x32, 0x30, 0x37, 0x39, 0x39, 0x0a, 0x23, 0x20, + 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, + 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x35, 0x36, 0x3a, 0x35, 0x66, 0x3a, 0x61, + 0x61, 0x3a, 0x38, 0x30, 0x3a, 0x36, 0x31, 0x3a, 0x31, 0x32, 0x3a, 0x31, + 0x37, 0x3a, 0x66, 0x36, 0x3a, 0x36, 0x37, 0x3a, 0x32, 0x31, 0x3a, 0x65, + 0x36, 0x3a, 0x32, 0x62, 0x3a, 0x36, 0x64, 0x3a, 0x36, 0x31, 0x3a, 0x35, + 0x36, 0x3a, 0x38, 0x65, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, + 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, + 0x20, 0x38, 0x30, 0x3a, 0x32, 0x35, 0x3a, 0x65, 0x66, 0x3a, 0x66, 0x34, + 0x3a, 0x36, 0x65, 0x3a, 0x37, 0x30, 0x3a, 0x63, 0x38, 0x3a, 0x64, 0x34, + 0x3a, 0x37, 0x32, 0x3a, 0x32, 0x34, 0x3a, 0x36, 0x35, 0x3a, 0x38, 0x34, + 0x3a, 0x66, 0x65, 0x3a, 0x34, 0x30, 0x3a, 0x33, 0x62, 0x3a, 0x38, 0x61, + 0x3a, 0x38, 0x64, 0x3a, 0x36, 0x61, 0x3a, 0x64, 0x62, 0x3a, 0x66, 0x35, + 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, + 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x38, + 0x64, 0x3a, 0x61, 0x30, 0x3a, 0x38, 0x34, 0x3a, 0x66, 0x63, 0x3a, 0x66, + 0x39, 0x3a, 0x39, 0x63, 0x3a, 0x65, 0x30, 0x3a, 0x37, 0x37, 0x3a, 0x32, + 0x32, 0x3a, 0x66, 0x38, 0x3a, 0x39, 0x62, 0x3a, 0x33, 0x32, 0x3a, 0x30, + 0x35, 0x3a, 0x39, 0x33, 0x3a, 0x39, 0x38, 0x3a, 0x30, 0x36, 0x3a, 0x66, + 0x61, 0x3a, 0x35, 0x63, 0x3a, 0x62, 0x38, 0x3a, 0x31, 0x31, 0x3a, 0x65, + 0x31, 0x3a, 0x63, 0x38, 0x3a, 0x31, 0x33, 0x3a, 0x66, 0x36, 0x3a, 0x61, + 0x31, 0x3a, 0x30, 0x38, 0x3a, 0x63, 0x37, 0x3a, 0x64, 0x33, 0x3a, 0x33, + 0x36, 0x3a, 0x62, 0x33, 0x3a, 0x34, 0x30, 0x3a, 0x38, 0x65, 0x0a, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, + 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x45, 0x71, 0x6a, 0x43, 0x43, 0x41, + 0x35, 0x4b, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x4f, 0x53, + 0x6b, 0x63, 0x41, 0x41, 0x51, 0x41, 0x43, 0x35, 0x61, 0x42, 0x64, 0x31, + 0x6a, 0x38, 0x41, 0x55, 0x62, 0x38, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, + 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x46, 0x42, + 0x51, 0x41, 0x77, 0x64, 0x6a, 0x45, 0x4c, 0x0a, 0x4d, 0x41, 0x6b, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x52, 0x45, 0x55, 0x78, + 0x48, 0x44, 0x41, 0x61, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, + 0x45, 0x31, 0x52, 0x44, 0x49, 0x46, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, + 0x51, 0x32, 0x56, 0x75, 0x64, 0x47, 0x56, 0x79, 0x49, 0x45, 0x64, 0x74, + 0x59, 0x6b, 0x67, 0x78, 0x49, 0x6a, 0x41, 0x67, 0x42, 0x67, 0x4e, 0x56, + 0x0a, 0x42, 0x41, 0x73, 0x54, 0x47, 0x56, 0x52, 0x44, 0x49, 0x46, 0x52, + 0x79, 0x64, 0x58, 0x4e, 0x30, 0x51, 0x32, 0x56, 0x75, 0x64, 0x47, 0x56, + 0x79, 0x49, 0x45, 0x4e, 0x73, 0x59, 0x58, 0x4e, 0x7a, 0x49, 0x44, 0x4d, + 0x67, 0x51, 0x30, 0x45, 0x78, 0x4a, 0x54, 0x41, 0x6a, 0x42, 0x67, 0x4e, + 0x56, 0x42, 0x41, 0x4d, 0x54, 0x48, 0x46, 0x52, 0x44, 0x49, 0x46, 0x52, + 0x79, 0x64, 0x58, 0x4e, 0x30, 0x0a, 0x51, 0x32, 0x56, 0x75, 0x64, 0x47, + 0x56, 0x79, 0x49, 0x45, 0x4e, 0x73, 0x59, 0x58, 0x4e, 0x7a, 0x49, 0x44, + 0x4d, 0x67, 0x51, 0x30, 0x45, 0x67, 0x53, 0x55, 0x6b, 0x77, 0x48, 0x68, + 0x63, 0x4e, 0x4d, 0x44, 0x59, 0x77, 0x4d, 0x54, 0x45, 0x79, 0x4d, 0x54, + 0x51, 0x30, 0x4d, 0x54, 0x55, 0x33, 0x57, 0x68, 0x63, 0x4e, 0x4d, 0x6a, + 0x55, 0x78, 0x4d, 0x6a, 0x4d, 0x78, 0x4d, 0x6a, 0x49, 0x31, 0x0a, 0x4f, + 0x54, 0x55, 0x35, 0x57, 0x6a, 0x42, 0x32, 0x4d, 0x51, 0x73, 0x77, 0x43, + 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x45, 0x52, + 0x54, 0x45, 0x63, 0x4d, 0x42, 0x6f, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, + 0x68, 0x4d, 0x54, 0x56, 0x45, 0x4d, 0x67, 0x56, 0x48, 0x4a, 0x31, 0x63, + 0x33, 0x52, 0x44, 0x5a, 0x57, 0x35, 0x30, 0x5a, 0x58, 0x49, 0x67, 0x52, + 0x32, 0x31, 0x69, 0x0a, 0x53, 0x44, 0x45, 0x69, 0x4d, 0x43, 0x41, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x43, 0x78, 0x4d, 0x5a, 0x56, 0x45, 0x4d, 0x67, + 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x52, 0x44, 0x5a, 0x57, 0x35, 0x30, + 0x5a, 0x58, 0x49, 0x67, 0x51, 0x32, 0x78, 0x68, 0x63, 0x33, 0x4d, 0x67, + 0x4d, 0x79, 0x42, 0x44, 0x51, 0x54, 0x45, 0x6c, 0x4d, 0x43, 0x4d, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x63, 0x0a, 0x56, 0x45, 0x4d, + 0x67, 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x52, 0x44, 0x5a, 0x57, 0x35, + 0x30, 0x5a, 0x58, 0x49, 0x67, 0x51, 0x32, 0x78, 0x68, 0x63, 0x33, 0x4d, + 0x67, 0x4d, 0x79, 0x42, 0x44, 0x51, 0x53, 0x42, 0x4a, 0x53, 0x54, 0x43, + 0x43, 0x41, 0x53, 0x49, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, + 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x42, 0x42, 0x51, 0x41, + 0x44, 0x0a, 0x67, 0x67, 0x45, 0x50, 0x41, 0x44, 0x43, 0x43, 0x41, 0x51, + 0x6f, 0x43, 0x67, 0x67, 0x45, 0x42, 0x41, 0x4c, 0x54, 0x67, 0x75, 0x31, + 0x47, 0x37, 0x4f, 0x56, 0x79, 0x4c, 0x42, 0x4d, 0x56, 0x4d, 0x65, 0x52, + 0x77, 0x6a, 0x68, 0x6a, 0x45, 0x51, 0x59, 0x30, 0x4e, 0x56, 0x4a, 0x7a, + 0x2f, 0x47, 0x52, 0x63, 0x65, 0x6b, 0x50, 0x65, 0x77, 0x4a, 0x44, 0x52, + 0x6f, 0x65, 0x49, 0x4d, 0x4a, 0x57, 0x0a, 0x48, 0x74, 0x34, 0x62, 0x4e, + 0x77, 0x63, 0x77, 0x49, 0x69, 0x39, 0x76, 0x38, 0x51, 0x62, 0x78, 0x71, + 0x36, 0x33, 0x57, 0x79, 0x4b, 0x74, 0x68, 0x6f, 0x79, 0x39, 0x44, 0x78, + 0x4c, 0x43, 0x79, 0x4c, 0x66, 0x7a, 0x44, 0x6c, 0x6d, 0x6c, 0x37, 0x66, + 0x6f, 0x72, 0x6b, 0x7a, 0x4d, 0x41, 0x35, 0x45, 0x70, 0x42, 0x43, 0x59, + 0x4d, 0x6e, 0x4d, 0x4e, 0x57, 0x6a, 0x75, 0x32, 0x6c, 0x2b, 0x51, 0x0a, + 0x56, 0x6c, 0x2f, 0x4e, 0x48, 0x45, 0x31, 0x62, 0x57, 0x45, 0x6e, 0x72, + 0x44, 0x67, 0x46, 0x50, 0x5a, 0x50, 0x6f, 0x73, 0x50, 0x49, 0x6c, 0x59, + 0x32, 0x43, 0x38, 0x75, 0x34, 0x72, 0x42, 0x6f, 0x36, 0x53, 0x49, 0x37, + 0x64, 0x59, 0x6e, 0x57, 0x52, 0x42, 0x70, 0x6c, 0x38, 0x68, 0x75, 0x58, + 0x4a, 0x68, 0x30, 0x6f, 0x62, 0x61, 0x7a, 0x6f, 0x76, 0x56, 0x6b, 0x64, + 0x4b, 0x79, 0x54, 0x32, 0x0a, 0x31, 0x6f, 0x51, 0x44, 0x5a, 0x6f, 0x67, + 0x6b, 0x41, 0x48, 0x68, 0x67, 0x38, 0x66, 0x69, 0x72, 0x2f, 0x67, 0x4b, + 0x79, 0x61, 0x2f, 0x73, 0x69, 0x2b, 0x7a, 0x58, 0x6d, 0x46, 0x74, 0x47, + 0x74, 0x39, 0x69, 0x34, 0x53, 0x35, 0x50, 0x6f, 0x31, 0x61, 0x75, 0x55, + 0x5a, 0x75, 0x56, 0x33, 0x62, 0x4f, 0x78, 0x34, 0x61, 0x2b, 0x39, 0x50, + 0x2f, 0x46, 0x52, 0x51, 0x49, 0x32, 0x41, 0x6c, 0x71, 0x0a, 0x75, 0x6b, + 0x57, 0x64, 0x46, 0x48, 0x6c, 0x67, 0x66, 0x61, 0x39, 0x41, 0x69, 0x67, + 0x64, 0x7a, 0x73, 0x35, 0x4f, 0x57, 0x30, 0x33, 0x51, 0x30, 0x6a, 0x54, + 0x6f, 0x33, 0x4b, 0x64, 0x35, 0x63, 0x37, 0x50, 0x58, 0x75, 0x4c, 0x6a, + 0x48, 0x43, 0x49, 0x4e, 0x79, 0x2b, 0x38, 0x55, 0x39, 0x2f, 0x49, 0x31, + 0x4c, 0x5a, 0x57, 0x2b, 0x4a, 0x6b, 0x32, 0x5a, 0x79, 0x71, 0x42, 0x77, + 0x69, 0x31, 0x0a, 0x52, 0x62, 0x33, 0x52, 0x30, 0x44, 0x48, 0x42, 0x71, + 0x31, 0x53, 0x66, 0x71, 0x64, 0x4c, 0x44, 0x59, 0x6d, 0x41, 0x44, 0x38, + 0x62, 0x73, 0x35, 0x53, 0x70, 0x4a, 0x4b, 0x50, 0x51, 0x71, 0x35, 0x6e, + 0x63, 0x57, 0x67, 0x2f, 0x6a, 0x63, 0x43, 0x41, 0x77, 0x45, 0x41, 0x41, + 0x61, 0x4f, 0x43, 0x41, 0x54, 0x51, 0x77, 0x67, 0x67, 0x45, 0x77, 0x4d, + 0x41, 0x38, 0x47, 0x41, 0x31, 0x55, 0x64, 0x0a, 0x45, 0x77, 0x45, 0x42, + 0x2f, 0x77, 0x51, 0x46, 0x4d, 0x41, 0x4d, 0x42, 0x41, 0x66, 0x38, 0x77, + 0x44, 0x67, 0x59, 0x44, 0x56, 0x52, 0x30, 0x50, 0x41, 0x51, 0x48, 0x2f, + 0x42, 0x41, 0x51, 0x44, 0x41, 0x67, 0x45, 0x47, 0x4d, 0x42, 0x30, 0x47, + 0x41, 0x31, 0x55, 0x64, 0x44, 0x67, 0x51, 0x57, 0x42, 0x42, 0x54, 0x55, + 0x6f, 0x76, 0x79, 0x66, 0x73, 0x38, 0x50, 0x59, 0x41, 0x39, 0x4e, 0x58, + 0x0a, 0x58, 0x41, 0x65, 0x6b, 0x30, 0x43, 0x53, 0x6e, 0x77, 0x50, 0x49, + 0x41, 0x31, 0x44, 0x43, 0x42, 0x37, 0x51, 0x59, 0x44, 0x56, 0x52, 0x30, + 0x66, 0x42, 0x49, 0x48, 0x6c, 0x4d, 0x49, 0x48, 0x69, 0x4d, 0x49, 0x48, + 0x66, 0x6f, 0x49, 0x48, 0x63, 0x6f, 0x49, 0x48, 0x5a, 0x68, 0x6a, 0x56, + 0x6f, 0x64, 0x48, 0x52, 0x77, 0x4f, 0x69, 0x38, 0x76, 0x64, 0x33, 0x64, + 0x33, 0x4c, 0x6e, 0x52, 0x79, 0x0a, 0x64, 0x58, 0x4e, 0x30, 0x59, 0x32, + 0x56, 0x75, 0x64, 0x47, 0x56, 0x79, 0x4c, 0x6d, 0x52, 0x6c, 0x4c, 0x32, + 0x4e, 0x79, 0x62, 0x43, 0x39, 0x32, 0x4d, 0x69, 0x39, 0x30, 0x59, 0x31, + 0x39, 0x6a, 0x62, 0x47, 0x46, 0x7a, 0x63, 0x31, 0x38, 0x7a, 0x58, 0x32, + 0x4e, 0x68, 0x58, 0x30, 0x6c, 0x4a, 0x4c, 0x6d, 0x4e, 0x79, 0x62, 0x49, + 0x61, 0x42, 0x6e, 0x32, 0x78, 0x6b, 0x59, 0x58, 0x41, 0x36, 0x0a, 0x4c, + 0x79, 0x39, 0x33, 0x64, 0x33, 0x63, 0x75, 0x64, 0x48, 0x4a, 0x31, 0x63, + 0x33, 0x52, 0x6a, 0x5a, 0x57, 0x35, 0x30, 0x5a, 0x58, 0x49, 0x75, 0x5a, + 0x47, 0x55, 0x76, 0x51, 0x30, 0x34, 0x39, 0x56, 0x45, 0x4d, 0x6c, 0x4d, + 0x6a, 0x42, 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x45, 0x4e, 0x6c, 0x62, + 0x6e, 0x52, 0x6c, 0x63, 0x69, 0x55, 0x79, 0x4d, 0x45, 0x4e, 0x73, 0x59, + 0x58, 0x4e, 0x7a, 0x0a, 0x4a, 0x54, 0x49, 0x77, 0x4d, 0x79, 0x55, 0x79, + 0x4d, 0x45, 0x4e, 0x42, 0x4a, 0x54, 0x49, 0x77, 0x53, 0x55, 0x6b, 0x73, + 0x54, 0x7a, 0x31, 0x55, 0x51, 0x79, 0x55, 0x79, 0x4d, 0x46, 0x52, 0x79, + 0x64, 0x58, 0x4e, 0x30, 0x51, 0x32, 0x56, 0x75, 0x64, 0x47, 0x56, 0x79, + 0x4a, 0x54, 0x49, 0x77, 0x52, 0x32, 0x31, 0x69, 0x53, 0x43, 0x78, 0x50, + 0x56, 0x54, 0x31, 0x79, 0x62, 0x32, 0x39, 0x30, 0x0a, 0x59, 0x32, 0x56, + 0x79, 0x64, 0x48, 0x4d, 0x73, 0x52, 0x45, 0x4d, 0x39, 0x64, 0x48, 0x4a, + 0x31, 0x63, 0x33, 0x52, 0x6a, 0x5a, 0x57, 0x35, 0x30, 0x5a, 0x58, 0x49, + 0x73, 0x52, 0x45, 0x4d, 0x39, 0x5a, 0x47, 0x55, 0x2f, 0x59, 0x32, 0x56, + 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, 0x56, + 0x53, 0x5a, 0x58, 0x5a, 0x76, 0x59, 0x32, 0x46, 0x30, 0x61, 0x57, 0x39, + 0x75, 0x0a, 0x54, 0x47, 0x6c, 0x7a, 0x64, 0x44, 0x39, 0x69, 0x59, 0x58, + 0x4e, 0x6c, 0x50, 0x7a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, + 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x41, + 0x4f, 0x43, 0x41, 0x51, 0x45, 0x41, 0x4e, 0x6d, 0x44, 0x6b, 0x63, 0x50, + 0x63, 0x47, 0x49, 0x45, 0x50, 0x5a, 0x49, 0x78, 0x70, 0x43, 0x38, 0x76, + 0x69, 0x6a, 0x73, 0x72, 0x6c, 0x4e, 0x0a, 0x69, 0x72, 0x54, 0x7a, 0x77, + 0x70, 0x70, 0x56, 0x4d, 0x58, 0x7a, 0x45, 0x4f, 0x32, 0x65, 0x61, 0x74, + 0x4e, 0x39, 0x4e, 0x44, 0x6f, 0x71, 0x54, 0x53, 0x68, 0x65, 0x4c, 0x47, + 0x34, 0x33, 0x4b, 0x69, 0x65, 0x48, 0x50, 0x4f, 0x68, 0x36, 0x73, 0x48, + 0x66, 0x47, 0x63, 0x4d, 0x72, 0x53, 0x4f, 0x57, 0x58, 0x61, 0x69, 0x51, + 0x59, 0x55, 0x6c, 0x4e, 0x36, 0x41, 0x54, 0x30, 0x50, 0x56, 0x38, 0x0a, + 0x54, 0x74, 0x58, 0x71, 0x6c, 0x75, 0x4a, 0x75, 0x63, 0x73, 0x47, 0x37, + 0x4b, 0x76, 0x35, 0x73, 0x62, 0x76, 0x69, 0x52, 0x6d, 0x45, 0x62, 0x38, + 0x79, 0x52, 0x74, 0x58, 0x57, 0x2b, 0x72, 0x49, 0x47, 0x6a, 0x73, 0x2f, + 0x73, 0x46, 0x47, 0x59, 0x50, 0x41, 0x66, 0x61, 0x4c, 0x46, 0x6b, 0x42, + 0x32, 0x6f, 0x74, 0x45, 0x36, 0x4f, 0x46, 0x30, 0x2f, 0x61, 0x64, 0x6f, + 0x33, 0x56, 0x53, 0x36, 0x0a, 0x67, 0x30, 0x62, 0x73, 0x79, 0x45, 0x61, + 0x31, 0x2b, 0x4b, 0x2b, 0x58, 0x77, 0x44, 0x73, 0x4a, 0x48, 0x49, 0x2f, + 0x4f, 0x63, 0x70, 0x59, 0x39, 0x4d, 0x31, 0x5a, 0x77, 0x76, 0x4a, 0x62, + 0x4c, 0x32, 0x4e, 0x56, 0x39, 0x49, 0x4a, 0x71, 0x44, 0x6e, 0x78, 0x72, + 0x63, 0x4f, 0x66, 0x48, 0x46, 0x63, 0x71, 0x4d, 0x52, 0x41, 0x2f, 0x30, + 0x37, 0x51, 0x6c, 0x49, 0x70, 0x32, 0x2b, 0x67, 0x42, 0x0a, 0x39, 0x35, + 0x74, 0x65, 0x6a, 0x4e, 0x61, 0x4e, 0x68, 0x6b, 0x34, 0x5a, 0x2b, 0x72, + 0x77, 0x63, 0x76, 0x73, 0x55, 0x68, 0x70, 0x59, 0x65, 0x65, 0x65, 0x43, + 0x34, 0x32, 0x32, 0x77, 0x6c, 0x78, 0x6f, 0x33, 0x49, 0x30, 0x2b, 0x47, + 0x7a, 0x6a, 0x42, 0x67, 0x6e, 0x79, 0x58, 0x6c, 0x61, 0x6c, 0x30, 0x39, + 0x32, 0x59, 0x2b, 0x74, 0x54, 0x6d, 0x42, 0x76, 0x54, 0x77, 0x74, 0x69, + 0x42, 0x6a, 0x0a, 0x53, 0x2b, 0x6f, 0x70, 0x76, 0x61, 0x71, 0x43, 0x5a, + 0x68, 0x37, 0x37, 0x67, 0x61, 0x71, 0x6e, 0x4e, 0x36, 0x30, 0x54, 0x47, + 0x4f, 0x61, 0x53, 0x77, 0x34, 0x48, 0x42, 0x4d, 0x37, 0x75, 0x49, 0x48, + 0x71, 0x48, 0x6e, 0x34, 0x72, 0x53, 0x39, 0x4d, 0x57, 0x77, 0x4f, 0x55, + 0x54, 0x31, 0x76, 0x2b, 0x35, 0x5a, 0x57, 0x67, 0x4f, 0x49, 0x32, 0x46, + 0x39, 0x48, 0x63, 0x35, 0x41, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, + 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, + 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, + 0x54, 0x43, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, + 0x65, 0x72, 0x20, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, + 0x20, 0x43, 0x41, 0x20, 0x49, 0x20, 0x4f, 0x3d, 0x54, 0x43, 0x20, 0x54, + 0x72, 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x47, + 0x6d, 0x62, 0x48, 0x20, 0x4f, 0x55, 0x3d, 0x54, 0x43, 0x20, 0x54, 0x72, + 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x55, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x0a, 0x23, + 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, + 0x3d, 0x54, 0x43, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, + 0x74, 0x65, 0x72, 0x20, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, + 0x6c, 0x20, 0x43, 0x41, 0x20, 0x49, 0x20, 0x4f, 0x3d, 0x54, 0x43, 0x20, + 0x54, 0x72, 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, + 0x47, 0x6d, 0x62, 0x48, 0x20, 0x4f, 0x55, 0x3d, 0x54, 0x43, 0x20, 0x54, + 0x72, 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x55, + 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x0a, + 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x54, 0x43, + 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, + 0x20, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x20, 0x43, + 0x41, 0x20, 0x49, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, + 0x6c, 0x3a, 0x20, 0x36, 0x30, 0x31, 0x30, 0x32, 0x34, 0x38, 0x34, 0x32, + 0x30, 0x34, 0x32, 0x31, 0x38, 0x39, 0x30, 0x33, 0x35, 0x32, 0x39, 0x35, + 0x36, 0x31, 0x39, 0x35, 0x38, 0x34, 0x37, 0x33, 0x34, 0x37, 0x32, 0x36, + 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x34, 0x35, 0x3a, 0x65, + 0x31, 0x3a, 0x61, 0x35, 0x3a, 0x37, 0x32, 0x3a, 0x63, 0x35, 0x3a, 0x61, + 0x39, 0x3a, 0x33, 0x36, 0x3a, 0x36, 0x34, 0x3a, 0x34, 0x30, 0x3a, 0x39, + 0x65, 0x3a, 0x66, 0x35, 0x3a, 0x65, 0x34, 0x3a, 0x35, 0x38, 0x3a, 0x38, + 0x34, 0x3a, 0x36, 0x37, 0x3a, 0x38, 0x63, 0x0a, 0x23, 0x20, 0x53, 0x48, + 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x3a, 0x20, 0x36, 0x62, 0x3a, 0x32, 0x66, 0x3a, 0x33, 0x34, + 0x3a, 0x61, 0x64, 0x3a, 0x38, 0x39, 0x3a, 0x35, 0x38, 0x3a, 0x62, 0x65, + 0x3a, 0x36, 0x32, 0x3a, 0x66, 0x64, 0x3a, 0x62, 0x30, 0x3a, 0x36, 0x62, + 0x3a, 0x35, 0x63, 0x3a, 0x63, 0x65, 0x3a, 0x62, 0x62, 0x3a, 0x39, 0x64, + 0x3a, 0x64, 0x39, 0x3a, 0x34, 0x66, 0x3a, 0x34, 0x65, 0x3a, 0x33, 0x39, + 0x3a, 0x66, 0x33, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, + 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, + 0x3a, 0x20, 0x65, 0x62, 0x3a, 0x66, 0x33, 0x3a, 0x63, 0x30, 0x3a, 0x32, + 0x61, 0x3a, 0x38, 0x37, 0x3a, 0x38, 0x39, 0x3a, 0x62, 0x31, 0x3a, 0x66, + 0x62, 0x3a, 0x37, 0x64, 0x3a, 0x35, 0x31, 0x3a, 0x31, 0x39, 0x3a, 0x39, + 0x35, 0x3a, 0x64, 0x36, 0x3a, 0x36, 0x33, 0x3a, 0x62, 0x37, 0x3a, 0x32, + 0x39, 0x3a, 0x30, 0x36, 0x3a, 0x64, 0x39, 0x3a, 0x31, 0x33, 0x3a, 0x63, + 0x65, 0x3a, 0x30, 0x64, 0x3a, 0x35, 0x65, 0x3a, 0x31, 0x30, 0x3a, 0x35, + 0x36, 0x3a, 0x38, 0x61, 0x3a, 0x38, 0x61, 0x3a, 0x37, 0x37, 0x3a, 0x65, + 0x32, 0x3a, 0x35, 0x38, 0x3a, 0x36, 0x31, 0x3a, 0x36, 0x37, 0x3a, 0x65, + 0x37, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, + 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x44, 0x33, 0x54, + 0x43, 0x43, 0x41, 0x73, 0x57, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, + 0x49, 0x4f, 0x48, 0x61, 0x49, 0x41, 0x41, 0x51, 0x41, 0x43, 0x37, 0x4c, + 0x64, 0x67, 0x67, 0x48, 0x69, 0x4e, 0x74, 0x67, 0x59, 0x77, 0x44, 0x51, + 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, + 0x45, 0x46, 0x42, 0x51, 0x41, 0x77, 0x65, 0x54, 0x45, 0x4c, 0x0a, 0x4d, + 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x52, + 0x45, 0x55, 0x78, 0x48, 0x44, 0x41, 0x61, 0x42, 0x67, 0x4e, 0x56, 0x42, + 0x41, 0x6f, 0x54, 0x45, 0x31, 0x52, 0x44, 0x49, 0x46, 0x52, 0x79, 0x64, + 0x58, 0x4e, 0x30, 0x51, 0x32, 0x56, 0x75, 0x64, 0x47, 0x56, 0x79, 0x49, + 0x45, 0x64, 0x74, 0x59, 0x6b, 0x67, 0x78, 0x4a, 0x44, 0x41, 0x69, 0x42, + 0x67, 0x4e, 0x56, 0x0a, 0x42, 0x41, 0x73, 0x54, 0x47, 0x31, 0x52, 0x44, + 0x49, 0x46, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x51, 0x32, 0x56, 0x75, + 0x64, 0x47, 0x56, 0x79, 0x49, 0x46, 0x56, 0x75, 0x61, 0x58, 0x5a, 0x6c, + 0x63, 0x6e, 0x4e, 0x68, 0x62, 0x43, 0x42, 0x44, 0x51, 0x54, 0x45, 0x6d, + 0x4d, 0x43, 0x51, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x64, + 0x56, 0x45, 0x4d, 0x67, 0x56, 0x48, 0x4a, 0x31, 0x0a, 0x63, 0x33, 0x52, + 0x44, 0x5a, 0x57, 0x35, 0x30, 0x5a, 0x58, 0x49, 0x67, 0x56, 0x57, 0x35, + 0x70, 0x64, 0x6d, 0x56, 0x79, 0x63, 0x32, 0x46, 0x73, 0x49, 0x45, 0x4e, + 0x42, 0x49, 0x45, 0x6b, 0x77, 0x48, 0x68, 0x63, 0x4e, 0x4d, 0x44, 0x59, + 0x77, 0x4d, 0x7a, 0x49, 0x79, 0x4d, 0x54, 0x55, 0x31, 0x4e, 0x44, 0x49, + 0x34, 0x57, 0x68, 0x63, 0x4e, 0x4d, 0x6a, 0x55, 0x78, 0x4d, 0x6a, 0x4d, + 0x78, 0x0a, 0x4d, 0x6a, 0x49, 0x31, 0x4f, 0x54, 0x55, 0x35, 0x57, 0x6a, + 0x42, 0x35, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, + 0x51, 0x47, 0x45, 0x77, 0x4a, 0x45, 0x52, 0x54, 0x45, 0x63, 0x4d, 0x42, + 0x6f, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x54, 0x56, 0x45, + 0x4d, 0x67, 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x52, 0x44, 0x5a, 0x57, + 0x35, 0x30, 0x5a, 0x58, 0x49, 0x67, 0x0a, 0x52, 0x32, 0x31, 0x69, 0x53, + 0x44, 0x45, 0x6b, 0x4d, 0x43, 0x49, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, + 0x78, 0x4d, 0x62, 0x56, 0x45, 0x4d, 0x67, 0x56, 0x48, 0x4a, 0x31, 0x63, + 0x33, 0x52, 0x44, 0x5a, 0x57, 0x35, 0x30, 0x5a, 0x58, 0x49, 0x67, 0x56, + 0x57, 0x35, 0x70, 0x64, 0x6d, 0x56, 0x79, 0x63, 0x32, 0x46, 0x73, 0x49, + 0x45, 0x4e, 0x42, 0x4d, 0x53, 0x59, 0x77, 0x4a, 0x41, 0x59, 0x44, 0x0a, + 0x56, 0x51, 0x51, 0x44, 0x45, 0x78, 0x31, 0x55, 0x51, 0x79, 0x42, 0x55, + 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x45, 0x4e, 0x6c, 0x62, 0x6e, 0x52, 0x6c, + 0x63, 0x69, 0x42, 0x56, 0x62, 0x6d, 0x6c, 0x32, 0x5a, 0x58, 0x4a, 0x7a, + 0x59, 0x57, 0x77, 0x67, 0x51, 0x30, 0x45, 0x67, 0x53, 0x54, 0x43, 0x43, + 0x41, 0x53, 0x49, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, + 0x68, 0x76, 0x63, 0x4e, 0x0a, 0x41, 0x51, 0x45, 0x42, 0x42, 0x51, 0x41, + 0x44, 0x67, 0x67, 0x45, 0x50, 0x41, 0x44, 0x43, 0x43, 0x41, 0x51, 0x6f, + 0x43, 0x67, 0x67, 0x45, 0x42, 0x41, 0x4b, 0x52, 0x33, 0x49, 0x35, 0x5a, + 0x45, 0x72, 0x35, 0x44, 0x30, 0x4d, 0x61, 0x63, 0x51, 0x39, 0x43, 0x61, + 0x48, 0x6e, 0x50, 0x4d, 0x34, 0x32, 0x51, 0x39, 0x65, 0x33, 0x73, 0x39, + 0x42, 0x36, 0x44, 0x47, 0x74, 0x78, 0x6e, 0x53, 0x52, 0x0a, 0x4a, 0x4a, + 0x5a, 0x34, 0x48, 0x67, 0x6d, 0x67, 0x6d, 0x35, 0x71, 0x56, 0x53, 0x6b, + 0x72, 0x31, 0x59, 0x6e, 0x77, 0x43, 0x71, 0x4d, 0x71, 0x73, 0x2b, 0x31, + 0x6f, 0x45, 0x64, 0x6a, 0x6e, 0x65, 0x58, 0x2f, 0x48, 0x35, 0x73, 0x37, + 0x2f, 0x7a, 0x41, 0x31, 0x68, 0x56, 0x30, 0x71, 0x71, 0x33, 0x34, 0x77, + 0x51, 0x69, 0x30, 0x66, 0x69, 0x55, 0x32, 0x69, 0x49, 0x49, 0x41, 0x49, + 0x33, 0x54, 0x0a, 0x66, 0x43, 0x5a, 0x64, 0x7a, 0x48, 0x64, 0x35, 0x35, + 0x79, 0x78, 0x34, 0x4f, 0x61, 0x67, 0x6d, 0x63, 0x77, 0x36, 0x69, 0x58, + 0x53, 0x56, 0x70, 0x68, 0x55, 0x39, 0x56, 0x44, 0x70, 0x72, 0x76, 0x78, + 0x72, 0x6c, 0x45, 0x34, 0x56, 0x63, 0x39, 0x33, 0x78, 0x39, 0x55, 0x49, + 0x75, 0x56, 0x76, 0x5a, 0x61, 0x6f, 0x7a, 0x68, 0x44, 0x72, 0x7a, 0x7a, + 0x6e, 0x71, 0x2b, 0x56, 0x5a, 0x65, 0x75, 0x0a, 0x6a, 0x52, 0x49, 0x50, + 0x46, 0x44, 0x50, 0x69, 0x55, 0x48, 0x44, 0x44, 0x53, 0x59, 0x63, 0x54, + 0x76, 0x46, 0x48, 0x65, 0x31, 0x35, 0x67, 0x53, 0x57, 0x75, 0x38, 0x36, + 0x67, 0x7a, 0x4f, 0x53, 0x42, 0x6e, 0x57, 0x4c, 0x6b, 0x6e, 0x77, 0x53, + 0x61, 0x48, 0x74, 0x77, 0x61, 0x67, 0x2b, 0x31, 0x6d, 0x37, 0x5a, 0x33, + 0x57, 0x30, 0x68, 0x5a, 0x6e, 0x65, 0x54, 0x76, 0x57, 0x71, 0x33, 0x7a, + 0x0a, 0x77, 0x5a, 0x37, 0x55, 0x31, 0x30, 0x56, 0x4f, 0x79, 0x6c, 0x59, + 0x30, 0x49, 0x62, 0x77, 0x2b, 0x46, 0x31, 0x74, 0x76, 0x64, 0x77, 0x78, + 0x49, 0x41, 0x55, 0x4d, 0x70, 0x73, 0x4e, 0x30, 0x2f, 0x6c, 0x6d, 0x37, + 0x6d, 0x6c, 0x61, 0x6f, 0x4d, 0x77, 0x43, 0x43, 0x32, 0x2f, 0x54, 0x34, + 0x32, 0x4a, 0x35, 0x7a, 0x6a, 0x58, 0x4d, 0x39, 0x4f, 0x67, 0x64, 0x77, + 0x5a, 0x75, 0x35, 0x47, 0x51, 0x0a, 0x66, 0x65, 0x7a, 0x6d, 0x6c, 0x77, + 0x51, 0x65, 0x6b, 0x38, 0x77, 0x69, 0x53, 0x64, 0x65, 0x58, 0x68, 0x72, + 0x59, 0x54, 0x43, 0x6a, 0x78, 0x44, 0x49, 0x33, 0x64, 0x2b, 0x38, 0x4e, + 0x7a, 0x6d, 0x7a, 0x53, 0x51, 0x66, 0x4f, 0x34, 0x4f, 0x62, 0x4e, 0x44, + 0x71, 0x44, 0x4e, 0x4f, 0x4d, 0x43, 0x41, 0x77, 0x45, 0x41, 0x41, 0x61, + 0x4e, 0x6a, 0x4d, 0x47, 0x45, 0x77, 0x48, 0x77, 0x59, 0x44, 0x0a, 0x56, + 0x52, 0x30, 0x6a, 0x42, 0x42, 0x67, 0x77, 0x46, 0x6f, 0x41, 0x55, 0x6b, + 0x71, 0x52, 0x31, 0x4c, 0x4b, 0x53, 0x65, 0x76, 0x6f, 0x46, 0x45, 0x36, + 0x33, 0x6e, 0x38, 0x69, 0x73, 0x57, 0x56, 0x70, 0x65, 0x73, 0x51, 0x64, + 0x58, 0x4d, 0x77, 0x44, 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, 0x54, 0x41, + 0x51, 0x48, 0x2f, 0x42, 0x41, 0x55, 0x77, 0x41, 0x77, 0x45, 0x42, 0x2f, + 0x7a, 0x41, 0x4f, 0x0a, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x38, 0x42, + 0x41, 0x66, 0x38, 0x45, 0x42, 0x41, 0x4d, 0x43, 0x41, 0x59, 0x59, 0x77, + 0x48, 0x51, 0x59, 0x44, 0x56, 0x52, 0x30, 0x4f, 0x42, 0x42, 0x59, 0x45, + 0x46, 0x4a, 0x4b, 0x6b, 0x64, 0x53, 0x79, 0x6b, 0x6e, 0x72, 0x36, 0x42, + 0x52, 0x4f, 0x74, 0x35, 0x2f, 0x49, 0x72, 0x46, 0x6c, 0x61, 0x58, 0x72, + 0x45, 0x48, 0x56, 0x7a, 0x4d, 0x41, 0x30, 0x47, 0x0a, 0x43, 0x53, 0x71, + 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x42, 0x51, 0x55, + 0x41, 0x41, 0x34, 0x49, 0x42, 0x41, 0x51, 0x41, 0x6f, 0x30, 0x75, 0x43, + 0x47, 0x31, 0x65, 0x62, 0x34, 0x65, 0x2f, 0x43, 0x58, 0x33, 0x43, 0x4a, + 0x72, 0x4f, 0x35, 0x55, 0x55, 0x56, 0x67, 0x38, 0x52, 0x4d, 0x4b, 0x57, + 0x61, 0x54, 0x7a, 0x71, 0x77, 0x4f, 0x75, 0x41, 0x47, 0x79, 0x32, 0x58, + 0x31, 0x0a, 0x37, 0x63, 0x61, 0x58, 0x4a, 0x2f, 0x34, 0x6c, 0x38, 0x6c, + 0x66, 0x6d, 0x58, 0x70, 0x57, 0x4d, 0x50, 0x6d, 0x52, 0x67, 0x46, 0x56, + 0x70, 0x2f, 0x4c, 0x77, 0x30, 0x42, 0x78, 0x62, 0x46, 0x67, 0x2f, 0x55, + 0x55, 0x31, 0x7a, 0x2f, 0x43, 0x79, 0x76, 0x77, 0x62, 0x5a, 0x37, 0x31, + 0x71, 0x2b, 0x73, 0x32, 0x49, 0x68, 0x74, 0x4e, 0x65, 0x72, 0x4e, 0x58, + 0x78, 0x54, 0x50, 0x71, 0x59, 0x6e, 0x0a, 0x38, 0x61, 0x45, 0x74, 0x32, + 0x68, 0x6f, 0x6a, 0x6e, 0x63, 0x7a, 0x64, 0x37, 0x44, 0x77, 0x74, 0x6e, + 0x69, 0x63, 0x30, 0x58, 0x51, 0x2f, 0x43, 0x4e, 0x6e, 0x6d, 0x38, 0x79, + 0x55, 0x70, 0x69, 0x4c, 0x65, 0x31, 0x72, 0x32, 0x58, 0x31, 0x42, 0x51, + 0x33, 0x79, 0x32, 0x71, 0x73, 0x72, 0x74, 0x59, 0x62, 0x45, 0x33, 0x67, + 0x68, 0x55, 0x4a, 0x47, 0x6f, 0x6f, 0x57, 0x4d, 0x4e, 0x6a, 0x73, 0x0a, + 0x79, 0x64, 0x5a, 0x48, 0x63, 0x6e, 0x68, 0x4c, 0x45, 0x45, 0x59, 0x55, + 0x6a, 0x6c, 0x38, 0x4f, 0x72, 0x2b, 0x7a, 0x48, 0x4c, 0x36, 0x73, 0x51, + 0x31, 0x37, 0x62, 0x78, 0x62, 0x75, 0x79, 0x47, 0x73, 0x73, 0x4c, 0x6f, + 0x44, 0x5a, 0x4a, 0x7a, 0x33, 0x4b, 0x4c, 0x30, 0x44, 0x7a, 0x71, 0x2f, + 0x59, 0x53, 0x4d, 0x51, 0x69, 0x5a, 0x78, 0x49, 0x51, 0x47, 0x35, 0x77, + 0x41, 0x4c, 0x50, 0x54, 0x0a, 0x75, 0x6a, 0x64, 0x45, 0x57, 0x42, 0x46, + 0x36, 0x41, 0x6d, 0x71, 0x49, 0x38, 0x44, 0x63, 0x30, 0x38, 0x42, 0x6e, + 0x70, 0x72, 0x4e, 0x52, 0x6c, 0x63, 0x2f, 0x5a, 0x70, 0x6a, 0x47, 0x53, + 0x55, 0x4f, 0x6e, 0x6d, 0x46, 0x4b, 0x62, 0x41, 0x57, 0x4b, 0x77, 0x79, + 0x43, 0x50, 0x77, 0x61, 0x63, 0x78, 0x2f, 0x30, 0x51, 0x4b, 0x35, 0x34, + 0x50, 0x4c, 0x4c, 0x61, 0x65, 0x34, 0x78, 0x57, 0x2f, 0x0a, 0x32, 0x54, + 0x59, 0x63, 0x75, 0x69, 0x55, 0x61, 0x55, 0x6a, 0x30, 0x61, 0x37, 0x43, + 0x49, 0x4d, 0x48, 0x4f, 0x43, 0x6b, 0x6f, 0x6a, 0x33, 0x77, 0x36, 0x44, + 0x6e, 0x50, 0x67, 0x63, 0x42, 0x37, 0x37, 0x56, 0x30, 0x66, 0x62, 0x38, + 0x58, 0x51, 0x43, 0x39, 0x65, 0x59, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, + 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, + 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x43, + 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, + 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x4f, 0x3d, + 0x43, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, + 0x49, 0x6e, 0x63, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x43, 0x79, 0x62, 0x65, 0x72, 0x74, + 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, + 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x4f, 0x3d, 0x43, 0x79, 0x62, 0x65, 0x72, + 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x0a, 0x23, + 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x43, 0x79, 0x62, + 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, + 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x22, 0x0a, 0x23, 0x20, 0x53, + 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x34, 0x38, 0x33, 0x35, 0x37, + 0x30, 0x33, 0x32, 0x37, 0x38, 0x34, 0x35, 0x39, 0x36, 0x38, 0x32, 0x38, + 0x37, 0x37, 0x34, 0x38, 0x34, 0x33, 0x36, 0x30, 0x0a, 0x23, 0x20, 0x4d, + 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x3a, 0x20, 0x37, 0x32, 0x3a, 0x65, 0x34, 0x3a, 0x34, 0x61, + 0x3a, 0x38, 0x37, 0x3a, 0x65, 0x33, 0x3a, 0x36, 0x39, 0x3a, 0x34, 0x30, + 0x3a, 0x38, 0x30, 0x3a, 0x37, 0x37, 0x3a, 0x65, 0x61, 0x3a, 0x62, 0x63, + 0x3a, 0x65, 0x33, 0x3a, 0x66, 0x34, 0x3a, 0x66, 0x66, 0x3a, 0x66, 0x30, + 0x3a, 0x65, 0x31, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x35, 0x66, 0x3a, 0x34, 0x33, 0x3a, 0x65, 0x35, 0x3a, 0x62, 0x31, 0x3a, + 0x62, 0x66, 0x3a, 0x66, 0x38, 0x3a, 0x37, 0x38, 0x3a, 0x38, 0x63, 0x3a, + 0x61, 0x63, 0x3a, 0x31, 0x63, 0x3a, 0x63, 0x37, 0x3a, 0x63, 0x61, 0x3a, + 0x34, 0x61, 0x3a, 0x39, 0x61, 0x3a, 0x63, 0x36, 0x3a, 0x32, 0x32, 0x3a, + 0x32, 0x62, 0x3a, 0x63, 0x63, 0x3a, 0x33, 0x34, 0x3a, 0x63, 0x36, 0x0a, + 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, + 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x39, 0x36, + 0x3a, 0x30, 0x61, 0x3a, 0x64, 0x66, 0x3a, 0x30, 0x30, 0x3a, 0x36, 0x33, + 0x3a, 0x65, 0x39, 0x3a, 0x36, 0x33, 0x3a, 0x35, 0x36, 0x3a, 0x37, 0x35, + 0x3a, 0x30, 0x63, 0x3a, 0x32, 0x39, 0x3a, 0x36, 0x35, 0x3a, 0x64, 0x64, + 0x3a, 0x30, 0x61, 0x3a, 0x30, 0x38, 0x3a, 0x36, 0x37, 0x3a, 0x64, 0x61, + 0x3a, 0x30, 0x62, 0x3a, 0x39, 0x63, 0x3a, 0x62, 0x64, 0x3a, 0x36, 0x65, + 0x3a, 0x37, 0x37, 0x3a, 0x37, 0x31, 0x3a, 0x34, 0x61, 0x3a, 0x65, 0x61, + 0x3a, 0x66, 0x62, 0x3a, 0x32, 0x33, 0x3a, 0x34, 0x39, 0x3a, 0x61, 0x62, + 0x3a, 0x33, 0x39, 0x3a, 0x33, 0x64, 0x3a, 0x61, 0x33, 0x0a, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, + 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x44, 0x6f, 0x54, 0x43, 0x43, 0x41, 0x6f, + 0x6d, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x4c, 0x42, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x42, 0x44, 0x34, 0x57, 0x71, 0x4c, 0x55, + 0x67, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, + 0x63, 0x4e, 0x41, 0x51, 0x45, 0x46, 0x42, 0x51, 0x41, 0x77, 0x4f, 0x7a, + 0x45, 0x59, 0x4d, 0x42, 0x59, 0x47, 0x0a, 0x41, 0x31, 0x55, 0x45, 0x43, + 0x68, 0x4d, 0x50, 0x51, 0x33, 0x6c, 0x69, 0x5a, 0x58, 0x4a, 0x30, 0x63, + 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x77, 0x67, 0x53, 0x57, 0x35, 0x6a, 0x4d, + 0x52, 0x38, 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, + 0x78, 0x5a, 0x44, 0x65, 0x57, 0x4a, 0x6c, 0x63, 0x6e, 0x52, 0x79, 0x64, + 0x58, 0x4e, 0x30, 0x49, 0x45, 0x64, 0x73, 0x62, 0x32, 0x4a, 0x68, 0x0a, + 0x62, 0x43, 0x42, 0x53, 0x62, 0x32, 0x39, 0x30, 0x4d, 0x42, 0x34, 0x58, + 0x44, 0x54, 0x41, 0x32, 0x4d, 0x54, 0x49, 0x78, 0x4e, 0x54, 0x41, 0x34, + 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x46, 0x6f, 0x58, 0x44, 0x54, 0x49, 0x78, + 0x4d, 0x54, 0x49, 0x78, 0x4e, 0x54, 0x41, 0x34, 0x4d, 0x44, 0x41, 0x77, + 0x4d, 0x46, 0x6f, 0x77, 0x4f, 0x7a, 0x45, 0x59, 0x4d, 0x42, 0x59, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x0a, 0x43, 0x68, 0x4d, 0x50, 0x51, 0x33, 0x6c, + 0x69, 0x5a, 0x58, 0x4a, 0x30, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x77, + 0x67, 0x53, 0x57, 0x35, 0x6a, 0x4d, 0x52, 0x38, 0x77, 0x48, 0x51, 0x59, + 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, 0x78, 0x5a, 0x44, 0x65, 0x57, 0x4a, + 0x6c, 0x63, 0x6e, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x49, 0x45, 0x64, + 0x73, 0x62, 0x32, 0x4a, 0x68, 0x62, 0x43, 0x42, 0x53, 0x0a, 0x62, 0x32, + 0x39, 0x30, 0x4d, 0x49, 0x49, 0x42, 0x49, 0x6a, 0x41, 0x4e, 0x42, 0x67, + 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, + 0x45, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x51, 0x38, 0x41, 0x4d, 0x49, + 0x49, 0x42, 0x43, 0x67, 0x4b, 0x43, 0x41, 0x51, 0x45, 0x41, 0x2b, 0x4d, + 0x69, 0x38, 0x76, 0x52, 0x52, 0x51, 0x5a, 0x68, 0x50, 0x2f, 0x38, 0x4e, + 0x4e, 0x35, 0x0a, 0x37, 0x43, 0x50, 0x79, 0x74, 0x78, 0x72, 0x48, 0x6a, + 0x6f, 0x58, 0x78, 0x45, 0x6e, 0x4f, 0x6d, 0x47, 0x61, 0x6f, 0x51, 0x32, + 0x35, 0x79, 0x69, 0x5a, 0x58, 0x52, 0x61, 0x64, 0x7a, 0x35, 0x52, 0x66, + 0x56, 0x62, 0x32, 0x33, 0x43, 0x4f, 0x32, 0x31, 0x4f, 0x31, 0x66, 0x57, + 0x4c, 0x45, 0x33, 0x54, 0x64, 0x56, 0x4a, 0x44, 0x6d, 0x37, 0x31, 0x61, + 0x6f, 0x66, 0x57, 0x30, 0x6f, 0x7a, 0x53, 0x0a, 0x4a, 0x38, 0x62, 0x69, + 0x2f, 0x7a, 0x61, 0x66, 0x6d, 0x47, 0x57, 0x67, 0x45, 0x30, 0x37, 0x47, + 0x4b, 0x6d, 0x53, 0x62, 0x31, 0x5a, 0x41, 0x53, 0x7a, 0x78, 0x51, 0x47, + 0x39, 0x44, 0x76, 0x6a, 0x31, 0x43, 0x69, 0x2b, 0x36, 0x41, 0x37, 0x34, + 0x71, 0x30, 0x35, 0x49, 0x6c, 0x47, 0x32, 0x4f, 0x6c, 0x54, 0x45, 0x51, + 0x58, 0x4f, 0x32, 0x69, 0x4c, 0x62, 0x33, 0x56, 0x4f, 0x6d, 0x32, 0x79, + 0x0a, 0x48, 0x4c, 0x74, 0x67, 0x77, 0x45, 0x5a, 0x4c, 0x41, 0x66, 0x56, + 0x4a, 0x72, 0x6e, 0x35, 0x47, 0x69, 0x74, 0x42, 0x30, 0x6a, 0x61, 0x45, + 0x4d, 0x41, 0x73, 0x37, 0x75, 0x2f, 0x4f, 0x65, 0x50, 0x75, 0x47, 0x74, + 0x6d, 0x38, 0x33, 0x39, 0x45, 0x41, 0x4c, 0x39, 0x6d, 0x4a, 0x52, 0x51, + 0x72, 0x33, 0x52, 0x41, 0x77, 0x48, 0x51, 0x65, 0x57, 0x50, 0x30, 0x33, + 0x32, 0x61, 0x37, 0x69, 0x50, 0x0a, 0x74, 0x33, 0x73, 0x4d, 0x70, 0x54, + 0x6a, 0x72, 0x33, 0x6b, 0x66, 0x62, 0x31, 0x56, 0x30, 0x35, 0x2f, 0x49, + 0x69, 0x6e, 0x38, 0x39, 0x63, 0x71, 0x64, 0x50, 0x48, 0x6f, 0x57, 0x71, + 0x49, 0x37, 0x6e, 0x31, 0x43, 0x36, 0x70, 0x6f, 0x78, 0x46, 0x4e, 0x63, + 0x4a, 0x51, 0x5a, 0x5a, 0x58, 0x63, 0x59, 0x34, 0x4c, 0x76, 0x33, 0x62, + 0x39, 0x33, 0x54, 0x5a, 0x78, 0x69, 0x79, 0x57, 0x4e, 0x7a, 0x0a, 0x46, + 0x74, 0x41, 0x70, 0x44, 0x30, 0x6d, 0x70, 0x53, 0x50, 0x43, 0x7a, 0x71, + 0x72, 0x64, 0x73, 0x78, 0x61, 0x63, 0x77, 0x4f, 0x55, 0x42, 0x64, 0x72, + 0x73, 0x54, 0x69, 0x58, 0x53, 0x5a, 0x54, 0x38, 0x4d, 0x34, 0x63, 0x49, + 0x77, 0x68, 0x68, 0x71, 0x4a, 0x51, 0x5a, 0x75, 0x67, 0x52, 0x69, 0x51, + 0x4f, 0x77, 0x66, 0x4f, 0x48, 0x42, 0x33, 0x45, 0x67, 0x5a, 0x78, 0x70, + 0x7a, 0x41, 0x59, 0x0a, 0x58, 0x53, 0x55, 0x6e, 0x70, 0x51, 0x49, 0x44, + 0x41, 0x51, 0x41, 0x42, 0x6f, 0x34, 0x47, 0x6c, 0x4d, 0x49, 0x47, 0x69, + 0x4d, 0x41, 0x34, 0x47, 0x41, 0x31, 0x55, 0x64, 0x44, 0x77, 0x45, 0x42, + 0x2f, 0x77, 0x51, 0x45, 0x41, 0x77, 0x49, 0x42, 0x42, 0x6a, 0x41, 0x50, + 0x42, 0x67, 0x4e, 0x56, 0x48, 0x52, 0x4d, 0x42, 0x41, 0x66, 0x38, 0x45, + 0x42, 0x54, 0x41, 0x44, 0x41, 0x51, 0x48, 0x2f, 0x0a, 0x4d, 0x42, 0x30, + 0x47, 0x41, 0x31, 0x55, 0x64, 0x44, 0x67, 0x51, 0x57, 0x42, 0x42, 0x53, + 0x32, 0x43, 0x48, 0x73, 0x4e, 0x65, 0x73, 0x79, 0x73, 0x49, 0x45, 0x79, + 0x47, 0x56, 0x6a, 0x4a, 0x65, 0x7a, 0x36, 0x74, 0x75, 0x68, 0x53, 0x31, + 0x77, 0x56, 0x7a, 0x41, 0x2f, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x52, 0x38, + 0x45, 0x4f, 0x44, 0x41, 0x32, 0x4d, 0x44, 0x53, 0x67, 0x4d, 0x71, 0x41, + 0x77, 0x0a, 0x68, 0x69, 0x35, 0x6f, 0x64, 0x48, 0x52, 0x77, 0x4f, 0x69, + 0x38, 0x76, 0x64, 0x33, 0x64, 0x33, 0x4d, 0x69, 0x35, 0x77, 0x64, 0x57, + 0x4a, 0x73, 0x61, 0x57, 0x4d, 0x74, 0x64, 0x48, 0x4a, 0x31, 0x63, 0x33, + 0x51, 0x75, 0x59, 0x32, 0x39, 0x74, 0x4c, 0x32, 0x4e, 0x79, 0x62, 0x43, + 0x39, 0x6a, 0x64, 0x43, 0x39, 0x6a, 0x64, 0x48, 0x4a, 0x76, 0x62, 0x33, + 0x51, 0x75, 0x59, 0x33, 0x4a, 0x73, 0x0a, 0x4d, 0x42, 0x38, 0x47, 0x41, + 0x31, 0x55, 0x64, 0x49, 0x77, 0x51, 0x59, 0x4d, 0x42, 0x61, 0x41, 0x46, + 0x4c, 0x59, 0x49, 0x65, 0x77, 0x31, 0x36, 0x7a, 0x4b, 0x77, 0x67, 0x54, + 0x49, 0x5a, 0x57, 0x4d, 0x6c, 0x37, 0x50, 0x71, 0x32, 0x36, 0x46, 0x4c, + 0x58, 0x42, 0x58, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, + 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x42, 0x51, 0x55, 0x41, 0x0a, + 0x41, 0x34, 0x49, 0x42, 0x41, 0x51, 0x42, 0x57, 0x37, 0x77, 0x6f, 0x6a, + 0x6f, 0x46, 0x52, 0x4f, 0x6c, 0x5a, 0x66, 0x4a, 0x2b, 0x49, 0x6e, 0x61, + 0x52, 0x63, 0x48, 0x55, 0x6f, 0x77, 0x41, 0x6c, 0x39, 0x42, 0x38, 0x54, + 0x71, 0x37, 0x65, 0x6a, 0x68, 0x56, 0x68, 0x70, 0x77, 0x6a, 0x43, 0x74, + 0x32, 0x42, 0x57, 0x4b, 0x4c, 0x65, 0x50, 0x4a, 0x7a, 0x59, 0x46, 0x61, + 0x2b, 0x48, 0x4d, 0x6a, 0x0a, 0x57, 0x71, 0x64, 0x38, 0x42, 0x66, 0x50, + 0x39, 0x49, 0x6a, 0x73, 0x4f, 0x30, 0x51, 0x62, 0x45, 0x32, 0x7a, 0x5a, + 0x4d, 0x63, 0x77, 0x53, 0x4f, 0x35, 0x62, 0x41, 0x69, 0x35, 0x4d, 0x58, + 0x7a, 0x4c, 0x71, 0x58, 0x5a, 0x49, 0x2b, 0x4f, 0x34, 0x54, 0x6b, 0x6f, + 0x67, 0x70, 0x32, 0x34, 0x43, 0x4a, 0x4a, 0x38, 0x69, 0x59, 0x47, 0x64, + 0x37, 0x69, 0x78, 0x31, 0x79, 0x43, 0x63, 0x55, 0x78, 0x0a, 0x58, 0x4f, + 0x6c, 0x35, 0x6e, 0x34, 0x42, 0x48, 0x50, 0x61, 0x32, 0x68, 0x43, 0x77, + 0x63, 0x55, 0x50, 0x55, 0x66, 0x2f, 0x41, 0x32, 0x6b, 0x61, 0x44, 0x41, + 0x74, 0x45, 0x35, 0x32, 0x4d, 0x6c, 0x70, 0x33, 0x2b, 0x79, 0x79, 0x62, + 0x68, 0x32, 0x68, 0x4f, 0x30, 0x6a, 0x39, 0x6e, 0x30, 0x48, 0x71, 0x30, + 0x56, 0x2b, 0x30, 0x39, 0x2b, 0x7a, 0x76, 0x2b, 0x6d, 0x4b, 0x74, 0x73, + 0x32, 0x6f, 0x0a, 0x6f, 0x6d, 0x63, 0x72, 0x55, 0x74, 0x57, 0x33, 0x5a, + 0x66, 0x41, 0x35, 0x54, 0x47, 0x4f, 0x67, 0x6b, 0x58, 0x6d, 0x54, 0x55, + 0x67, 0x39, 0x55, 0x33, 0x59, 0x4f, 0x37, 0x6e, 0x39, 0x47, 0x50, 0x70, + 0x31, 0x4e, 0x7a, 0x77, 0x38, 0x76, 0x2f, 0x4d, 0x4f, 0x78, 0x38, 0x42, + 0x4c, 0x6a, 0x59, 0x52, 0x42, 0x2b, 0x54, 0x58, 0x33, 0x45, 0x4a, 0x49, + 0x72, 0x64, 0x75, 0x50, 0x75, 0x6f, 0x63, 0x0a, 0x41, 0x30, 0x36, 0x64, + 0x47, 0x69, 0x42, 0x68, 0x2b, 0x34, 0x45, 0x33, 0x37, 0x46, 0x37, 0x38, + 0x43, 0x6b, 0x57, 0x72, 0x31, 0x2b, 0x63, 0x58, 0x56, 0x64, 0x43, 0x67, + 0x36, 0x6d, 0x43, 0x62, 0x70, 0x76, 0x62, 0x6a, 0x6a, 0x46, 0x73, 0x70, + 0x77, 0x67, 0x5a, 0x67, 0x46, 0x4a, 0x30, 0x74, 0x6c, 0x30, 0x79, 0x70, + 0x6b, 0x78, 0x57, 0x64, 0x59, 0x63, 0x51, 0x42, 0x58, 0x30, 0x6a, 0x57, + 0x0a, 0x57, 0x4c, 0x31, 0x57, 0x4d, 0x52, 0x4a, 0x4f, 0x45, 0x63, 0x67, + 0x68, 0x34, 0x4c, 0x4d, 0x52, 0x6b, 0x57, 0x58, 0x62, 0x74, 0x4b, 0x61, + 0x49, 0x4f, 0x4d, 0x35, 0x56, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, + 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, + 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, + 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x47, 0x65, + 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, + 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x20, 0x4f, 0x3d, 0x47, 0x65, + 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, + 0x4f, 0x55, 0x3d, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x38, 0x20, + 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, + 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, + 0x6e, 0x6c, 0x79, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, + 0x73, 0x74, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, + 0x20, 0x47, 0x33, 0x20, 0x4f, 0x3d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, + 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x28, + 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x38, 0x20, 0x47, 0x65, 0x6f, 0x54, + 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, + 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x0a, + 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x47, 0x65, + 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, + 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x22, 0x0a, 0x23, 0x20, 0x53, + 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x32, 0x38, 0x38, 0x30, 0x39, + 0x31, 0x30, 0x35, 0x37, 0x36, 0x39, 0x39, 0x32, 0x38, 0x35, 0x36, 0x34, + 0x33, 0x31, 0x33, 0x39, 0x38, 0x34, 0x30, 0x38, 0x35, 0x32, 0x30, 0x39, + 0x39, 0x37, 0x35, 0x38, 0x38, 0x35, 0x35, 0x39, 0x39, 0x0a, 0x23, 0x20, + 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, + 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x62, 0x35, 0x3a, 0x65, 0x38, 0x3a, 0x33, + 0x34, 0x3a, 0x33, 0x36, 0x3a, 0x63, 0x39, 0x3a, 0x31, 0x30, 0x3a, 0x34, + 0x34, 0x3a, 0x35, 0x38, 0x3a, 0x34, 0x38, 0x3a, 0x37, 0x30, 0x3a, 0x36, + 0x64, 0x3a, 0x32, 0x65, 0x3a, 0x38, 0x33, 0x3a, 0x64, 0x34, 0x3a, 0x62, + 0x38, 0x3a, 0x30, 0x35, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, + 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, + 0x20, 0x30, 0x33, 0x3a, 0x39, 0x65, 0x3a, 0x65, 0x64, 0x3a, 0x62, 0x38, + 0x3a, 0x30, 0x62, 0x3a, 0x65, 0x37, 0x3a, 0x61, 0x30, 0x3a, 0x33, 0x63, + 0x3a, 0x36, 0x39, 0x3a, 0x35, 0x33, 0x3a, 0x38, 0x39, 0x3a, 0x33, 0x62, + 0x3a, 0x32, 0x30, 0x3a, 0x64, 0x32, 0x3a, 0x64, 0x39, 0x3a, 0x33, 0x32, + 0x3a, 0x33, 0x61, 0x3a, 0x34, 0x63, 0x3a, 0x32, 0x61, 0x3a, 0x66, 0x64, + 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, + 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x62, + 0x34, 0x3a, 0x37, 0x38, 0x3a, 0x62, 0x38, 0x3a, 0x31, 0x32, 0x3a, 0x32, + 0x35, 0x3a, 0x30, 0x64, 0x3a, 0x66, 0x38, 0x3a, 0x37, 0x38, 0x3a, 0x36, + 0x33, 0x3a, 0x35, 0x63, 0x3a, 0x32, 0x61, 0x3a, 0x61, 0x37, 0x3a, 0x65, + 0x63, 0x3a, 0x37, 0x64, 0x3a, 0x31, 0x35, 0x3a, 0x35, 0x65, 0x3a, 0x61, + 0x61, 0x3a, 0x36, 0x32, 0x3a, 0x35, 0x65, 0x3a, 0x65, 0x38, 0x3a, 0x32, + 0x39, 0x3a, 0x31, 0x36, 0x3a, 0x65, 0x32, 0x3a, 0x63, 0x64, 0x3a, 0x32, + 0x39, 0x3a, 0x34, 0x33, 0x3a, 0x36, 0x31, 0x3a, 0x38, 0x38, 0x3a, 0x36, + 0x63, 0x3a, 0x64, 0x31, 0x3a, 0x66, 0x62, 0x3a, 0x64, 0x34, 0x0a, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, + 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x44, 0x2f, 0x6a, 0x43, 0x43, 0x41, + 0x75, 0x61, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x51, 0x46, + 0x61, 0x78, 0x75, 0x6c, 0x42, 0x6d, 0x79, 0x65, 0x55, 0x74, 0x42, 0x39, + 0x69, 0x65, 0x70, 0x77, 0x78, 0x67, 0x50, 0x48, 0x7a, 0x41, 0x4e, 0x42, + 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, + 0x51, 0x73, 0x46, 0x41, 0x44, 0x43, 0x42, 0x0a, 0x6d, 0x44, 0x45, 0x4c, + 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, + 0x56, 0x56, 0x4d, 0x78, 0x46, 0x6a, 0x41, 0x55, 0x42, 0x67, 0x4e, 0x56, + 0x42, 0x41, 0x6f, 0x54, 0x44, 0x55, 0x64, 0x6c, 0x62, 0x31, 0x52, 0x79, + 0x64, 0x58, 0x4e, 0x30, 0x49, 0x45, 0x6c, 0x75, 0x59, 0x79, 0x34, 0x78, + 0x4f, 0x54, 0x41, 0x33, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, + 0x0a, 0x4d, 0x43, 0x68, 0x6a, 0x4b, 0x53, 0x41, 0x79, 0x4d, 0x44, 0x41, + 0x34, 0x49, 0x45, 0x64, 0x6c, 0x62, 0x31, 0x52, 0x79, 0x64, 0x58, 0x4e, + 0x30, 0x49, 0x45, 0x6c, 0x75, 0x59, 0x79, 0x34, 0x67, 0x4c, 0x53, 0x42, + 0x47, 0x62, 0x33, 0x49, 0x67, 0x59, 0x58, 0x56, 0x30, 0x61, 0x47, 0x39, + 0x79, 0x61, 0x58, 0x70, 0x6c, 0x5a, 0x43, 0x42, 0x31, 0x63, 0x32, 0x55, + 0x67, 0x62, 0x32, 0x35, 0x73, 0x0a, 0x65, 0x54, 0x45, 0x32, 0x4d, 0x44, + 0x51, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x74, 0x52, 0x32, + 0x56, 0x76, 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x51, 0x67, 0x55, 0x48, + 0x4a, 0x70, 0x62, 0x57, 0x46, 0x79, 0x65, 0x53, 0x42, 0x44, 0x5a, 0x58, + 0x4a, 0x30, 0x61, 0x57, 0x5a, 0x70, 0x59, 0x32, 0x46, 0x30, 0x61, 0x57, + 0x39, 0x75, 0x49, 0x45, 0x46, 0x31, 0x64, 0x47, 0x68, 0x76, 0x0a, 0x63, + 0x6d, 0x6c, 0x30, 0x65, 0x53, 0x41, 0x74, 0x49, 0x45, 0x63, 0x7a, 0x4d, + 0x42, 0x34, 0x58, 0x44, 0x54, 0x41, 0x34, 0x4d, 0x44, 0x51, 0x77, 0x4d, + 0x6a, 0x41, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x46, 0x6f, 0x58, 0x44, + 0x54, 0x4d, 0x33, 0x4d, 0x54, 0x49, 0x77, 0x4d, 0x54, 0x49, 0x7a, 0x4e, + 0x54, 0x6b, 0x31, 0x4f, 0x56, 0x6f, 0x77, 0x67, 0x5a, 0x67, 0x78, 0x43, + 0x7a, 0x41, 0x4a, 0x0a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x59, 0x54, + 0x41, 0x6c, 0x56, 0x54, 0x4d, 0x52, 0x59, 0x77, 0x46, 0x41, 0x59, 0x44, + 0x56, 0x51, 0x51, 0x4b, 0x45, 0x77, 0x31, 0x48, 0x5a, 0x57, 0x39, 0x55, + 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x4a, 0x62, 0x6d, 0x4d, 0x75, + 0x4d, 0x54, 0x6b, 0x77, 0x4e, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, + 0x45, 0x7a, 0x41, 0x6f, 0x59, 0x79, 0x6b, 0x67, 0x0a, 0x4d, 0x6a, 0x41, + 0x77, 0x4f, 0x43, 0x42, 0x48, 0x5a, 0x57, 0x39, 0x55, 0x63, 0x6e, 0x56, + 0x7a, 0x64, 0x43, 0x42, 0x4a, 0x62, 0x6d, 0x4d, 0x75, 0x49, 0x43, 0x30, + 0x67, 0x52, 0x6d, 0x39, 0x79, 0x49, 0x47, 0x46, 0x31, 0x64, 0x47, 0x68, + 0x76, 0x63, 0x6d, 0x6c, 0x36, 0x5a, 0x57, 0x51, 0x67, 0x64, 0x58, 0x4e, + 0x6c, 0x49, 0x47, 0x39, 0x75, 0x62, 0x48, 0x6b, 0x78, 0x4e, 0x6a, 0x41, + 0x30, 0x0a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x4d, 0x54, 0x4c, 0x55, + 0x64, 0x6c, 0x62, 0x31, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x49, 0x46, + 0x42, 0x79, 0x61, 0x57, 0x31, 0x68, 0x63, 0x6e, 0x6b, 0x67, 0x51, 0x32, + 0x56, 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, + 0x6c, 0x76, 0x62, 0x69, 0x42, 0x42, 0x64, 0x58, 0x52, 0x6f, 0x62, 0x33, + 0x4a, 0x70, 0x64, 0x48, 0x6b, 0x67, 0x0a, 0x4c, 0x53, 0x42, 0x48, 0x4d, + 0x7a, 0x43, 0x43, 0x41, 0x53, 0x49, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, + 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x42, 0x42, + 0x51, 0x41, 0x44, 0x67, 0x67, 0x45, 0x50, 0x41, 0x44, 0x43, 0x43, 0x41, + 0x51, 0x6f, 0x43, 0x67, 0x67, 0x45, 0x42, 0x41, 0x4e, 0x7a, 0x69, 0x58, + 0x6d, 0x4a, 0x59, 0x48, 0x54, 0x4e, 0x58, 0x4f, 0x54, 0x49, 0x7a, 0x0a, + 0x2b, 0x75, 0x76, 0x4c, 0x68, 0x34, 0x79, 0x6e, 0x31, 0x45, 0x72, 0x64, + 0x42, 0x6f, 0x6a, 0x71, 0x5a, 0x49, 0x34, 0x78, 0x6d, 0x4b, 0x55, 0x34, + 0x6b, 0x42, 0x36, 0x59, 0x7a, 0x79, 0x35, 0x6a, 0x4b, 0x2f, 0x42, 0x47, + 0x76, 0x45, 0x53, 0x79, 0x69, 0x61, 0x48, 0x41, 0x4b, 0x41, 0x78, 0x4a, + 0x63, 0x43, 0x47, 0x56, 0x6e, 0x32, 0x54, 0x41, 0x70, 0x70, 0x4d, 0x53, + 0x41, 0x6d, 0x55, 0x6d, 0x0a, 0x68, 0x73, 0x61, 0x6c, 0x69, 0x66, 0x44, + 0x36, 0x31, 0x34, 0x53, 0x67, 0x63, 0x4b, 0x39, 0x50, 0x47, 0x70, 0x63, + 0x2f, 0x42, 0x6b, 0x54, 0x56, 0x79, 0x65, 0x74, 0x79, 0x45, 0x48, 0x33, + 0x6b, 0x4d, 0x53, 0x6a, 0x37, 0x48, 0x47, 0x48, 0x6d, 0x4b, 0x41, 0x64, + 0x45, 0x63, 0x35, 0x49, 0x69, 0x61, 0x61, 0x63, 0x44, 0x69, 0x47, 0x79, + 0x64, 0x59, 0x38, 0x68, 0x53, 0x32, 0x70, 0x67, 0x6e, 0x0a, 0x35, 0x77, + 0x68, 0x4d, 0x63, 0x44, 0x36, 0x30, 0x79, 0x52, 0x4c, 0x42, 0x78, 0x57, + 0x65, 0x44, 0x58, 0x54, 0x50, 0x7a, 0x41, 0x78, 0x48, 0x73, 0x61, 0x74, + 0x42, 0x54, 0x34, 0x74, 0x47, 0x36, 0x4e, 0x6d, 0x43, 0x55, 0x67, 0x4c, + 0x74, 0x68, 0x59, 0x32, 0x78, 0x62, 0x46, 0x33, 0x37, 0x66, 0x51, 0x4a, + 0x51, 0x65, 0x71, 0x77, 0x33, 0x43, 0x49, 0x53, 0x68, 0x77, 0x69, 0x50, + 0x2f, 0x57, 0x0a, 0x4a, 0x6d, 0x78, 0x73, 0x59, 0x41, 0x51, 0x6c, 0x54, + 0x6c, 0x56, 0x2b, 0x66, 0x65, 0x2b, 0x2f, 0x6c, 0x45, 0x6a, 0x65, 0x74, + 0x78, 0x33, 0x64, 0x63, 0x49, 0x30, 0x46, 0x58, 0x34, 0x69, 0x6c, 0x6d, + 0x2f, 0x4c, 0x43, 0x37, 0x75, 0x72, 0x52, 0x51, 0x45, 0x46, 0x74, 0x59, + 0x6a, 0x67, 0x64, 0x56, 0x67, 0x62, 0x46, 0x41, 0x30, 0x64, 0x52, 0x49, + 0x42, 0x6e, 0x38, 0x65, 0x78, 0x41, 0x4c, 0x0a, 0x44, 0x6d, 0x4b, 0x75, + 0x64, 0x6c, 0x57, 0x2f, 0x58, 0x33, 0x65, 0x2b, 0x50, 0x6b, 0x6b, 0x42, + 0x55, 0x7a, 0x32, 0x59, 0x4a, 0x51, 0x4e, 0x32, 0x4a, 0x46, 0x6f, 0x64, + 0x74, 0x4e, 0x75, 0x4a, 0x36, 0x6e, 0x6e, 0x6c, 0x74, 0x72, 0x4d, 0x37, + 0x50, 0x37, 0x70, 0x4d, 0x4b, 0x45, 0x46, 0x2f, 0x42, 0x71, 0x78, 0x71, + 0x6a, 0x73, 0x48, 0x51, 0x39, 0x67, 0x55, 0x64, 0x66, 0x65, 0x5a, 0x43, + 0x0a, 0x68, 0x75, 0x4f, 0x6c, 0x31, 0x55, 0x63, 0x43, 0x41, 0x77, 0x45, + 0x41, 0x41, 0x61, 0x4e, 0x43, 0x4d, 0x45, 0x41, 0x77, 0x44, 0x77, 0x59, + 0x44, 0x56, 0x52, 0x30, 0x54, 0x41, 0x51, 0x48, 0x2f, 0x42, 0x41, 0x55, + 0x77, 0x41, 0x77, 0x45, 0x42, 0x2f, 0x7a, 0x41, 0x4f, 0x42, 0x67, 0x4e, + 0x56, 0x48, 0x51, 0x38, 0x42, 0x41, 0x66, 0x38, 0x45, 0x42, 0x41, 0x4d, + 0x43, 0x41, 0x51, 0x59, 0x77, 0x0a, 0x48, 0x51, 0x59, 0x44, 0x56, 0x52, + 0x30, 0x4f, 0x42, 0x42, 0x59, 0x45, 0x46, 0x4d, 0x52, 0x35, 0x79, 0x6f, + 0x36, 0x68, 0x54, 0x67, 0x4d, 0x64, 0x48, 0x4e, 0x78, 0x72, 0x32, 0x7a, + 0x46, 0x62, 0x6c, 0x44, 0x34, 0x2f, 0x4d, 0x48, 0x38, 0x74, 0x4d, 0x41, + 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, + 0x45, 0x42, 0x43, 0x77, 0x55, 0x41, 0x41, 0x34, 0x49, 0x42, 0x0a, 0x41, + 0x51, 0x41, 0x74, 0x78, 0x52, 0x50, 0x50, 0x56, 0x6f, 0x42, 0x37, 0x65, + 0x6e, 0x69, 0x39, 0x6e, 0x36, 0x34, 0x73, 0x6d, 0x65, 0x66, 0x76, 0x32, + 0x74, 0x2b, 0x55, 0x58, 0x67, 0x6c, 0x70, 0x70, 0x2b, 0x64, 0x75, 0x61, + 0x49, 0x79, 0x39, 0x63, 0x72, 0x35, 0x48, 0x71, 0x51, 0x36, 0x58, 0x45, + 0x72, 0x68, 0x4b, 0x38, 0x57, 0x54, 0x54, 0x4f, 0x64, 0x38, 0x6c, 0x4e, + 0x4e, 0x54, 0x42, 0x0a, 0x7a, 0x55, 0x36, 0x42, 0x38, 0x41, 0x38, 0x45, + 0x78, 0x43, 0x53, 0x7a, 0x4e, 0x4a, 0x62, 0x47, 0x70, 0x71, 0x6f, 0x77, + 0x33, 0x32, 0x68, 0x68, 0x63, 0x39, 0x66, 0x35, 0x6a, 0x6f, 0x57, 0x4a, + 0x37, 0x77, 0x35, 0x65, 0x6c, 0x53, 0x68, 0x4b, 0x4b, 0x69, 0x65, 0x50, + 0x45, 0x49, 0x34, 0x75, 0x66, 0x49, 0x62, 0x45, 0x41, 0x70, 0x37, 0x61, + 0x44, 0x48, 0x64, 0x6c, 0x44, 0x6b, 0x51, 0x4e, 0x0a, 0x6b, 0x76, 0x33, + 0x39, 0x73, 0x78, 0x59, 0x32, 0x2b, 0x68, 0x45, 0x4e, 0x48, 0x59, 0x77, + 0x4f, 0x42, 0x34, 0x6c, 0x71, 0x4b, 0x56, 0x62, 0x33, 0x63, 0x76, 0x54, + 0x64, 0x46, 0x5a, 0x78, 0x33, 0x4e, 0x57, 0x5a, 0x58, 0x71, 0x78, 0x4e, + 0x54, 0x32, 0x49, 0x37, 0x42, 0x51, 0x4d, 0x58, 0x58, 0x45, 0x78, 0x5a, + 0x61, 0x63, 0x73, 0x65, 0x33, 0x61, 0x51, 0x48, 0x45, 0x65, 0x72, 0x47, + 0x44, 0x0a, 0x41, 0x57, 0x68, 0x39, 0x6a, 0x55, 0x47, 0x68, 0x6c, 0x42, + 0x6a, 0x42, 0x4a, 0x56, 0x7a, 0x38, 0x38, 0x50, 0x36, 0x44, 0x41, 0x6f, + 0x64, 0x38, 0x44, 0x51, 0x33, 0x50, 0x4c, 0x67, 0x68, 0x63, 0x53, 0x6b, + 0x41, 0x4e, 0x50, 0x75, 0x79, 0x42, 0x59, 0x65, 0x59, 0x6b, 0x32, 0x38, + 0x72, 0x67, 0x44, 0x69, 0x30, 0x48, 0x73, 0x6a, 0x35, 0x57, 0x33, 0x49, + 0x33, 0x31, 0x51, 0x59, 0x55, 0x48, 0x0a, 0x53, 0x4a, 0x73, 0x4d, 0x43, + 0x38, 0x74, 0x4a, 0x50, 0x33, 0x33, 0x73, 0x74, 0x2f, 0x33, 0x4c, 0x6a, + 0x57, 0x65, 0x4a, 0x47, 0x71, 0x76, 0x74, 0x75, 0x78, 0x36, 0x6a, 0x41, + 0x41, 0x67, 0x49, 0x46, 0x79, 0x71, 0x43, 0x58, 0x44, 0x46, 0x64, 0x52, + 0x6f, 0x6f, 0x74, 0x44, 0x34, 0x61, 0x62, 0x64, 0x4e, 0x6c, 0x46, 0x2b, + 0x39, 0x52, 0x41, 0x73, 0x58, 0x71, 0x71, 0x61, 0x43, 0x32, 0x47, 0x0a, + 0x73, 0x70, 0x6b, 0x69, 0x34, 0x63, 0x45, 0x72, 0x78, 0x35, 0x7a, 0x34, + 0x38, 0x31, 0x2b, 0x6f, 0x67, 0x68, 0x4c, 0x72, 0x47, 0x52, 0x45, 0x74, + 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, + 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, + 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, + 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, + 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x20, 0x4f, 0x3d, 0x74, + 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, + 0x4f, 0x55, 0x3d, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x37, 0x20, + 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, + 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, + 0x6c, 0x79, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, + 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, + 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x20, 0x4f, 0x3d, 0x74, + 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, + 0x4f, 0x55, 0x3d, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x37, 0x20, + 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, + 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, + 0x6c, 0x79, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, + 0x22, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50, 0x72, 0x69, 0x6d, + 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, + 0x2d, 0x20, 0x47, 0x32, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, + 0x61, 0x6c, 0x3a, 0x20, 0x37, 0x31, 0x37, 0x35, 0x38, 0x33, 0x32, 0x30, + 0x36, 0x37, 0x32, 0x38, 0x32, 0x35, 0x34, 0x31, 0x30, 0x30, 0x32, 0x30, + 0x36, 0x36, 0x31, 0x36, 0x32, 0x31, 0x30, 0x38, 0x35, 0x32, 0x35, 0x36, + 0x34, 0x37, 0x32, 0x34, 0x30, 0x36, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, + 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, + 0x3a, 0x20, 0x37, 0x34, 0x3a, 0x39, 0x64, 0x3a, 0x65, 0x61, 0x3a, 0x36, + 0x30, 0x3a, 0x32, 0x34, 0x3a, 0x63, 0x34, 0x3a, 0x66, 0x64, 0x3a, 0x32, + 0x32, 0x3a, 0x35, 0x33, 0x3a, 0x33, 0x65, 0x3a, 0x63, 0x63, 0x3a, 0x33, + 0x61, 0x3a, 0x37, 0x32, 0x3a, 0x64, 0x39, 0x3a, 0x32, 0x39, 0x3a, 0x34, + 0x66, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, + 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x61, 0x61, + 0x3a, 0x64, 0x62, 0x3a, 0x62, 0x63, 0x3a, 0x32, 0x32, 0x3a, 0x32, 0x33, + 0x3a, 0x38, 0x66, 0x3a, 0x63, 0x34, 0x3a, 0x30, 0x31, 0x3a, 0x61, 0x31, + 0x3a, 0x32, 0x37, 0x3a, 0x62, 0x62, 0x3a, 0x33, 0x38, 0x3a, 0x64, 0x64, + 0x3a, 0x66, 0x34, 0x3a, 0x31, 0x64, 0x3a, 0x64, 0x62, 0x3a, 0x30, 0x38, + 0x3a, 0x39, 0x65, 0x3a, 0x66, 0x30, 0x3a, 0x31, 0x32, 0x0a, 0x23, 0x20, + 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x61, 0x34, 0x3a, 0x33, + 0x31, 0x3a, 0x30, 0x64, 0x3a, 0x35, 0x30, 0x3a, 0x61, 0x66, 0x3a, 0x31, + 0x38, 0x3a, 0x61, 0x36, 0x3a, 0x34, 0x34, 0x3a, 0x37, 0x31, 0x3a, 0x39, + 0x30, 0x3a, 0x33, 0x37, 0x3a, 0x32, 0x61, 0x3a, 0x38, 0x36, 0x3a, 0x61, + 0x66, 0x3a, 0x61, 0x66, 0x3a, 0x38, 0x62, 0x3a, 0x39, 0x35, 0x3a, 0x31, + 0x66, 0x3a, 0x66, 0x62, 0x3a, 0x34, 0x33, 0x3a, 0x31, 0x64, 0x3a, 0x38, + 0x33, 0x3a, 0x37, 0x66, 0x3a, 0x31, 0x65, 0x3a, 0x35, 0x36, 0x3a, 0x38, + 0x38, 0x3a, 0x62, 0x34, 0x3a, 0x35, 0x39, 0x3a, 0x37, 0x31, 0x3a, 0x65, + 0x64, 0x3a, 0x31, 0x35, 0x3a, 0x35, 0x37, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, + 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, + 0x4d, 0x49, 0x49, 0x43, 0x69, 0x44, 0x43, 0x43, 0x41, 0x67, 0x32, 0x67, + 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x51, 0x4e, 0x66, 0x77, 0x6d, + 0x58, 0x4e, 0x6d, 0x45, 0x54, 0x38, 0x6b, 0x39, 0x4a, 0x6a, 0x31, 0x58, + 0x6d, 0x36, 0x37, 0x58, 0x56, 0x6a, 0x41, 0x4b, 0x42, 0x67, 0x67, 0x71, + 0x68, 0x6b, 0x6a, 0x4f, 0x50, 0x51, 0x51, 0x44, 0x41, 0x7a, 0x43, 0x42, + 0x68, 0x44, 0x45, 0x4c, 0x0a, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x42, 0x68, 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, 0x46, 0x54, 0x41, + 0x54, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, 0x44, 0x48, 0x52, + 0x6f, 0x59, 0x58, 0x64, 0x30, 0x5a, 0x53, 0x77, 0x67, 0x53, 0x57, 0x35, + 0x6a, 0x4c, 0x6a, 0x45, 0x34, 0x4d, 0x44, 0x59, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x43, 0x78, 0x4d, 0x76, 0x4b, 0x47, 0x4d, 0x70, 0x0a, 0x49, 0x44, + 0x49, 0x77, 0x4d, 0x44, 0x63, 0x67, 0x64, 0x47, 0x68, 0x68, 0x64, 0x33, + 0x52, 0x6c, 0x4c, 0x43, 0x42, 0x4a, 0x62, 0x6d, 0x4d, 0x75, 0x49, 0x43, + 0x30, 0x67, 0x52, 0x6d, 0x39, 0x79, 0x49, 0x47, 0x46, 0x31, 0x64, 0x47, + 0x68, 0x76, 0x63, 0x6d, 0x6c, 0x36, 0x5a, 0x57, 0x51, 0x67, 0x64, 0x58, + 0x4e, 0x6c, 0x49, 0x47, 0x39, 0x75, 0x62, 0x48, 0x6b, 0x78, 0x4a, 0x44, + 0x41, 0x69, 0x0a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x4d, 0x54, 0x47, + 0x33, 0x52, 0x6f, 0x59, 0x58, 0x64, 0x30, 0x5a, 0x53, 0x42, 0x51, 0x63, + 0x6d, 0x6c, 0x74, 0x59, 0x58, 0x4a, 0x35, 0x49, 0x46, 0x4a, 0x76, 0x62, + 0x33, 0x51, 0x67, 0x51, 0x30, 0x45, 0x67, 0x4c, 0x53, 0x42, 0x48, 0x4d, + 0x6a, 0x41, 0x65, 0x46, 0x77, 0x30, 0x77, 0x4e, 0x7a, 0x45, 0x78, 0x4d, + 0x44, 0x55, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x0a, 0x4d, 0x44, 0x42, 0x61, + 0x46, 0x77, 0x30, 0x7a, 0x4f, 0x44, 0x41, 0x78, 0x4d, 0x54, 0x67, 0x79, + 0x4d, 0x7a, 0x55, 0x35, 0x4e, 0x54, 0x6c, 0x61, 0x4d, 0x49, 0x47, 0x45, + 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, + 0x45, 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, 0x56, 0x4d, 0x42, 0x4d, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4d, 0x64, 0x47, 0x68, 0x68, + 0x0a, 0x64, 0x33, 0x52, 0x6c, 0x4c, 0x43, 0x42, 0x4a, 0x62, 0x6d, 0x4d, + 0x75, 0x4d, 0x54, 0x67, 0x77, 0x4e, 0x67, 0x59, 0x44, 0x56, 0x51, 0x51, + 0x4c, 0x45, 0x79, 0x38, 0x6f, 0x59, 0x79, 0x6b, 0x67, 0x4d, 0x6a, 0x41, + 0x77, 0x4e, 0x79, 0x42, 0x30, 0x61, 0x47, 0x46, 0x33, 0x64, 0x47, 0x55, + 0x73, 0x49, 0x45, 0x6c, 0x75, 0x59, 0x79, 0x34, 0x67, 0x4c, 0x53, 0x42, + 0x47, 0x62, 0x33, 0x49, 0x67, 0x0a, 0x59, 0x58, 0x56, 0x30, 0x61, 0x47, + 0x39, 0x79, 0x61, 0x58, 0x70, 0x6c, 0x5a, 0x43, 0x42, 0x31, 0x63, 0x32, + 0x55, 0x67, 0x62, 0x32, 0x35, 0x73, 0x65, 0x54, 0x45, 0x6b, 0x4d, 0x43, + 0x49, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x62, 0x64, 0x47, + 0x68, 0x68, 0x64, 0x33, 0x52, 0x6c, 0x49, 0x46, 0x42, 0x79, 0x61, 0x57, + 0x31, 0x68, 0x63, 0x6e, 0x6b, 0x67, 0x55, 0x6d, 0x39, 0x76, 0x0a, 0x64, + 0x43, 0x42, 0x44, 0x51, 0x53, 0x41, 0x74, 0x49, 0x45, 0x63, 0x79, 0x4d, + 0x48, 0x59, 0x77, 0x45, 0x41, 0x59, 0x48, 0x4b, 0x6f, 0x5a, 0x49, 0x7a, + 0x6a, 0x30, 0x43, 0x41, 0x51, 0x59, 0x46, 0x4b, 0x34, 0x45, 0x45, 0x41, + 0x43, 0x49, 0x44, 0x59, 0x67, 0x41, 0x45, 0x6f, 0x74, 0x57, 0x63, 0x67, + 0x6e, 0x75, 0x56, 0x6e, 0x66, 0x46, 0x53, 0x65, 0x49, 0x66, 0x2b, 0x69, + 0x68, 0x61, 0x2f, 0x0a, 0x42, 0x65, 0x62, 0x66, 0x6f, 0x77, 0x4a, 0x50, + 0x44, 0x51, 0x66, 0x47, 0x41, 0x46, 0x47, 0x36, 0x44, 0x41, 0x4a, 0x53, + 0x4c, 0x53, 0x4b, 0x6b, 0x51, 0x6a, 0x6e, 0x45, 0x2f, 0x6f, 0x2f, 0x71, + 0x79, 0x63, 0x47, 0x2b, 0x31, 0x45, 0x33, 0x2f, 0x6e, 0x33, 0x71, 0x65, + 0x34, 0x72, 0x46, 0x38, 0x6d, 0x71, 0x32, 0x6e, 0x68, 0x67, 0x6c, 0x7a, + 0x68, 0x39, 0x48, 0x6e, 0x6d, 0x75, 0x4e, 0x36, 0x0a, 0x70, 0x61, 0x70, + 0x75, 0x2b, 0x37, 0x71, 0x7a, 0x63, 0x4d, 0x42, 0x6e, 0x69, 0x4b, 0x49, + 0x31, 0x31, 0x4b, 0x4f, 0x61, 0x73, 0x66, 0x32, 0x74, 0x77, 0x75, 0x38, + 0x78, 0x2b, 0x71, 0x69, 0x35, 0x38, 0x2f, 0x73, 0x49, 0x78, 0x70, 0x48, + 0x52, 0x2b, 0x79, 0x6d, 0x56, 0x6f, 0x30, 0x49, 0x77, 0x51, 0x44, 0x41, + 0x50, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x52, 0x4d, 0x42, 0x41, 0x66, 0x38, + 0x45, 0x0a, 0x42, 0x54, 0x41, 0x44, 0x41, 0x51, 0x48, 0x2f, 0x4d, 0x41, + 0x34, 0x47, 0x41, 0x31, 0x55, 0x64, 0x44, 0x77, 0x45, 0x42, 0x2f, 0x77, + 0x51, 0x45, 0x41, 0x77, 0x49, 0x42, 0x42, 0x6a, 0x41, 0x64, 0x42, 0x67, + 0x4e, 0x56, 0x48, 0x51, 0x34, 0x45, 0x46, 0x67, 0x51, 0x55, 0x6d, 0x74, + 0x67, 0x41, 0x4d, 0x41, 0x44, 0x6e, 0x61, 0x33, 0x2b, 0x46, 0x47, 0x4f, + 0x36, 0x4c, 0x74, 0x73, 0x36, 0x4b, 0x0a, 0x44, 0x50, 0x67, 0x52, 0x34, + 0x62, 0x73, 0x77, 0x43, 0x67, 0x59, 0x49, 0x4b, 0x6f, 0x5a, 0x49, 0x7a, + 0x6a, 0x30, 0x45, 0x41, 0x77, 0x4d, 0x44, 0x61, 0x51, 0x41, 0x77, 0x5a, + 0x67, 0x49, 0x78, 0x41, 0x4e, 0x33, 0x34, 0x34, 0x46, 0x64, 0x48, 0x57, + 0x36, 0x66, 0x6d, 0x43, 0x73, 0x4f, 0x39, 0x39, 0x59, 0x43, 0x4b, 0x6c, + 0x7a, 0x55, 0x4e, 0x47, 0x34, 0x6b, 0x38, 0x56, 0x49, 0x5a, 0x33, 0x0a, + 0x4b, 0x4d, 0x71, 0x68, 0x39, 0x48, 0x6e, 0x65, 0x74, 0x65, 0x59, 0x34, + 0x73, 0x50, 0x42, 0x6c, 0x63, 0x49, 0x78, 0x2f, 0x41, 0x6c, 0x54, 0x43, + 0x76, 0x2f, 0x2f, 0x59, 0x6f, 0x54, 0x37, 0x5a, 0x7a, 0x77, 0x49, 0x78, + 0x41, 0x4d, 0x53, 0x4e, 0x6c, 0x50, 0x7a, 0x63, 0x55, 0x39, 0x4c, 0x63, + 0x6e, 0x58, 0x67, 0x57, 0x48, 0x78, 0x55, 0x7a, 0x49, 0x31, 0x4e, 0x53, + 0x34, 0x31, 0x6f, 0x78, 0x0a, 0x58, 0x5a, 0x33, 0x4b, 0x72, 0x72, 0x30, + 0x54, 0x4b, 0x55, 0x51, 0x4e, 0x4a, 0x31, 0x75, 0x6f, 0x35, 0x32, 0x69, + 0x63, 0x45, 0x76, 0x64, 0x59, 0x50, 0x79, 0x35, 0x79, 0x41, 0x6c, 0x65, + 0x6a, 0x6a, 0x36, 0x45, 0x55, 0x4c, 0x67, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, + 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, + 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, + 0x4e, 0x3d, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50, 0x72, 0x69, + 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, + 0x20, 0x2d, 0x20, 0x47, 0x33, 0x20, 0x4f, 0x3d, 0x74, 0x68, 0x61, 0x77, + 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, + 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x44, + 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2f, 0x28, 0x63, 0x29, 0x20, + 0x32, 0x30, 0x30, 0x38, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, + 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, + 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, + 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x0a, 0x23, 0x20, 0x53, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x74, 0x68, + 0x61, 0x77, 0x74, 0x65, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, + 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, + 0x33, 0x20, 0x4f, 0x3d, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, + 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x44, 0x69, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x38, + 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, + 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, + 0x6e, 0x6c, 0x79, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50, 0x72, 0x69, + 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, + 0x20, 0x2d, 0x20, 0x47, 0x33, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, + 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x31, 0x32, 0x37, 0x36, 0x31, 0x34, 0x31, + 0x35, 0x37, 0x30, 0x35, 0x36, 0x36, 0x38, 0x31, 0x32, 0x39, 0x39, 0x38, + 0x30, 0x35, 0x35, 0x35, 0x36, 0x34, 0x37, 0x36, 0x32, 0x37, 0x35, 0x39, + 0x39, 0x35, 0x34, 0x31, 0x34, 0x37, 0x37, 0x39, 0x0a, 0x23, 0x20, 0x4d, + 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x3a, 0x20, 0x66, 0x62, 0x3a, 0x31, 0x62, 0x3a, 0x35, 0x64, + 0x3a, 0x34, 0x33, 0x3a, 0x38, 0x61, 0x3a, 0x39, 0x34, 0x3a, 0x63, 0x64, + 0x3a, 0x34, 0x34, 0x3a, 0x63, 0x36, 0x3a, 0x37, 0x36, 0x3a, 0x66, 0x32, + 0x3a, 0x34, 0x33, 0x3a, 0x34, 0x62, 0x3a, 0x34, 0x37, 0x3a, 0x65, 0x37, + 0x3a, 0x33, 0x31, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x66, 0x31, 0x3a, 0x38, 0x62, 0x3a, 0x35, 0x33, 0x3a, 0x38, 0x64, 0x3a, + 0x31, 0x62, 0x3a, 0x65, 0x39, 0x3a, 0x30, 0x33, 0x3a, 0x62, 0x36, 0x3a, + 0x61, 0x36, 0x3a, 0x66, 0x30, 0x3a, 0x35, 0x36, 0x3a, 0x34, 0x33, 0x3a, + 0x35, 0x62, 0x3a, 0x31, 0x37, 0x3a, 0x31, 0x35, 0x3a, 0x38, 0x39, 0x3a, + 0x63, 0x61, 0x3a, 0x66, 0x33, 0x3a, 0x36, 0x62, 0x3a, 0x66, 0x32, 0x0a, + 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, + 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x34, 0x62, + 0x3a, 0x30, 0x33, 0x3a, 0x66, 0x34, 0x3a, 0x35, 0x38, 0x3a, 0x30, 0x37, + 0x3a, 0x61, 0x64, 0x3a, 0x37, 0x30, 0x3a, 0x66, 0x32, 0x3a, 0x31, 0x62, + 0x3a, 0x66, 0x63, 0x3a, 0x32, 0x63, 0x3a, 0x61, 0x65, 0x3a, 0x37, 0x31, + 0x3a, 0x63, 0x39, 0x3a, 0x66, 0x64, 0x3a, 0x65, 0x34, 0x3a, 0x36, 0x30, + 0x3a, 0x34, 0x63, 0x3a, 0x30, 0x36, 0x3a, 0x34, 0x63, 0x3a, 0x66, 0x35, + 0x3a, 0x66, 0x66, 0x3a, 0x62, 0x36, 0x3a, 0x38, 0x36, 0x3a, 0x62, 0x61, + 0x3a, 0x65, 0x35, 0x3a, 0x64, 0x62, 0x3a, 0x61, 0x61, 0x3a, 0x64, 0x37, + 0x3a, 0x66, 0x64, 0x3a, 0x64, 0x33, 0x3a, 0x34, 0x63, 0x0a, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, + 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x45, 0x4b, 0x6a, 0x43, 0x43, 0x41, 0x78, + 0x4b, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x51, 0x59, 0x41, + 0x47, 0x58, 0x74, 0x30, 0x61, 0x6e, 0x36, 0x72, 0x53, 0x30, 0x6d, 0x74, + 0x5a, 0x4c, 0x4c, 0x2f, 0x65, 0x51, 0x2b, 0x7a, 0x41, 0x4e, 0x42, 0x67, + 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, + 0x73, 0x46, 0x41, 0x44, 0x43, 0x42, 0x0a, 0x72, 0x6a, 0x45, 0x4c, 0x4d, + 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x56, + 0x56, 0x4d, 0x78, 0x46, 0x54, 0x41, 0x54, 0x42, 0x67, 0x4e, 0x56, 0x42, + 0x41, 0x6f, 0x54, 0x44, 0x48, 0x52, 0x6f, 0x59, 0x58, 0x64, 0x30, 0x5a, + 0x53, 0x77, 0x67, 0x53, 0x57, 0x35, 0x6a, 0x4c, 0x6a, 0x45, 0x6f, 0x4d, + 0x43, 0x59, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x78, 0x4d, 0x66, 0x0a, + 0x51, 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, + 0x64, 0x47, 0x6c, 0x76, 0x62, 0x69, 0x42, 0x54, 0x5a, 0x58, 0x4a, 0x32, + 0x61, 0x57, 0x4e, 0x6c, 0x63, 0x79, 0x42, 0x45, 0x61, 0x58, 0x5a, 0x70, + 0x63, 0x32, 0x6c, 0x76, 0x62, 0x6a, 0x45, 0x34, 0x4d, 0x44, 0x59, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x43, 0x78, 0x4d, 0x76, 0x4b, 0x47, 0x4d, 0x70, + 0x49, 0x44, 0x49, 0x77, 0x0a, 0x4d, 0x44, 0x67, 0x67, 0x64, 0x47, 0x68, + 0x68, 0x64, 0x33, 0x52, 0x6c, 0x4c, 0x43, 0x42, 0x4a, 0x62, 0x6d, 0x4d, + 0x75, 0x49, 0x43, 0x30, 0x67, 0x52, 0x6d, 0x39, 0x79, 0x49, 0x47, 0x46, + 0x31, 0x64, 0x47, 0x68, 0x76, 0x63, 0x6d, 0x6c, 0x36, 0x5a, 0x57, 0x51, + 0x67, 0x64, 0x58, 0x4e, 0x6c, 0x49, 0x47, 0x39, 0x75, 0x62, 0x48, 0x6b, + 0x78, 0x4a, 0x44, 0x41, 0x69, 0x42, 0x67, 0x4e, 0x56, 0x0a, 0x42, 0x41, + 0x4d, 0x54, 0x47, 0x33, 0x52, 0x6f, 0x59, 0x58, 0x64, 0x30, 0x5a, 0x53, + 0x42, 0x51, 0x63, 0x6d, 0x6c, 0x74, 0x59, 0x58, 0x4a, 0x35, 0x49, 0x46, + 0x4a, 0x76, 0x62, 0x33, 0x51, 0x67, 0x51, 0x30, 0x45, 0x67, 0x4c, 0x53, + 0x42, 0x48, 0x4d, 0x7a, 0x41, 0x65, 0x46, 0x77, 0x30, 0x77, 0x4f, 0x44, + 0x41, 0x30, 0x4d, 0x44, 0x49, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x44, + 0x42, 0x61, 0x0a, 0x46, 0x77, 0x30, 0x7a, 0x4e, 0x7a, 0x45, 0x79, 0x4d, + 0x44, 0x45, 0x79, 0x4d, 0x7a, 0x55, 0x35, 0x4e, 0x54, 0x6c, 0x61, 0x4d, + 0x49, 0x47, 0x75, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, + 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, 0x56, 0x4d, + 0x42, 0x4d, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4d, 0x64, + 0x47, 0x68, 0x68, 0x64, 0x33, 0x52, 0x6c, 0x0a, 0x4c, 0x43, 0x42, 0x4a, + 0x62, 0x6d, 0x4d, 0x75, 0x4d, 0x53, 0x67, 0x77, 0x4a, 0x67, 0x59, 0x44, + 0x56, 0x51, 0x51, 0x4c, 0x45, 0x78, 0x39, 0x44, 0x5a, 0x58, 0x4a, 0x30, + 0x61, 0x57, 0x5a, 0x70, 0x59, 0x32, 0x46, 0x30, 0x61, 0x57, 0x39, 0x75, + 0x49, 0x46, 0x4e, 0x6c, 0x63, 0x6e, 0x5a, 0x70, 0x59, 0x32, 0x56, 0x7a, + 0x49, 0x45, 0x52, 0x70, 0x64, 0x6d, 0x6c, 0x7a, 0x61, 0x57, 0x39, 0x75, + 0x0a, 0x4d, 0x54, 0x67, 0x77, 0x4e, 0x67, 0x59, 0x44, 0x56, 0x51, 0x51, + 0x4c, 0x45, 0x79, 0x38, 0x6f, 0x59, 0x79, 0x6b, 0x67, 0x4d, 0x6a, 0x41, + 0x77, 0x4f, 0x43, 0x42, 0x30, 0x61, 0x47, 0x46, 0x33, 0x64, 0x47, 0x55, + 0x73, 0x49, 0x45, 0x6c, 0x75, 0x59, 0x79, 0x34, 0x67, 0x4c, 0x53, 0x42, + 0x47, 0x62, 0x33, 0x49, 0x67, 0x59, 0x58, 0x56, 0x30, 0x61, 0x47, 0x39, + 0x79, 0x61, 0x58, 0x70, 0x6c, 0x0a, 0x5a, 0x43, 0x42, 0x31, 0x63, 0x32, + 0x55, 0x67, 0x62, 0x32, 0x35, 0x73, 0x65, 0x54, 0x45, 0x6b, 0x4d, 0x43, + 0x49, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x62, 0x64, 0x47, + 0x68, 0x68, 0x64, 0x33, 0x52, 0x6c, 0x49, 0x46, 0x42, 0x79, 0x61, 0x57, + 0x31, 0x68, 0x63, 0x6e, 0x6b, 0x67, 0x55, 0x6d, 0x39, 0x76, 0x64, 0x43, + 0x42, 0x44, 0x51, 0x53, 0x41, 0x74, 0x49, 0x45, 0x63, 0x7a, 0x0a, 0x4d, + 0x49, 0x49, 0x42, 0x49, 0x6a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, + 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x45, 0x46, 0x41, + 0x41, 0x4f, 0x43, 0x41, 0x51, 0x38, 0x41, 0x4d, 0x49, 0x49, 0x42, 0x43, + 0x67, 0x4b, 0x43, 0x41, 0x51, 0x45, 0x41, 0x73, 0x72, 0x38, 0x6e, 0x4c, + 0x50, 0x76, 0x62, 0x32, 0x46, 0x76, 0x64, 0x65, 0x48, 0x73, 0x62, 0x6e, + 0x6e, 0x64, 0x6d, 0x0a, 0x67, 0x63, 0x73, 0x2b, 0x76, 0x48, 0x79, 0x75, + 0x38, 0x36, 0x59, 0x6e, 0x6d, 0x6a, 0x53, 0x6a, 0x61, 0x44, 0x46, 0x78, + 0x4f, 0x44, 0x4e, 0x69, 0x35, 0x50, 0x4e, 0x78, 0x5a, 0x6e, 0x6d, 0x78, + 0x71, 0x57, 0x57, 0x6a, 0x70, 0x59, 0x76, 0x56, 0x6a, 0x32, 0x41, 0x74, + 0x50, 0x30, 0x4c, 0x4d, 0x71, 0x6d, 0x73, 0x79, 0x77, 0x43, 0x50, 0x4c, + 0x4c, 0x45, 0x48, 0x64, 0x35, 0x4e, 0x2f, 0x38, 0x0a, 0x59, 0x5a, 0x7a, + 0x69, 0x63, 0x37, 0x49, 0x69, 0x6c, 0x52, 0x46, 0x44, 0x47, 0x46, 0x2f, + 0x45, 0x74, 0x68, 0x39, 0x58, 0x62, 0x41, 0x6f, 0x46, 0x57, 0x43, 0x4c, + 0x49, 0x4e, 0x6b, 0x77, 0x36, 0x66, 0x4b, 0x58, 0x52, 0x7a, 0x34, 0x61, + 0x76, 0x69, 0x4b, 0x64, 0x45, 0x41, 0x68, 0x4e, 0x30, 0x63, 0x58, 0x4d, + 0x4b, 0x51, 0x6c, 0x6b, 0x43, 0x2b, 0x42, 0x73, 0x55, 0x61, 0x30, 0x4c, + 0x66, 0x0a, 0x62, 0x31, 0x2b, 0x36, 0x61, 0x34, 0x4b, 0x69, 0x6e, 0x56, + 0x76, 0x6e, 0x53, 0x72, 0x30, 0x65, 0x41, 0x58, 0x4c, 0x62, 0x53, 0x33, + 0x54, 0x6f, 0x4f, 0x33, 0x39, 0x2f, 0x66, 0x52, 0x38, 0x45, 0x74, 0x43, + 0x61, 0x62, 0x34, 0x4c, 0x52, 0x61, 0x72, 0x45, 0x63, 0x39, 0x56, 0x62, + 0x6a, 0x58, 0x73, 0x43, 0x5a, 0x53, 0x4b, 0x41, 0x45, 0x78, 0x51, 0x47, + 0x62, 0x59, 0x32, 0x53, 0x53, 0x39, 0x0a, 0x39, 0x69, 0x72, 0x59, 0x37, + 0x43, 0x46, 0x4a, 0x58, 0x4a, 0x76, 0x32, 0x65, 0x75, 0x6c, 0x2f, 0x56, + 0x54, 0x56, 0x2b, 0x6c, 0x6d, 0x75, 0x4e, 0x6b, 0x35, 0x4d, 0x6e, 0x79, + 0x35, 0x4b, 0x37, 0x36, 0x71, 0x78, 0x41, 0x77, 0x4a, 0x2f, 0x43, 0x2b, + 0x49, 0x44, 0x50, 0x58, 0x66, 0x52, 0x61, 0x33, 0x4d, 0x35, 0x30, 0x68, + 0x71, 0x59, 0x2b, 0x62, 0x41, 0x74, 0x54, 0x79, 0x72, 0x32, 0x53, 0x0a, + 0x7a, 0x68, 0x6b, 0x47, 0x63, 0x75, 0x59, 0x4d, 0x58, 0x44, 0x68, 0x70, + 0x78, 0x77, 0x54, 0x57, 0x76, 0x47, 0x7a, 0x4f, 0x57, 0x2f, 0x62, 0x33, + 0x61, 0x4a, 0x7a, 0x63, 0x4a, 0x52, 0x56, 0x49, 0x69, 0x4b, 0x48, 0x70, + 0x71, 0x66, 0x69, 0x59, 0x6e, 0x4f, 0x44, 0x7a, 0x31, 0x54, 0x45, 0x6f, + 0x59, 0x52, 0x46, 0x73, 0x5a, 0x35, 0x61, 0x4e, 0x4f, 0x5a, 0x6e, 0x4c, + 0x77, 0x6b, 0x55, 0x6b, 0x0a, 0x4f, 0x51, 0x49, 0x44, 0x41, 0x51, 0x41, + 0x42, 0x6f, 0x30, 0x49, 0x77, 0x51, 0x44, 0x41, 0x50, 0x42, 0x67, 0x4e, + 0x56, 0x48, 0x52, 0x4d, 0x42, 0x41, 0x66, 0x38, 0x45, 0x42, 0x54, 0x41, + 0x44, 0x41, 0x51, 0x48, 0x2f, 0x4d, 0x41, 0x34, 0x47, 0x41, 0x31, 0x55, + 0x64, 0x44, 0x77, 0x45, 0x42, 0x2f, 0x77, 0x51, 0x45, 0x41, 0x77, 0x49, + 0x42, 0x42, 0x6a, 0x41, 0x64, 0x42, 0x67, 0x4e, 0x56, 0x0a, 0x48, 0x51, + 0x34, 0x45, 0x46, 0x67, 0x51, 0x55, 0x72, 0x57, 0x79, 0x71, 0x6c, 0x47, + 0x43, 0x63, 0x37, 0x65, 0x54, 0x2f, 0x2b, 0x6a, 0x34, 0x4b, 0x64, 0x43, + 0x74, 0x6a, 0x41, 0x2f, 0x65, 0x32, 0x57, 0x62, 0x38, 0x77, 0x44, 0x51, + 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, + 0x45, 0x4c, 0x42, 0x51, 0x41, 0x44, 0x67, 0x67, 0x45, 0x42, 0x41, 0x42, + 0x70, 0x41, 0x0a, 0x32, 0x4a, 0x56, 0x6c, 0x72, 0x41, 0x6d, 0x53, 0x69, + 0x63, 0x59, 0x35, 0x39, 0x42, 0x44, 0x6c, 0x71, 0x51, 0x35, 0x6d, 0x55, + 0x31, 0x31, 0x34, 0x33, 0x76, 0x6f, 0x6b, 0x6b, 0x62, 0x76, 0x6e, 0x52, + 0x46, 0x48, 0x66, 0x78, 0x68, 0x59, 0x30, 0x43, 0x75, 0x39, 0x71, 0x52, + 0x46, 0x48, 0x71, 0x4b, 0x77, 0x65, 0x4b, 0x41, 0x33, 0x72, 0x44, 0x36, + 0x7a, 0x38, 0x4b, 0x4c, 0x46, 0x49, 0x57, 0x0a, 0x6f, 0x43, 0x74, 0x44, + 0x75, 0x53, 0x57, 0x51, 0x50, 0x33, 0x43, 0x70, 0x4d, 0x79, 0x56, 0x74, + 0x52, 0x52, 0x6f, 0x6f, 0x4f, 0x79, 0x66, 0x50, 0x71, 0x73, 0x4d, 0x70, + 0x51, 0x68, 0x76, 0x66, 0x4f, 0x30, 0x7a, 0x41, 0x4d, 0x7a, 0x52, 0x62, + 0x51, 0x59, 0x69, 0x2f, 0x61, 0x79, 0x74, 0x6c, 0x72, 0x79, 0x6a, 0x76, + 0x73, 0x76, 0x58, 0x44, 0x71, 0x6d, 0x62, 0x4f, 0x65, 0x31, 0x62, 0x75, + 0x0a, 0x74, 0x38, 0x6a, 0x4c, 0x5a, 0x38, 0x48, 0x4a, 0x6e, 0x42, 0x6f, + 0x59, 0x75, 0x4d, 0x54, 0x44, 0x53, 0x51, 0x50, 0x78, 0x59, 0x41, 0x35, + 0x51, 0x7a, 0x55, 0x62, 0x46, 0x38, 0x33, 0x64, 0x35, 0x39, 0x37, 0x59, + 0x56, 0x34, 0x44, 0x6a, 0x62, 0x78, 0x79, 0x38, 0x6f, 0x6f, 0x41, 0x77, + 0x2f, 0x64, 0x79, 0x5a, 0x30, 0x32, 0x53, 0x55, 0x53, 0x32, 0x6a, 0x48, + 0x61, 0x47, 0x68, 0x37, 0x63, 0x0a, 0x4b, 0x55, 0x47, 0x52, 0x49, 0x6a, + 0x78, 0x70, 0x70, 0x37, 0x73, 0x43, 0x38, 0x72, 0x5a, 0x63, 0x4a, 0x77, + 0x4f, 0x4a, 0x39, 0x41, 0x62, 0x71, 0x6d, 0x2b, 0x52, 0x79, 0x67, 0x75, + 0x4f, 0x68, 0x43, 0x63, 0x48, 0x70, 0x41, 0x42, 0x6e, 0x54, 0x50, 0x74, + 0x52, 0x77, 0x61, 0x37, 0x70, 0x78, 0x70, 0x71, 0x70, 0x59, 0x72, 0x76, + 0x53, 0x37, 0x36, 0x57, 0x79, 0x32, 0x37, 0x34, 0x66, 0x4d, 0x0a, 0x6d, + 0x37, 0x76, 0x2f, 0x4f, 0x65, 0x5a, 0x57, 0x59, 0x64, 0x4d, 0x4b, 0x70, + 0x38, 0x52, 0x63, 0x54, 0x47, 0x42, 0x37, 0x42, 0x58, 0x63, 0x6d, 0x65, + 0x72, 0x2f, 0x59, 0x42, 0x31, 0x49, 0x73, 0x59, 0x76, 0x64, 0x77, 0x59, + 0x39, 0x6b, 0x35, 0x76, 0x47, 0x38, 0x63, 0x77, 0x6e, 0x6e, 0x63, 0x64, + 0x69, 0x6d, 0x76, 0x7a, 0x73, 0x55, 0x73, 0x5a, 0x41, 0x52, 0x65, 0x69, + 0x44, 0x5a, 0x75, 0x0a, 0x4d, 0x64, 0x52, 0x41, 0x47, 0x6d, 0x49, 0x30, + 0x4e, 0x6a, 0x38, 0x31, 0x41, 0x61, 0x36, 0x73, 0x59, 0x36, 0x41, 0x3d, + 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, + 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, + 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, + 0x47, 0x32, 0x20, 0x4f, 0x3d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x28, 0x63, + 0x29, 0x20, 0x32, 0x30, 0x30, 0x37, 0x20, 0x47, 0x65, 0x6f, 0x54, 0x72, + 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, + 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x0a, 0x23, + 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, + 0x3d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x50, 0x72, + 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x20, 0x4f, + 0x3d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, + 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, + 0x30, 0x37, 0x20, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, + 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, + 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, + 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, + 0x47, 0x32, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, + 0x3a, 0x20, 0x38, 0x30, 0x36, 0x38, 0x32, 0x38, 0x36, 0x33, 0x32, 0x30, + 0x33, 0x33, 0x38, 0x31, 0x30, 0x36, 0x35, 0x37, 0x38, 0x32, 0x31, 0x37, + 0x37, 0x39, 0x30, 0x38, 0x37, 0x35, 0x31, 0x37, 0x39, 0x34, 0x36, 0x31, + 0x39, 0x32, 0x34, 0x33, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x30, 0x31, 0x3a, 0x35, 0x65, 0x3a, 0x64, 0x38, 0x3a, 0x36, 0x62, 0x3a, + 0x62, 0x64, 0x3a, 0x36, 0x66, 0x3a, 0x33, 0x64, 0x3a, 0x38, 0x65, 0x3a, + 0x61, 0x31, 0x3a, 0x33, 0x31, 0x3a, 0x66, 0x38, 0x3a, 0x31, 0x32, 0x3a, + 0x65, 0x30, 0x3a, 0x39, 0x38, 0x3a, 0x37, 0x33, 0x3a, 0x36, 0x61, 0x0a, + 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x38, 0x64, 0x3a, 0x31, + 0x37, 0x3a, 0x38, 0x34, 0x3a, 0x64, 0x35, 0x3a, 0x33, 0x37, 0x3a, 0x66, + 0x33, 0x3a, 0x30, 0x33, 0x3a, 0x37, 0x64, 0x3a, 0x65, 0x63, 0x3a, 0x37, + 0x30, 0x3a, 0x66, 0x65, 0x3a, 0x35, 0x37, 0x3a, 0x38, 0x62, 0x3a, 0x35, + 0x31, 0x3a, 0x39, 0x61, 0x3a, 0x39, 0x39, 0x3a, 0x65, 0x36, 0x3a, 0x31, + 0x30, 0x3a, 0x64, 0x37, 0x3a, 0x62, 0x30, 0x0a, 0x23, 0x20, 0x53, 0x48, + 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, + 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x35, 0x65, 0x3a, 0x64, 0x62, 0x3a, + 0x37, 0x61, 0x3a, 0x63, 0x34, 0x3a, 0x33, 0x62, 0x3a, 0x38, 0x32, 0x3a, + 0x61, 0x30, 0x3a, 0x36, 0x61, 0x3a, 0x38, 0x37, 0x3a, 0x36, 0x31, 0x3a, + 0x65, 0x38, 0x3a, 0x64, 0x37, 0x3a, 0x62, 0x65, 0x3a, 0x34, 0x39, 0x3a, + 0x37, 0x39, 0x3a, 0x65, 0x62, 0x3a, 0x66, 0x32, 0x3a, 0x36, 0x31, 0x3a, + 0x31, 0x66, 0x3a, 0x37, 0x64, 0x3a, 0x64, 0x37, 0x3a, 0x39, 0x62, 0x3a, + 0x66, 0x39, 0x3a, 0x31, 0x63, 0x3a, 0x31, 0x63, 0x3a, 0x36, 0x62, 0x3a, + 0x35, 0x36, 0x3a, 0x36, 0x61, 0x3a, 0x32, 0x31, 0x3a, 0x39, 0x65, 0x3a, + 0x64, 0x37, 0x3a, 0x36, 0x36, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, + 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, + 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, + 0x49, 0x43, 0x72, 0x6a, 0x43, 0x43, 0x41, 0x6a, 0x57, 0x67, 0x41, 0x77, + 0x49, 0x42, 0x41, 0x67, 0x49, 0x51, 0x50, 0x4c, 0x4c, 0x30, 0x53, 0x41, + 0x6f, 0x41, 0x34, 0x76, 0x37, 0x72, 0x4a, 0x44, 0x74, 0x65, 0x59, 0x44, + 0x37, 0x44, 0x61, 0x7a, 0x41, 0x4b, 0x42, 0x67, 0x67, 0x71, 0x68, 0x6b, + 0x6a, 0x4f, 0x50, 0x51, 0x51, 0x44, 0x41, 0x7a, 0x43, 0x42, 0x6d, 0x44, + 0x45, 0x4c, 0x0a, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, + 0x68, 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, 0x46, 0x6a, 0x41, 0x55, 0x42, + 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, 0x44, 0x55, 0x64, 0x6c, 0x62, + 0x31, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x49, 0x45, 0x6c, 0x75, 0x59, + 0x79, 0x34, 0x78, 0x4f, 0x54, 0x41, 0x33, 0x42, 0x67, 0x4e, 0x56, 0x42, + 0x41, 0x73, 0x54, 0x4d, 0x43, 0x68, 0x6a, 0x0a, 0x4b, 0x53, 0x41, 0x79, + 0x4d, 0x44, 0x41, 0x33, 0x49, 0x45, 0x64, 0x6c, 0x62, 0x31, 0x52, 0x79, + 0x64, 0x58, 0x4e, 0x30, 0x49, 0x45, 0x6c, 0x75, 0x59, 0x79, 0x34, 0x67, + 0x4c, 0x53, 0x42, 0x47, 0x62, 0x33, 0x49, 0x67, 0x59, 0x58, 0x56, 0x30, + 0x61, 0x47, 0x39, 0x79, 0x61, 0x58, 0x70, 0x6c, 0x5a, 0x43, 0x42, 0x31, + 0x63, 0x32, 0x55, 0x67, 0x62, 0x32, 0x35, 0x73, 0x65, 0x54, 0x45, 0x32, + 0x0a, 0x4d, 0x44, 0x51, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, + 0x74, 0x52, 0x32, 0x56, 0x76, 0x56, 0x48, 0x4a, 0x31, 0x63, 0x33, 0x51, + 0x67, 0x55, 0x48, 0x4a, 0x70, 0x62, 0x57, 0x46, 0x79, 0x65, 0x53, 0x42, + 0x44, 0x5a, 0x58, 0x4a, 0x30, 0x61, 0x57, 0x5a, 0x70, 0x59, 0x32, 0x46, + 0x30, 0x61, 0x57, 0x39, 0x75, 0x49, 0x45, 0x46, 0x31, 0x64, 0x47, 0x68, + 0x76, 0x63, 0x6d, 0x6c, 0x30, 0x0a, 0x65, 0x53, 0x41, 0x74, 0x49, 0x45, + 0x63, 0x79, 0x4d, 0x42, 0x34, 0x58, 0x44, 0x54, 0x41, 0x33, 0x4d, 0x54, + 0x45, 0x77, 0x4e, 0x54, 0x41, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x46, + 0x6f, 0x58, 0x44, 0x54, 0x4d, 0x34, 0x4d, 0x44, 0x45, 0x78, 0x4f, 0x44, + 0x49, 0x7a, 0x4e, 0x54, 0x6b, 0x31, 0x4f, 0x56, 0x6f, 0x77, 0x67, 0x5a, + 0x67, 0x78, 0x43, 0x7a, 0x41, 0x4a, 0x42, 0x67, 0x4e, 0x56, 0x0a, 0x42, + 0x41, 0x59, 0x54, 0x41, 0x6c, 0x56, 0x54, 0x4d, 0x52, 0x59, 0x77, 0x46, + 0x41, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4b, 0x45, 0x77, 0x31, 0x48, 0x5a, + 0x57, 0x39, 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x4a, 0x62, + 0x6d, 0x4d, 0x75, 0x4d, 0x54, 0x6b, 0x77, 0x4e, 0x77, 0x59, 0x44, 0x56, + 0x51, 0x51, 0x4c, 0x45, 0x7a, 0x41, 0x6f, 0x59, 0x79, 0x6b, 0x67, 0x4d, + 0x6a, 0x41, 0x77, 0x0a, 0x4e, 0x79, 0x42, 0x48, 0x5a, 0x57, 0x39, 0x55, + 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x4a, 0x62, 0x6d, 0x4d, 0x75, + 0x49, 0x43, 0x30, 0x67, 0x52, 0x6d, 0x39, 0x79, 0x49, 0x47, 0x46, 0x31, + 0x64, 0x47, 0x68, 0x76, 0x63, 0x6d, 0x6c, 0x36, 0x5a, 0x57, 0x51, 0x67, + 0x64, 0x58, 0x4e, 0x6c, 0x49, 0x47, 0x39, 0x75, 0x62, 0x48, 0x6b, 0x78, + 0x4e, 0x6a, 0x41, 0x30, 0x42, 0x67, 0x4e, 0x56, 0x0a, 0x42, 0x41, 0x4d, + 0x54, 0x4c, 0x55, 0x64, 0x6c, 0x62, 0x31, 0x52, 0x79, 0x64, 0x58, 0x4e, + 0x30, 0x49, 0x46, 0x42, 0x79, 0x61, 0x57, 0x31, 0x68, 0x63, 0x6e, 0x6b, + 0x67, 0x51, 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, + 0x68, 0x64, 0x47, 0x6c, 0x76, 0x62, 0x69, 0x42, 0x42, 0x64, 0x58, 0x52, + 0x6f, 0x62, 0x33, 0x4a, 0x70, 0x64, 0x48, 0x6b, 0x67, 0x4c, 0x53, 0x42, + 0x48, 0x0a, 0x4d, 0x6a, 0x42, 0x32, 0x4d, 0x42, 0x41, 0x47, 0x42, 0x79, + 0x71, 0x47, 0x53, 0x4d, 0x34, 0x39, 0x41, 0x67, 0x45, 0x47, 0x42, 0x53, + 0x75, 0x42, 0x42, 0x41, 0x41, 0x69, 0x41, 0x32, 0x49, 0x41, 0x42, 0x42, + 0x57, 0x78, 0x36, 0x50, 0x30, 0x44, 0x46, 0x55, 0x50, 0x6c, 0x72, 0x4f, + 0x75, 0x48, 0x4e, 0x78, 0x46, 0x69, 0x37, 0x39, 0x4b, 0x44, 0x4e, 0x6c, + 0x4a, 0x39, 0x52, 0x56, 0x63, 0x4c, 0x0a, 0x53, 0x6f, 0x31, 0x37, 0x56, + 0x44, 0x73, 0x36, 0x62, 0x6c, 0x38, 0x56, 0x41, 0x73, 0x42, 0x51, 0x70, + 0x73, 0x38, 0x6c, 0x4c, 0x33, 0x33, 0x4b, 0x53, 0x4c, 0x6a, 0x48, 0x55, + 0x47, 0x4d, 0x63, 0x4b, 0x69, 0x45, 0x49, 0x66, 0x4a, 0x6f, 0x32, 0x32, + 0x41, 0x76, 0x2b, 0x30, 0x53, 0x62, 0x46, 0x57, 0x44, 0x45, 0x77, 0x4b, + 0x43, 0x58, 0x7a, 0x58, 0x56, 0x32, 0x6a, 0x75, 0x4c, 0x61, 0x6c, 0x0a, + 0x74, 0x4a, 0x4c, 0x74, 0x62, 0x43, 0x79, 0x66, 0x36, 0x39, 0x31, 0x44, + 0x69, 0x61, 0x49, 0x38, 0x53, 0x30, 0x69, 0x52, 0x48, 0x56, 0x44, 0x73, + 0x4a, 0x74, 0x2f, 0x57, 0x59, 0x43, 0x36, 0x39, 0x49, 0x61, 0x4e, 0x43, + 0x4d, 0x45, 0x41, 0x77, 0x44, 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, 0x54, + 0x41, 0x51, 0x48, 0x2f, 0x42, 0x41, 0x55, 0x77, 0x41, 0x77, 0x45, 0x42, + 0x2f, 0x7a, 0x41, 0x4f, 0x0a, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x38, + 0x42, 0x41, 0x66, 0x38, 0x45, 0x42, 0x41, 0x4d, 0x43, 0x41, 0x51, 0x59, + 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, 0x52, 0x30, 0x4f, 0x42, 0x42, 0x59, + 0x45, 0x46, 0x42, 0x56, 0x66, 0x4e, 0x56, 0x64, 0x52, 0x56, 0x66, 0x73, + 0x6c, 0x73, 0x71, 0x30, 0x44, 0x61, 0x66, 0x77, 0x42, 0x6f, 0x2f, 0x71, + 0x2b, 0x45, 0x56, 0x58, 0x56, 0x4d, 0x41, 0x6f, 0x47, 0x0a, 0x43, 0x43, + 0x71, 0x47, 0x53, 0x4d, 0x34, 0x39, 0x42, 0x41, 0x4d, 0x44, 0x41, 0x32, + 0x63, 0x41, 0x4d, 0x47, 0x51, 0x43, 0x4d, 0x47, 0x53, 0x57, 0x57, 0x61, + 0x62, 0x6f, 0x43, 0x64, 0x36, 0x4c, 0x75, 0x76, 0x70, 0x61, 0x69, 0x49, + 0x6a, 0x77, 0x48, 0x35, 0x48, 0x54, 0x52, 0x71, 0x6a, 0x79, 0x53, 0x6b, + 0x77, 0x43, 0x59, 0x2f, 0x74, 0x73, 0x58, 0x7a, 0x6a, 0x62, 0x4c, 0x6b, + 0x47, 0x54, 0x0a, 0x71, 0x51, 0x37, 0x6d, 0x6e, 0x64, 0x77, 0x78, 0x48, + 0x4c, 0x4b, 0x67, 0x70, 0x78, 0x67, 0x63, 0x65, 0x65, 0x48, 0x48, 0x4e, + 0x67, 0x49, 0x77, 0x4f, 0x6c, 0x61, 0x76, 0x6d, 0x6e, 0x52, 0x73, 0x39, + 0x76, 0x75, 0x44, 0x34, 0x44, 0x50, 0x54, 0x43, 0x46, 0x2b, 0x68, 0x6e, + 0x4d, 0x4a, 0x62, 0x6e, 0x30, 0x62, 0x57, 0x74, 0x73, 0x75, 0x52, 0x42, + 0x6d, 0x4f, 0x69, 0x42, 0x75, 0x63, 0x7a, 0x0a, 0x72, 0x44, 0x36, 0x6f, + 0x67, 0x52, 0x4c, 0x51, 0x79, 0x37, 0x72, 0x51, 0x6b, 0x67, 0x75, 0x32, + 0x6e, 0x70, 0x61, 0x71, 0x42, 0x41, 0x2b, 0x4b, 0x0a, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, + 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, + 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, + 0x3d, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x55, 0x6e, + 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, + 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, + 0x20, 0x4f, 0x3d, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, + 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x56, 0x65, 0x72, + 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, + 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x28, 0x63, 0x29, 0x20, + 0x32, 0x30, 0x30, 0x38, 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, + 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, + 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, + 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x0a, 0x23, 0x20, + 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, + 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x55, 0x6e, 0x69, + 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, + 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, + 0x4f, 0x3d, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, + 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x56, 0x65, 0x72, 0x69, + 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, + 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, + 0x30, 0x30, 0x38, 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, + 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, + 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, + 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x0a, 0x23, 0x20, 0x4c, + 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x56, 0x65, 0x72, 0x69, 0x53, + 0x69, 0x67, 0x6e, 0x20, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, + 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, + 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x38, 0x35, 0x32, 0x30, 0x39, 0x35, + 0x37, 0x34, 0x37, 0x33, 0x34, 0x30, 0x38, 0x34, 0x35, 0x38, 0x31, 0x39, + 0x31, 0x37, 0x37, 0x36, 0x33, 0x37, 0x35, 0x32, 0x36, 0x34, 0x34, 0x30, + 0x33, 0x31, 0x37, 0x32, 0x36, 0x38, 0x37, 0x37, 0x0a, 0x23, 0x20, 0x4d, + 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x3a, 0x20, 0x38, 0x65, 0x3a, 0x61, 0x64, 0x3a, 0x62, 0x35, + 0x3a, 0x30, 0x31, 0x3a, 0x61, 0x61, 0x3a, 0x34, 0x64, 0x3a, 0x38, 0x31, + 0x3a, 0x65, 0x34, 0x3a, 0x38, 0x63, 0x3a, 0x31, 0x64, 0x3a, 0x64, 0x31, + 0x3a, 0x65, 0x31, 0x3a, 0x31, 0x34, 0x3a, 0x30, 0x30, 0x3a, 0x39, 0x35, + 0x3a, 0x31, 0x39, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x33, 0x36, 0x3a, 0x37, 0x39, 0x3a, 0x63, 0x61, 0x3a, 0x33, 0x35, 0x3a, + 0x36, 0x36, 0x3a, 0x38, 0x37, 0x3a, 0x37, 0x32, 0x3a, 0x33, 0x30, 0x3a, + 0x34, 0x64, 0x3a, 0x33, 0x30, 0x3a, 0x61, 0x35, 0x3a, 0x66, 0x62, 0x3a, + 0x38, 0x37, 0x3a, 0x33, 0x62, 0x3a, 0x30, 0x66, 0x3a, 0x61, 0x37, 0x3a, + 0x37, 0x62, 0x3a, 0x62, 0x37, 0x3a, 0x30, 0x64, 0x3a, 0x35, 0x34, 0x0a, + 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, + 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x32, 0x33, + 0x3a, 0x39, 0x39, 0x3a, 0x35, 0x36, 0x3a, 0x31, 0x31, 0x3a, 0x32, 0x37, + 0x3a, 0x61, 0x35, 0x3a, 0x37, 0x31, 0x3a, 0x32, 0x35, 0x3a, 0x64, 0x65, + 0x3a, 0x38, 0x63, 0x3a, 0x65, 0x66, 0x3a, 0x65, 0x61, 0x3a, 0x36, 0x31, + 0x3a, 0x30, 0x64, 0x3a, 0x64, 0x66, 0x3a, 0x32, 0x66, 0x3a, 0x61, 0x30, + 0x3a, 0x37, 0x38, 0x3a, 0x62, 0x35, 0x3a, 0x63, 0x38, 0x3a, 0x30, 0x36, + 0x3a, 0x37, 0x66, 0x3a, 0x34, 0x65, 0x3a, 0x38, 0x32, 0x3a, 0x38, 0x32, + 0x3a, 0x39, 0x30, 0x3a, 0x62, 0x66, 0x3a, 0x62, 0x38, 0x3a, 0x36, 0x30, + 0x3a, 0x65, 0x38, 0x3a, 0x34, 0x62, 0x3a, 0x33, 0x63, 0x0a, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, + 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x45, 0x75, 0x54, 0x43, 0x43, 0x41, 0x36, + 0x47, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x51, 0x51, 0x42, + 0x72, 0x45, 0x5a, 0x43, 0x47, 0x7a, 0x45, 0x79, 0x45, 0x44, 0x44, 0x72, + 0x76, 0x6b, 0x45, 0x68, 0x72, 0x46, 0x48, 0x54, 0x41, 0x4e, 0x42, 0x67, + 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, + 0x73, 0x46, 0x41, 0x44, 0x43, 0x42, 0x0a, 0x76, 0x54, 0x45, 0x4c, 0x4d, + 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x56, + 0x56, 0x4d, 0x78, 0x46, 0x7a, 0x41, 0x56, 0x42, 0x67, 0x4e, 0x56, 0x42, + 0x41, 0x6f, 0x54, 0x44, 0x6c, 0x5a, 0x6c, 0x63, 0x6d, 0x6c, 0x54, 0x61, + 0x57, 0x64, 0x75, 0x4c, 0x43, 0x42, 0x4a, 0x62, 0x6d, 0x4d, 0x75, 0x4d, + 0x52, 0x38, 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, 0x0a, + 0x45, 0x78, 0x5a, 0x57, 0x5a, 0x58, 0x4a, 0x70, 0x55, 0x32, 0x6c, 0x6e, + 0x62, 0x69, 0x42, 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x4f, + 0x5a, 0x58, 0x52, 0x33, 0x62, 0x33, 0x4a, 0x72, 0x4d, 0x54, 0x6f, 0x77, + 0x4f, 0x41, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, 0x7a, 0x45, 0x6f, + 0x59, 0x79, 0x6b, 0x67, 0x4d, 0x6a, 0x41, 0x77, 0x4f, 0x43, 0x42, 0x57, + 0x5a, 0x58, 0x4a, 0x70, 0x0a, 0x55, 0x32, 0x6c, 0x6e, 0x62, 0x69, 0x77, + 0x67, 0x53, 0x57, 0x35, 0x6a, 0x4c, 0x69, 0x41, 0x74, 0x49, 0x45, 0x5a, + 0x76, 0x63, 0x69, 0x42, 0x68, 0x64, 0x58, 0x52, 0x6f, 0x62, 0x33, 0x4a, + 0x70, 0x65, 0x6d, 0x56, 0x6b, 0x49, 0x48, 0x56, 0x7a, 0x5a, 0x53, 0x42, + 0x76, 0x62, 0x6d, 0x78, 0x35, 0x4d, 0x54, 0x67, 0x77, 0x4e, 0x67, 0x59, + 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, 0x79, 0x39, 0x57, 0x0a, 0x5a, 0x58, + 0x4a, 0x70, 0x55, 0x32, 0x6c, 0x6e, 0x62, 0x69, 0x42, 0x56, 0x62, 0x6d, + 0x6c, 0x32, 0x5a, 0x58, 0x4a, 0x7a, 0x59, 0x57, 0x77, 0x67, 0x55, 0x6d, + 0x39, 0x76, 0x64, 0x43, 0x42, 0x44, 0x5a, 0x58, 0x4a, 0x30, 0x61, 0x57, + 0x5a, 0x70, 0x59, 0x32, 0x46, 0x30, 0x61, 0x57, 0x39, 0x75, 0x49, 0x45, + 0x46, 0x31, 0x64, 0x47, 0x68, 0x76, 0x63, 0x6d, 0x6c, 0x30, 0x65, 0x54, + 0x41, 0x65, 0x0a, 0x46, 0x77, 0x30, 0x77, 0x4f, 0x44, 0x41, 0x30, 0x4d, + 0x44, 0x49, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x44, 0x42, 0x61, 0x46, + 0x77, 0x30, 0x7a, 0x4e, 0x7a, 0x45, 0x79, 0x4d, 0x44, 0x45, 0x79, 0x4d, + 0x7a, 0x55, 0x35, 0x4e, 0x54, 0x6c, 0x61, 0x4d, 0x49, 0x47, 0x39, 0x4d, + 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, + 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, 0x58, 0x0a, 0x4d, 0x42, 0x55, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4f, 0x56, 0x6d, 0x56, 0x79, + 0x61, 0x56, 0x4e, 0x70, 0x5a, 0x32, 0x34, 0x73, 0x49, 0x45, 0x6c, 0x75, + 0x59, 0x79, 0x34, 0x78, 0x48, 0x7a, 0x41, 0x64, 0x42, 0x67, 0x4e, 0x56, + 0x42, 0x41, 0x73, 0x54, 0x46, 0x6c, 0x5a, 0x6c, 0x63, 0x6d, 0x6c, 0x54, + 0x61, 0x57, 0x64, 0x75, 0x49, 0x46, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, + 0x0a, 0x49, 0x45, 0x35, 0x6c, 0x64, 0x48, 0x64, 0x76, 0x63, 0x6d, 0x73, + 0x78, 0x4f, 0x6a, 0x41, 0x34, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, + 0x54, 0x4d, 0x53, 0x68, 0x6a, 0x4b, 0x53, 0x41, 0x79, 0x4d, 0x44, 0x41, + 0x34, 0x49, 0x46, 0x5a, 0x6c, 0x63, 0x6d, 0x6c, 0x54, 0x61, 0x57, 0x64, + 0x75, 0x4c, 0x43, 0x42, 0x4a, 0x62, 0x6d, 0x4d, 0x75, 0x49, 0x43, 0x30, + 0x67, 0x52, 0x6d, 0x39, 0x79, 0x0a, 0x49, 0x47, 0x46, 0x31, 0x64, 0x47, + 0x68, 0x76, 0x63, 0x6d, 0x6c, 0x36, 0x5a, 0x57, 0x51, 0x67, 0x64, 0x58, + 0x4e, 0x6c, 0x49, 0x47, 0x39, 0x75, 0x62, 0x48, 0x6b, 0x78, 0x4f, 0x44, + 0x41, 0x32, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x4d, 0x54, 0x4c, 0x31, + 0x5a, 0x6c, 0x63, 0x6d, 0x6c, 0x54, 0x61, 0x57, 0x64, 0x75, 0x49, 0x46, + 0x56, 0x75, 0x61, 0x58, 0x5a, 0x6c, 0x63, 0x6e, 0x4e, 0x68, 0x0a, 0x62, + 0x43, 0x42, 0x53, 0x62, 0x32, 0x39, 0x30, 0x49, 0x45, 0x4e, 0x6c, 0x63, + 0x6e, 0x52, 0x70, 0x5a, 0x6d, 0x6c, 0x6a, 0x59, 0x58, 0x52, 0x70, 0x62, + 0x32, 0x34, 0x67, 0x51, 0x58, 0x56, 0x30, 0x61, 0x47, 0x39, 0x79, 0x61, + 0x58, 0x52, 0x35, 0x4d, 0x49, 0x49, 0x42, 0x49, 0x6a, 0x41, 0x4e, 0x42, + 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, + 0x51, 0x45, 0x46, 0x0a, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x51, 0x38, 0x41, + 0x4d, 0x49, 0x49, 0x42, 0x43, 0x67, 0x4b, 0x43, 0x41, 0x51, 0x45, 0x41, + 0x78, 0x32, 0x45, 0x33, 0x58, 0x72, 0x45, 0x42, 0x4e, 0x4e, 0x74, 0x69, + 0x31, 0x78, 0x57, 0x62, 0x2f, 0x31, 0x68, 0x61, 0x6a, 0x43, 0x4d, 0x6a, + 0x31, 0x6d, 0x43, 0x4f, 0x6b, 0x64, 0x65, 0x51, 0x6d, 0x49, 0x4e, 0x36, + 0x35, 0x6c, 0x67, 0x5a, 0x4f, 0x49, 0x7a, 0x46, 0x0a, 0x39, 0x75, 0x56, + 0x6b, 0x68, 0x62, 0x53, 0x69, 0x63, 0x66, 0x76, 0x74, 0x76, 0x62, 0x6e, + 0x61, 0x7a, 0x55, 0x30, 0x41, 0x74, 0x4d, 0x67, 0x74, 0x63, 0x36, 0x58, + 0x48, 0x61, 0x58, 0x47, 0x56, 0x48, 0x7a, 0x6b, 0x38, 0x73, 0x6b, 0x51, + 0x48, 0x6e, 0x4f, 0x67, 0x4f, 0x2b, 0x6b, 0x31, 0x4b, 0x78, 0x43, 0x48, + 0x66, 0x4b, 0x57, 0x47, 0x50, 0x4d, 0x69, 0x4a, 0x68, 0x67, 0x73, 0x57, + 0x48, 0x0a, 0x48, 0x32, 0x36, 0x4d, 0x66, 0x46, 0x38, 0x57, 0x49, 0x46, + 0x46, 0x45, 0x30, 0x58, 0x42, 0x50, 0x56, 0x2b, 0x72, 0x6a, 0x48, 0x4f, + 0x50, 0x4d, 0x65, 0x65, 0x35, 0x59, 0x32, 0x41, 0x37, 0x43, 0x73, 0x30, + 0x57, 0x54, 0x77, 0x43, 0x7a, 0x6e, 0x6d, 0x68, 0x63, 0x72, 0x65, 0x77, + 0x41, 0x33, 0x65, 0x6b, 0x45, 0x7a, 0x65, 0x4f, 0x45, 0x7a, 0x34, 0x76, + 0x4d, 0x51, 0x47, 0x6e, 0x2b, 0x48, 0x0a, 0x4c, 0x4c, 0x37, 0x32, 0x39, + 0x66, 0x64, 0x43, 0x34, 0x75, 0x57, 0x2f, 0x68, 0x32, 0x4b, 0x4a, 0x58, + 0x77, 0x42, 0x4c, 0x33, 0x38, 0x58, 0x64, 0x35, 0x48, 0x56, 0x45, 0x4d, + 0x6b, 0x45, 0x36, 0x48, 0x6e, 0x46, 0x75, 0x61, 0x63, 0x73, 0x4c, 0x64, + 0x55, 0x59, 0x49, 0x30, 0x63, 0x72, 0x53, 0x4b, 0x35, 0x58, 0x51, 0x7a, + 0x2f, 0x75, 0x35, 0x51, 0x47, 0x74, 0x6b, 0x6a, 0x46, 0x64, 0x4e, 0x0a, + 0x2f, 0x42, 0x4d, 0x52, 0x65, 0x59, 0x54, 0x74, 0x58, 0x6c, 0x54, 0x32, + 0x4e, 0x4a, 0x38, 0x49, 0x41, 0x66, 0x4d, 0x51, 0x4a, 0x51, 0x59, 0x58, + 0x53, 0x74, 0x72, 0x78, 0x48, 0x58, 0x70, 0x6d, 0x61, 0x35, 0x68, 0x67, + 0x5a, 0x71, 0x54, 0x5a, 0x37, 0x39, 0x49, 0x75, 0x67, 0x76, 0x48, 0x77, + 0x37, 0x77, 0x6e, 0x71, 0x52, 0x4d, 0x6b, 0x56, 0x61, 0x75, 0x49, 0x44, + 0x62, 0x6a, 0x50, 0x54, 0x0a, 0x72, 0x4a, 0x39, 0x56, 0x41, 0x4d, 0x66, + 0x32, 0x43, 0x47, 0x71, 0x55, 0x75, 0x56, 0x2f, 0x63, 0x34, 0x44, 0x50, + 0x78, 0x68, 0x47, 0x44, 0x35, 0x57, 0x79, 0x63, 0x52, 0x74, 0x50, 0x77, + 0x57, 0x38, 0x72, 0x74, 0x57, 0x61, 0x6f, 0x41, 0x6c, 0x6a, 0x51, 0x49, + 0x44, 0x41, 0x51, 0x41, 0x42, 0x6f, 0x34, 0x47, 0x79, 0x4d, 0x49, 0x47, + 0x76, 0x4d, 0x41, 0x38, 0x47, 0x41, 0x31, 0x55, 0x64, 0x0a, 0x45, 0x77, + 0x45, 0x42, 0x2f, 0x77, 0x51, 0x46, 0x4d, 0x41, 0x4d, 0x42, 0x41, 0x66, + 0x38, 0x77, 0x44, 0x67, 0x59, 0x44, 0x56, 0x52, 0x30, 0x50, 0x41, 0x51, + 0x48, 0x2f, 0x42, 0x41, 0x51, 0x44, 0x41, 0x67, 0x45, 0x47, 0x4d, 0x47, + 0x30, 0x47, 0x43, 0x43, 0x73, 0x47, 0x41, 0x51, 0x55, 0x46, 0x42, 0x77, + 0x45, 0x4d, 0x42, 0x47, 0x45, 0x77, 0x58, 0x36, 0x46, 0x64, 0x6f, 0x46, + 0x73, 0x77, 0x0a, 0x57, 0x54, 0x42, 0x58, 0x4d, 0x46, 0x55, 0x57, 0x43, + 0x57, 0x6c, 0x74, 0x59, 0x57, 0x64, 0x6c, 0x4c, 0x32, 0x64, 0x70, 0x5a, + 0x6a, 0x41, 0x68, 0x4d, 0x42, 0x38, 0x77, 0x42, 0x77, 0x59, 0x46, 0x4b, + 0x77, 0x34, 0x44, 0x41, 0x68, 0x6f, 0x45, 0x46, 0x49, 0x2f, 0x6c, 0x30, + 0x78, 0x71, 0x47, 0x72, 0x49, 0x32, 0x4f, 0x61, 0x38, 0x50, 0x50, 0x67, + 0x47, 0x72, 0x55, 0x53, 0x42, 0x67, 0x73, 0x0a, 0x65, 0x78, 0x6b, 0x75, + 0x4d, 0x43, 0x55, 0x57, 0x49, 0x32, 0x68, 0x30, 0x64, 0x48, 0x41, 0x36, + 0x4c, 0x79, 0x39, 0x73, 0x62, 0x32, 0x64, 0x76, 0x4c, 0x6e, 0x5a, 0x6c, + 0x63, 0x6d, 0x6c, 0x7a, 0x61, 0x57, 0x64, 0x75, 0x4c, 0x6d, 0x4e, 0x76, + 0x62, 0x53, 0x39, 0x32, 0x63, 0x32, 0x78, 0x76, 0x5a, 0x32, 0x38, 0x75, + 0x5a, 0x32, 0x6c, 0x6d, 0x4d, 0x42, 0x30, 0x47, 0x41, 0x31, 0x55, 0x64, + 0x0a, 0x44, 0x67, 0x51, 0x57, 0x42, 0x42, 0x53, 0x32, 0x64, 0x2f, 0x70, + 0x70, 0x53, 0x45, 0x65, 0x66, 0x55, 0x78, 0x4c, 0x56, 0x77, 0x75, 0x6f, + 0x48, 0x4d, 0x6e, 0x59, 0x48, 0x30, 0x5a, 0x63, 0x48, 0x47, 0x54, 0x41, + 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, + 0x42, 0x41, 0x51, 0x73, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x51, 0x45, + 0x41, 0x53, 0x76, 0x6a, 0x34, 0x0a, 0x73, 0x41, 0x50, 0x6d, 0x4c, 0x47, + 0x64, 0x37, 0x35, 0x4a, 0x52, 0x33, 0x59, 0x38, 0x78, 0x75, 0x54, 0x50, + 0x6c, 0x39, 0x44, 0x67, 0x33, 0x63, 0x79, 0x4c, 0x6b, 0x31, 0x75, 0x58, + 0x42, 0x50, 0x59, 0x2f, 0x6f, 0x6b, 0x2b, 0x6d, 0x79, 0x44, 0x6a, 0x45, + 0x65, 0x64, 0x4f, 0x32, 0x50, 0x7a, 0x6d, 0x76, 0x6c, 0x32, 0x4d, 0x70, + 0x57, 0x52, 0x73, 0x58, 0x65, 0x38, 0x72, 0x4a, 0x71, 0x2b, 0x0a, 0x73, + 0x65, 0x51, 0x78, 0x49, 0x63, 0x61, 0x42, 0x6c, 0x56, 0x5a, 0x61, 0x44, + 0x72, 0x48, 0x43, 0x31, 0x4c, 0x47, 0x6d, 0x57, 0x61, 0x7a, 0x78, 0x59, + 0x38, 0x75, 0x34, 0x54, 0x42, 0x31, 0x5a, 0x6b, 0x45, 0x72, 0x76, 0x6b, + 0x42, 0x59, 0x6f, 0x48, 0x31, 0x71, 0x75, 0x45, 0x50, 0x75, 0x42, 0x55, + 0x44, 0x67, 0x4d, 0x62, 0x4d, 0x7a, 0x78, 0x50, 0x63, 0x50, 0x31, 0x59, + 0x2b, 0x4f, 0x7a, 0x0a, 0x34, 0x79, 0x48, 0x4a, 0x4a, 0x44, 0x6e, 0x70, + 0x2f, 0x52, 0x56, 0x6d, 0x52, 0x76, 0x51, 0x62, 0x45, 0x64, 0x42, 0x4e, + 0x63, 0x36, 0x4e, 0x39, 0x52, 0x76, 0x6b, 0x39, 0x37, 0x61, 0x68, 0x66, + 0x59, 0x74, 0x54, 0x78, 0x50, 0x2f, 0x6a, 0x67, 0x64, 0x46, 0x63, 0x72, + 0x47, 0x4a, 0x32, 0x42, 0x74, 0x4d, 0x51, 0x6f, 0x32, 0x70, 0x53, 0x58, + 0x70, 0x58, 0x44, 0x72, 0x72, 0x42, 0x32, 0x2b, 0x0a, 0x42, 0x78, 0x48, + 0x77, 0x31, 0x64, 0x76, 0x64, 0x35, 0x59, 0x7a, 0x77, 0x31, 0x54, 0x4b, + 0x77, 0x67, 0x2b, 0x5a, 0x58, 0x34, 0x6f, 0x2b, 0x2f, 0x76, 0x71, 0x47, + 0x71, 0x76, 0x7a, 0x30, 0x64, 0x74, 0x64, 0x51, 0x34, 0x36, 0x74, 0x65, + 0x77, 0x58, 0x44, 0x70, 0x50, 0x61, 0x6a, 0x2b, 0x50, 0x77, 0x47, 0x5a, + 0x73, 0x59, 0x36, 0x72, 0x70, 0x32, 0x61, 0x51, 0x57, 0x39, 0x49, 0x48, + 0x52, 0x0a, 0x6c, 0x52, 0x51, 0x4f, 0x66, 0x63, 0x32, 0x56, 0x4e, 0x4e, + 0x6e, 0x53, 0x6a, 0x33, 0x42, 0x7a, 0x67, 0x58, 0x75, 0x63, 0x66, 0x72, + 0x32, 0x59, 0x59, 0x64, 0x68, 0x46, 0x68, 0x35, 0x69, 0x51, 0x78, 0x65, + 0x75, 0x47, 0x4d, 0x4d, 0x59, 0x31, 0x76, 0x2f, 0x44, 0x2f, 0x77, 0x31, + 0x57, 0x49, 0x67, 0x30, 0x76, 0x76, 0x42, 0x5a, 0x49, 0x47, 0x63, 0x66, + 0x4b, 0x34, 0x6d, 0x4a, 0x4f, 0x33, 0x0a, 0x37, 0x4d, 0x32, 0x43, 0x59, + 0x66, 0x45, 0x34, 0x35, 0x6b, 0x2b, 0x58, 0x6d, 0x43, 0x70, 0x61, 0x6a, + 0x51, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, + 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, + 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x56, 0x65, 0x72, 0x69, + 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, + 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, + 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x34, 0x20, 0x4f, 0x3d, 0x56, + 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, + 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, + 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, + 0x6f, 0x72, 0x6b, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x37, + 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, + 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, + 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x56, 0x65, 0x72, 0x69, + 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, + 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, + 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x34, 0x20, 0x4f, 0x3d, 0x56, + 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, + 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, + 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, + 0x6f, 0x72, 0x6b, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x37, + 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, + 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, + 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, + 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, + 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, + 0x2d, 0x20, 0x47, 0x34, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, + 0x61, 0x6c, 0x3a, 0x20, 0x36, 0x33, 0x31, 0x34, 0x33, 0x34, 0x38, 0x34, + 0x33, 0x34, 0x38, 0x31, 0x35, 0x33, 0x35, 0x30, 0x36, 0x36, 0x36, 0x35, + 0x33, 0x31, 0x31, 0x39, 0x38, 0x35, 0x35, 0x30, 0x31, 0x34, 0x35, 0x38, + 0x36, 0x34, 0x30, 0x30, 0x35, 0x31, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, + 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, + 0x3a, 0x20, 0x33, 0x61, 0x3a, 0x35, 0x32, 0x3a, 0x65, 0x31, 0x3a, 0x65, + 0x37, 0x3a, 0x66, 0x64, 0x3a, 0x36, 0x66, 0x3a, 0x33, 0x61, 0x3a, 0x65, + 0x33, 0x3a, 0x36, 0x66, 0x3a, 0x66, 0x33, 0x3a, 0x36, 0x66, 0x3a, 0x39, + 0x39, 0x3a, 0x31, 0x62, 0x3a, 0x66, 0x39, 0x3a, 0x32, 0x32, 0x3a, 0x34, + 0x31, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, + 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x32, 0x32, + 0x3a, 0x64, 0x35, 0x3a, 0x64, 0x38, 0x3a, 0x64, 0x66, 0x3a, 0x38, 0x66, + 0x3a, 0x30, 0x32, 0x3a, 0x33, 0x31, 0x3a, 0x64, 0x31, 0x3a, 0x38, 0x64, + 0x3a, 0x66, 0x37, 0x3a, 0x39, 0x64, 0x3a, 0x62, 0x37, 0x3a, 0x63, 0x66, + 0x3a, 0x38, 0x61, 0x3a, 0x32, 0x64, 0x3a, 0x36, 0x34, 0x3a, 0x63, 0x39, + 0x3a, 0x33, 0x66, 0x3a, 0x36, 0x63, 0x3a, 0x33, 0x61, 0x0a, 0x23, 0x20, + 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x36, 0x39, 0x3a, 0x64, + 0x64, 0x3a, 0x64, 0x37, 0x3a, 0x65, 0x61, 0x3a, 0x39, 0x30, 0x3a, 0x62, + 0x62, 0x3a, 0x35, 0x37, 0x3a, 0x63, 0x39, 0x3a, 0x33, 0x65, 0x3a, 0x31, + 0x33, 0x3a, 0x35, 0x64, 0x3a, 0x63, 0x38, 0x3a, 0x35, 0x65, 0x3a, 0x61, + 0x36, 0x3a, 0x66, 0x63, 0x3a, 0x64, 0x35, 0x3a, 0x34, 0x38, 0x3a, 0x30, + 0x62, 0x3a, 0x36, 0x30, 0x3a, 0x33, 0x32, 0x3a, 0x33, 0x39, 0x3a, 0x62, + 0x64, 0x3a, 0x63, 0x34, 0x3a, 0x35, 0x34, 0x3a, 0x66, 0x63, 0x3a, 0x37, + 0x35, 0x3a, 0x38, 0x62, 0x3a, 0x32, 0x61, 0x3a, 0x32, 0x36, 0x3a, 0x63, + 0x66, 0x3a, 0x37, 0x66, 0x3a, 0x37, 0x39, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, + 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, + 0x4d, 0x49, 0x49, 0x44, 0x68, 0x44, 0x43, 0x43, 0x41, 0x77, 0x71, 0x67, + 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x51, 0x4c, 0x34, 0x44, 0x2b, + 0x49, 0x34, 0x77, 0x4f, 0x49, 0x67, 0x39, 0x49, 0x5a, 0x78, 0x49, 0x6f, + 0x6b, 0x59, 0x65, 0x73, 0x73, 0x7a, 0x41, 0x4b, 0x42, 0x67, 0x67, 0x71, + 0x68, 0x6b, 0x6a, 0x4f, 0x50, 0x51, 0x51, 0x44, 0x41, 0x7a, 0x43, 0x42, + 0x79, 0x6a, 0x45, 0x4c, 0x0a, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x42, 0x68, 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, 0x46, 0x7a, 0x41, + 0x56, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, 0x44, 0x6c, 0x5a, + 0x6c, 0x63, 0x6d, 0x6c, 0x54, 0x61, 0x57, 0x64, 0x75, 0x4c, 0x43, 0x42, + 0x4a, 0x62, 0x6d, 0x4d, 0x75, 0x4d, 0x52, 0x38, 0x77, 0x48, 0x51, 0x59, + 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, 0x78, 0x5a, 0x57, 0x0a, 0x5a, 0x58, + 0x4a, 0x70, 0x55, 0x32, 0x6c, 0x6e, 0x62, 0x69, 0x42, 0x55, 0x63, 0x6e, + 0x56, 0x7a, 0x64, 0x43, 0x42, 0x4f, 0x5a, 0x58, 0x52, 0x33, 0x62, 0x33, + 0x4a, 0x72, 0x4d, 0x54, 0x6f, 0x77, 0x4f, 0x41, 0x59, 0x44, 0x56, 0x51, + 0x51, 0x4c, 0x45, 0x7a, 0x45, 0x6f, 0x59, 0x79, 0x6b, 0x67, 0x4d, 0x6a, + 0x41, 0x77, 0x4e, 0x79, 0x42, 0x57, 0x5a, 0x58, 0x4a, 0x70, 0x55, 0x32, + 0x6c, 0x6e, 0x0a, 0x62, 0x69, 0x77, 0x67, 0x53, 0x57, 0x35, 0x6a, 0x4c, + 0x69, 0x41, 0x74, 0x49, 0x45, 0x5a, 0x76, 0x63, 0x69, 0x42, 0x68, 0x64, + 0x58, 0x52, 0x6f, 0x62, 0x33, 0x4a, 0x70, 0x65, 0x6d, 0x56, 0x6b, 0x49, + 0x48, 0x56, 0x7a, 0x5a, 0x53, 0x42, 0x76, 0x62, 0x6d, 0x78, 0x35, 0x4d, + 0x55, 0x55, 0x77, 0x51, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, + 0x7a, 0x78, 0x57, 0x5a, 0x58, 0x4a, 0x70, 0x0a, 0x55, 0x32, 0x6c, 0x6e, + 0x62, 0x69, 0x42, 0x44, 0x62, 0x47, 0x46, 0x7a, 0x63, 0x79, 0x41, 0x7a, + 0x49, 0x46, 0x42, 0x31, 0x59, 0x6d, 0x78, 0x70, 0x59, 0x79, 0x42, 0x51, + 0x63, 0x6d, 0x6c, 0x74, 0x59, 0x58, 0x4a, 0x35, 0x49, 0x45, 0x4e, 0x6c, + 0x63, 0x6e, 0x52, 0x70, 0x5a, 0x6d, 0x6c, 0x6a, 0x59, 0x58, 0x52, 0x70, + 0x62, 0x32, 0x34, 0x67, 0x51, 0x58, 0x56, 0x30, 0x61, 0x47, 0x39, 0x79, + 0x0a, 0x61, 0x58, 0x52, 0x35, 0x49, 0x43, 0x30, 0x67, 0x52, 0x7a, 0x51, + 0x77, 0x48, 0x68, 0x63, 0x4e, 0x4d, 0x44, 0x63, 0x78, 0x4d, 0x54, 0x41, + 0x31, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x57, 0x68, 0x63, + 0x4e, 0x4d, 0x7a, 0x67, 0x77, 0x4d, 0x54, 0x45, 0x34, 0x4d, 0x6a, 0x4d, + 0x31, 0x4f, 0x54, 0x55, 0x35, 0x57, 0x6a, 0x43, 0x42, 0x79, 0x6a, 0x45, + 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x0a, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, + 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, 0x46, 0x7a, 0x41, 0x56, 0x42, 0x67, + 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, 0x44, 0x6c, 0x5a, 0x6c, 0x63, 0x6d, + 0x6c, 0x54, 0x61, 0x57, 0x64, 0x75, 0x4c, 0x43, 0x42, 0x4a, 0x62, 0x6d, + 0x4d, 0x75, 0x4d, 0x52, 0x38, 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, 0x51, + 0x51, 0x4c, 0x45, 0x78, 0x5a, 0x57, 0x5a, 0x58, 0x4a, 0x70, 0x0a, 0x55, + 0x32, 0x6c, 0x6e, 0x62, 0x69, 0x42, 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x64, + 0x43, 0x42, 0x4f, 0x5a, 0x58, 0x52, 0x33, 0x62, 0x33, 0x4a, 0x72, 0x4d, + 0x54, 0x6f, 0x77, 0x4f, 0x41, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, + 0x7a, 0x45, 0x6f, 0x59, 0x79, 0x6b, 0x67, 0x4d, 0x6a, 0x41, 0x77, 0x4e, + 0x79, 0x42, 0x57, 0x5a, 0x58, 0x4a, 0x70, 0x55, 0x32, 0x6c, 0x6e, 0x62, + 0x69, 0x77, 0x67, 0x0a, 0x53, 0x57, 0x35, 0x6a, 0x4c, 0x69, 0x41, 0x74, + 0x49, 0x45, 0x5a, 0x76, 0x63, 0x69, 0x42, 0x68, 0x64, 0x58, 0x52, 0x6f, + 0x62, 0x33, 0x4a, 0x70, 0x65, 0x6d, 0x56, 0x6b, 0x49, 0x48, 0x56, 0x7a, + 0x5a, 0x53, 0x42, 0x76, 0x62, 0x6d, 0x78, 0x35, 0x4d, 0x55, 0x55, 0x77, + 0x51, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, 0x7a, 0x78, 0x57, + 0x5a, 0x58, 0x4a, 0x70, 0x55, 0x32, 0x6c, 0x6e, 0x0a, 0x62, 0x69, 0x42, + 0x44, 0x62, 0x47, 0x46, 0x7a, 0x63, 0x79, 0x41, 0x7a, 0x49, 0x46, 0x42, + 0x31, 0x59, 0x6d, 0x78, 0x70, 0x59, 0x79, 0x42, 0x51, 0x63, 0x6d, 0x6c, + 0x74, 0x59, 0x58, 0x4a, 0x35, 0x49, 0x45, 0x4e, 0x6c, 0x63, 0x6e, 0x52, + 0x70, 0x5a, 0x6d, 0x6c, 0x6a, 0x59, 0x58, 0x52, 0x70, 0x62, 0x32, 0x34, + 0x67, 0x51, 0x58, 0x56, 0x30, 0x61, 0x47, 0x39, 0x79, 0x61, 0x58, 0x52, + 0x35, 0x0a, 0x49, 0x43, 0x30, 0x67, 0x52, 0x7a, 0x51, 0x77, 0x64, 0x6a, + 0x41, 0x51, 0x42, 0x67, 0x63, 0x71, 0x68, 0x6b, 0x6a, 0x4f, 0x50, 0x51, + 0x49, 0x42, 0x42, 0x67, 0x55, 0x72, 0x67, 0x51, 0x51, 0x41, 0x49, 0x67, + 0x4e, 0x69, 0x41, 0x41, 0x53, 0x6e, 0x56, 0x6e, 0x70, 0x38, 0x55, 0x74, + 0x70, 0x6b, 0x6d, 0x77, 0x34, 0x74, 0x58, 0x4e, 0x68, 0x65, 0x72, 0x4a, + 0x49, 0x39, 0x2f, 0x67, 0x48, 0x6d, 0x0a, 0x47, 0x55, 0x6f, 0x39, 0x46, + 0x41, 0x4e, 0x4c, 0x2b, 0x6d, 0x41, 0x6e, 0x49, 0x4e, 0x6d, 0x44, 0x69, + 0x57, 0x6e, 0x36, 0x56, 0x4d, 0x61, 0x61, 0x47, 0x46, 0x35, 0x56, 0x4b, + 0x6d, 0x54, 0x65, 0x42, 0x76, 0x61, 0x4e, 0x53, 0x6a, 0x75, 0x74, 0x45, + 0x44, 0x78, 0x6c, 0x50, 0x5a, 0x43, 0x49, 0x42, 0x49, 0x6e, 0x67, 0x4d, + 0x47, 0x47, 0x7a, 0x72, 0x6c, 0x30, 0x42, 0x70, 0x33, 0x76, 0x65, 0x0a, + 0x66, 0x4c, 0x4b, 0x2b, 0x79, 0x6d, 0x56, 0x68, 0x41, 0x49, 0x61, 0x75, + 0x32, 0x6f, 0x39, 0x37, 0x30, 0x49, 0x6d, 0x74, 0x54, 0x52, 0x31, 0x5a, + 0x6d, 0x6b, 0x47, 0x78, 0x76, 0x45, 0x65, 0x41, 0x33, 0x4a, 0x35, 0x69, + 0x77, 0x2f, 0x6d, 0x6a, 0x67, 0x62, 0x49, 0x77, 0x67, 0x61, 0x38, 0x77, + 0x44, 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, 0x54, 0x41, 0x51, 0x48, 0x2f, + 0x42, 0x41, 0x55, 0x77, 0x0a, 0x41, 0x77, 0x45, 0x42, 0x2f, 0x7a, 0x41, + 0x4f, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x38, 0x42, 0x41, 0x66, 0x38, + 0x45, 0x42, 0x41, 0x4d, 0x43, 0x41, 0x51, 0x59, 0x77, 0x62, 0x51, 0x59, + 0x49, 0x4b, 0x77, 0x59, 0x42, 0x42, 0x51, 0x55, 0x48, 0x41, 0x51, 0x77, + 0x45, 0x59, 0x54, 0x42, 0x66, 0x6f, 0x56, 0x32, 0x67, 0x57, 0x7a, 0x42, + 0x5a, 0x4d, 0x46, 0x63, 0x77, 0x56, 0x52, 0x59, 0x4a, 0x0a, 0x61, 0x57, + 0x31, 0x68, 0x5a, 0x32, 0x55, 0x76, 0x5a, 0x32, 0x6c, 0x6d, 0x4d, 0x43, + 0x45, 0x77, 0x48, 0x7a, 0x41, 0x48, 0x42, 0x67, 0x55, 0x72, 0x44, 0x67, + 0x4d, 0x43, 0x47, 0x67, 0x51, 0x55, 0x6a, 0x2b, 0x58, 0x54, 0x47, 0x6f, + 0x61, 0x73, 0x6a, 0x59, 0x35, 0x72, 0x77, 0x38, 0x2b, 0x41, 0x61, 0x74, + 0x52, 0x49, 0x47, 0x43, 0x78, 0x37, 0x47, 0x53, 0x34, 0x77, 0x4a, 0x52, + 0x59, 0x6a, 0x0a, 0x61, 0x48, 0x52, 0x30, 0x63, 0x44, 0x6f, 0x76, 0x4c, + 0x32, 0x78, 0x76, 0x5a, 0x32, 0x38, 0x75, 0x64, 0x6d, 0x56, 0x79, 0x61, + 0x58, 0x4e, 0x70, 0x5a, 0x32, 0x34, 0x75, 0x59, 0x32, 0x39, 0x74, 0x4c, + 0x33, 0x5a, 0x7a, 0x62, 0x47, 0x39, 0x6e, 0x62, 0x79, 0x35, 0x6e, 0x61, + 0x57, 0x59, 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, 0x52, 0x30, 0x4f, 0x42, + 0x42, 0x59, 0x45, 0x46, 0x4c, 0x4d, 0x57, 0x0a, 0x6b, 0x66, 0x33, 0x75, + 0x70, 0x6d, 0x37, 0x6b, 0x74, 0x53, 0x35, 0x4a, 0x6a, 0x34, 0x64, 0x34, + 0x67, 0x59, 0x44, 0x73, 0x35, 0x62, 0x47, 0x31, 0x4d, 0x41, 0x6f, 0x47, + 0x43, 0x43, 0x71, 0x47, 0x53, 0x4d, 0x34, 0x39, 0x42, 0x41, 0x4d, 0x44, + 0x41, 0x32, 0x67, 0x41, 0x4d, 0x47, 0x55, 0x43, 0x4d, 0x47, 0x59, 0x68, + 0x44, 0x42, 0x67, 0x6d, 0x59, 0x46, 0x6f, 0x34, 0x65, 0x31, 0x5a, 0x43, + 0x0a, 0x34, 0x4b, 0x66, 0x38, 0x4e, 0x6f, 0x52, 0x52, 0x6b, 0x53, 0x41, + 0x73, 0x64, 0x6b, 0x31, 0x44, 0x50, 0x63, 0x51, 0x64, 0x68, 0x43, 0x50, + 0x51, 0x72, 0x4e, 0x5a, 0x38, 0x4e, 0x51, 0x62, 0x4f, 0x7a, 0x57, 0x6d, + 0x39, 0x6b, 0x41, 0x33, 0x62, 0x62, 0x45, 0x68, 0x43, 0x48, 0x51, 0x36, + 0x71, 0x51, 0x67, 0x49, 0x78, 0x41, 0x4a, 0x77, 0x39, 0x53, 0x44, 0x6b, + 0x6a, 0x4f, 0x56, 0x67, 0x61, 0x0a, 0x46, 0x52, 0x4a, 0x5a, 0x61, 0x70, + 0x37, 0x76, 0x31, 0x56, 0x6d, 0x79, 0x48, 0x56, 0x49, 0x73, 0x6d, 0x58, + 0x48, 0x4e, 0x78, 0x79, 0x6e, 0x66, 0x47, 0x79, 0x70, 0x68, 0x65, 0x33, + 0x48, 0x52, 0x33, 0x76, 0x50, 0x41, 0x35, 0x51, 0x30, 0x36, 0x53, 0x71, + 0x6f, 0x74, 0x70, 0x39, 0x69, 0x47, 0x4b, 0x74, 0x30, 0x75, 0x45, 0x41, + 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, + 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, + 0x65, 0x72, 0x3a, 0x20, 0x4f, 0x3d, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, + 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, + 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x0a, 0x23, + 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x4f, 0x3d, + 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, + 0x63, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, + 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, + 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x74, 0x79, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, + 0x3a, 0x20, 0x22, 0x56, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x20, + 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x0a, + 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x38, 0x30, + 0x35, 0x30, 0x37, 0x35, 0x37, 0x32, 0x37, 0x32, 0x32, 0x38, 0x36, 0x32, + 0x34, 0x38, 0x35, 0x35, 0x31, 0x35, 0x33, 0x30, 0x36, 0x34, 0x32, 0x39, + 0x39, 0x34, 0x30, 0x36, 0x39, 0x31, 0x33, 0x30, 0x39, 0x32, 0x34, 0x36, + 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x65, 0x66, 0x3a, 0x35, + 0x61, 0x3a, 0x66, 0x31, 0x3a, 0x33, 0x33, 0x3a, 0x65, 0x66, 0x3a, 0x66, + 0x31, 0x3a, 0x63, 0x64, 0x3a, 0x62, 0x62, 0x3a, 0x35, 0x31, 0x3a, 0x30, + 0x32, 0x3a, 0x65, 0x65, 0x3a, 0x31, 0x32, 0x3a, 0x31, 0x34, 0x3a, 0x34, + 0x62, 0x3a, 0x39, 0x36, 0x3a, 0x63, 0x34, 0x0a, 0x23, 0x20, 0x53, 0x48, + 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x3a, 0x20, 0x61, 0x31, 0x3a, 0x64, 0x62, 0x3a, 0x36, 0x33, + 0x3a, 0x39, 0x33, 0x3a, 0x39, 0x31, 0x3a, 0x36, 0x66, 0x3a, 0x31, 0x37, + 0x3a, 0x65, 0x34, 0x3a, 0x31, 0x38, 0x3a, 0x35, 0x35, 0x3a, 0x30, 0x39, + 0x3a, 0x34, 0x30, 0x3a, 0x30, 0x34, 0x3a, 0x31, 0x35, 0x3a, 0x63, 0x37, + 0x3a, 0x30, 0x32, 0x3a, 0x34, 0x30, 0x3a, 0x62, 0x30, 0x3a, 0x61, 0x65, + 0x3a, 0x36, 0x62, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, + 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, + 0x3a, 0x20, 0x61, 0x34, 0x3a, 0x62, 0x36, 0x3a, 0x62, 0x33, 0x3a, 0x39, + 0x39, 0x3a, 0x36, 0x66, 0x3a, 0x63, 0x32, 0x3a, 0x66, 0x33, 0x3a, 0x30, + 0x36, 0x3a, 0x62, 0x33, 0x3a, 0x66, 0x64, 0x3a, 0x38, 0x36, 0x3a, 0x38, + 0x31, 0x3a, 0x62, 0x64, 0x3a, 0x36, 0x33, 0x3a, 0x34, 0x31, 0x3a, 0x33, + 0x64, 0x3a, 0x38, 0x63, 0x3a, 0x35, 0x30, 0x3a, 0x30, 0x39, 0x3a, 0x63, + 0x63, 0x3a, 0x34, 0x66, 0x3a, 0x61, 0x33, 0x3a, 0x32, 0x39, 0x3a, 0x63, + 0x32, 0x3a, 0x63, 0x63, 0x3a, 0x66, 0x30, 0x3a, 0x65, 0x32, 0x3a, 0x66, + 0x61, 0x3a, 0x31, 0x62, 0x3a, 0x31, 0x34, 0x3a, 0x30, 0x33, 0x3a, 0x30, + 0x35, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, + 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x43, 0x50, 0x44, + 0x43, 0x43, 0x41, 0x61, 0x55, 0x43, 0x45, 0x44, 0x79, 0x52, 0x4d, 0x63, + 0x73, 0x66, 0x39, 0x74, 0x41, 0x62, 0x44, 0x70, 0x71, 0x34, 0x30, 0x45, + 0x53, 0x2f, 0x45, 0x72, 0x34, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, + 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x46, 0x42, 0x51, + 0x41, 0x77, 0x58, 0x7a, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x0a, 0x41, + 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, 0x46, + 0x7a, 0x41, 0x56, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, 0x44, + 0x6c, 0x5a, 0x6c, 0x63, 0x6d, 0x6c, 0x54, 0x61, 0x57, 0x64, 0x75, 0x4c, + 0x43, 0x42, 0x4a, 0x62, 0x6d, 0x4d, 0x75, 0x4d, 0x54, 0x63, 0x77, 0x4e, + 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, 0x79, 0x35, 0x44, 0x62, + 0x47, 0x46, 0x7a, 0x0a, 0x63, 0x79, 0x41, 0x7a, 0x49, 0x46, 0x42, 0x31, + 0x59, 0x6d, 0x78, 0x70, 0x59, 0x79, 0x42, 0x51, 0x63, 0x6d, 0x6c, 0x74, + 0x59, 0x58, 0x4a, 0x35, 0x49, 0x45, 0x4e, 0x6c, 0x63, 0x6e, 0x52, 0x70, + 0x5a, 0x6d, 0x6c, 0x6a, 0x59, 0x58, 0x52, 0x70, 0x62, 0x32, 0x34, 0x67, + 0x51, 0x58, 0x56, 0x30, 0x61, 0x47, 0x39, 0x79, 0x61, 0x58, 0x52, 0x35, + 0x4d, 0x42, 0x34, 0x58, 0x44, 0x54, 0x6b, 0x32, 0x0a, 0x4d, 0x44, 0x45, + 0x79, 0x4f, 0x54, 0x41, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x46, 0x6f, + 0x58, 0x44, 0x54, 0x49, 0x34, 0x4d, 0x44, 0x67, 0x77, 0x4d, 0x6a, 0x49, + 0x7a, 0x4e, 0x54, 0x6b, 0x31, 0x4f, 0x56, 0x6f, 0x77, 0x58, 0x7a, 0x45, + 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, + 0x43, 0x56, 0x56, 0x4d, 0x78, 0x46, 0x7a, 0x41, 0x56, 0x42, 0x67, 0x4e, + 0x56, 0x0a, 0x42, 0x41, 0x6f, 0x54, 0x44, 0x6c, 0x5a, 0x6c, 0x63, 0x6d, + 0x6c, 0x54, 0x61, 0x57, 0x64, 0x75, 0x4c, 0x43, 0x42, 0x4a, 0x62, 0x6d, + 0x4d, 0x75, 0x4d, 0x54, 0x63, 0x77, 0x4e, 0x51, 0x59, 0x44, 0x56, 0x51, + 0x51, 0x4c, 0x45, 0x79, 0x35, 0x44, 0x62, 0x47, 0x46, 0x7a, 0x63, 0x79, + 0x41, 0x7a, 0x49, 0x46, 0x42, 0x31, 0x59, 0x6d, 0x78, 0x70, 0x59, 0x79, + 0x42, 0x51, 0x63, 0x6d, 0x6c, 0x74, 0x0a, 0x59, 0x58, 0x4a, 0x35, 0x49, + 0x45, 0x4e, 0x6c, 0x63, 0x6e, 0x52, 0x70, 0x5a, 0x6d, 0x6c, 0x6a, 0x59, + 0x58, 0x52, 0x70, 0x62, 0x32, 0x34, 0x67, 0x51, 0x58, 0x56, 0x30, 0x61, + 0x47, 0x39, 0x79, 0x61, 0x58, 0x52, 0x35, 0x4d, 0x49, 0x47, 0x66, 0x4d, + 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, + 0x51, 0x45, 0x42, 0x41, 0x51, 0x55, 0x41, 0x41, 0x34, 0x47, 0x4e, 0x0a, + 0x41, 0x44, 0x43, 0x42, 0x69, 0x51, 0x4b, 0x42, 0x67, 0x51, 0x44, 0x4a, + 0x58, 0x46, 0x6d, 0x65, 0x38, 0x68, 0x75, 0x4b, 0x41, 0x52, 0x53, 0x30, + 0x45, 0x4e, 0x38, 0x45, 0x51, 0x4e, 0x76, 0x6a, 0x56, 0x36, 0x39, 0x71, + 0x52, 0x55, 0x43, 0x50, 0x68, 0x41, 0x77, 0x4c, 0x30, 0x54, 0x50, 0x5a, + 0x32, 0x52, 0x48, 0x50, 0x37, 0x67, 0x4a, 0x59, 0x48, 0x79, 0x58, 0x33, + 0x4b, 0x71, 0x68, 0x45, 0x0a, 0x42, 0x61, 0x72, 0x73, 0x41, 0x78, 0x39, + 0x34, 0x66, 0x35, 0x36, 0x54, 0x75, 0x5a, 0x6f, 0x41, 0x71, 0x69, 0x4e, + 0x39, 0x31, 0x71, 0x79, 0x46, 0x6f, 0x6d, 0x4e, 0x46, 0x78, 0x33, 0x49, + 0x6e, 0x7a, 0x50, 0x52, 0x4d, 0x78, 0x6e, 0x56, 0x78, 0x30, 0x6a, 0x6e, + 0x76, 0x54, 0x30, 0x4c, 0x77, 0x64, 0x64, 0x38, 0x4b, 0x6b, 0x4d, 0x61, + 0x4f, 0x49, 0x47, 0x2b, 0x59, 0x44, 0x2f, 0x69, 0x73, 0x0a, 0x49, 0x31, + 0x39, 0x77, 0x4b, 0x54, 0x61, 0x6b, 0x79, 0x59, 0x62, 0x6e, 0x73, 0x5a, + 0x6f, 0x67, 0x79, 0x31, 0x4f, 0x6c, 0x68, 0x65, 0x63, 0x39, 0x76, 0x6e, + 0x32, 0x61, 0x2f, 0x69, 0x52, 0x46, 0x4d, 0x39, 0x78, 0x32, 0x46, 0x65, + 0x30, 0x50, 0x6f, 0x6e, 0x46, 0x6b, 0x54, 0x47, 0x55, 0x75, 0x67, 0x57, + 0x68, 0x46, 0x70, 0x77, 0x49, 0x44, 0x41, 0x51, 0x41, 0x42, 0x4d, 0x41, + 0x30, 0x47, 0x0a, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, + 0x51, 0x45, 0x42, 0x42, 0x51, 0x55, 0x41, 0x41, 0x34, 0x47, 0x42, 0x41, + 0x42, 0x42, 0x79, 0x55, 0x71, 0x6b, 0x46, 0x46, 0x42, 0x6b, 0x79, 0x43, + 0x45, 0x48, 0x77, 0x78, 0x57, 0x73, 0x4b, 0x7a, 0x48, 0x34, 0x50, 0x49, + 0x52, 0x6e, 0x4e, 0x35, 0x47, 0x66, 0x63, 0x58, 0x36, 0x6b, 0x62, 0x35, + 0x73, 0x72, 0x6f, 0x63, 0x35, 0x30, 0x69, 0x0a, 0x32, 0x4a, 0x68, 0x75, + 0x63, 0x77, 0x4e, 0x68, 0x6b, 0x63, 0x56, 0x38, 0x73, 0x45, 0x56, 0x41, + 0x62, 0x6b, 0x53, 0x64, 0x6a, 0x62, 0x43, 0x78, 0x6c, 0x6e, 0x52, 0x68, + 0x4c, 0x51, 0x32, 0x70, 0x52, 0x64, 0x4b, 0x6b, 0x6b, 0x69, 0x72, 0x57, + 0x6d, 0x6e, 0x57, 0x58, 0x62, 0x6a, 0x39, 0x54, 0x2f, 0x55, 0x57, 0x5a, + 0x59, 0x42, 0x32, 0x6f, 0x4b, 0x30, 0x7a, 0x35, 0x58, 0x71, 0x63, 0x4a, + 0x0a, 0x32, 0x48, 0x55, 0x77, 0x31, 0x39, 0x4a, 0x6c, 0x59, 0x44, 0x31, + 0x6e, 0x31, 0x6b, 0x68, 0x56, 0x64, 0x57, 0x6b, 0x2f, 0x6b, 0x66, 0x56, + 0x49, 0x43, 0x30, 0x64, 0x70, 0x49, 0x6d, 0x6d, 0x43, 0x6c, 0x72, 0x37, + 0x4a, 0x79, 0x44, 0x69, 0x47, 0x53, 0x6e, 0x6f, 0x73, 0x63, 0x78, 0x6c, + 0x49, 0x61, 0x55, 0x35, 0x72, 0x66, 0x47, 0x57, 0x2f, 0x44, 0x2f, 0x78, + 0x77, 0x7a, 0x6f, 0x69, 0x51, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, + 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, + 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, + 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x47, 0x6c, + 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x4f, 0x3d, 0x47, + 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x4f, 0x55, + 0x3d, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, + 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x52, 0x33, + 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, + 0x43, 0x4e, 0x3d, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, + 0x6e, 0x20, 0x4f, 0x3d, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, + 0x67, 0x6e, 0x20, 0x4f, 0x55, 0x3d, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, + 0x53, 0x69, 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, + 0x20, 0x2d, 0x20, 0x52, 0x33, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, + 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x2d, + 0x20, 0x52, 0x33, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, + 0x6c, 0x3a, 0x20, 0x34, 0x38, 0x33, 0x35, 0x37, 0x30, 0x33, 0x32, 0x37, + 0x38, 0x34, 0x35, 0x39, 0x37, 0x35, 0x39, 0x34, 0x32, 0x36, 0x32, 0x30, + 0x39, 0x39, 0x35, 0x34, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x63, 0x35, 0x3a, 0x64, 0x66, 0x3a, 0x62, 0x38, 0x3a, 0x34, 0x39, 0x3a, + 0x63, 0x61, 0x3a, 0x30, 0x35, 0x3a, 0x31, 0x33, 0x3a, 0x35, 0x35, 0x3a, + 0x65, 0x65, 0x3a, 0x32, 0x64, 0x3a, 0x62, 0x61, 0x3a, 0x31, 0x61, 0x3a, + 0x63, 0x33, 0x3a, 0x33, 0x65, 0x3a, 0x62, 0x30, 0x3a, 0x32, 0x38, 0x0a, + 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x64, 0x36, 0x3a, 0x39, + 0x62, 0x3a, 0x35, 0x36, 0x3a, 0x31, 0x31, 0x3a, 0x34, 0x38, 0x3a, 0x66, + 0x30, 0x3a, 0x31, 0x63, 0x3a, 0x37, 0x37, 0x3a, 0x63, 0x35, 0x3a, 0x34, + 0x35, 0x3a, 0x37, 0x38, 0x3a, 0x63, 0x31, 0x3a, 0x30, 0x39, 0x3a, 0x32, + 0x36, 0x3a, 0x64, 0x66, 0x3a, 0x35, 0x62, 0x3a, 0x38, 0x35, 0x3a, 0x36, + 0x39, 0x3a, 0x37, 0x36, 0x3a, 0x61, 0x64, 0x0a, 0x23, 0x20, 0x53, 0x48, + 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, + 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x63, 0x62, 0x3a, 0x62, 0x35, 0x3a, + 0x32, 0x32, 0x3a, 0x64, 0x37, 0x3a, 0x62, 0x37, 0x3a, 0x66, 0x31, 0x3a, + 0x32, 0x37, 0x3a, 0x61, 0x64, 0x3a, 0x36, 0x61, 0x3a, 0x30, 0x31, 0x3a, + 0x31, 0x33, 0x3a, 0x38, 0x36, 0x3a, 0x35, 0x62, 0x3a, 0x64, 0x66, 0x3a, + 0x31, 0x63, 0x3a, 0x64, 0x34, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x65, 0x3a, + 0x37, 0x64, 0x3a, 0x30, 0x37, 0x3a, 0x35, 0x39, 0x3a, 0x61, 0x66, 0x3a, + 0x36, 0x33, 0x3a, 0x35, 0x61, 0x3a, 0x37, 0x63, 0x3a, 0x66, 0x34, 0x3a, + 0x37, 0x32, 0x3a, 0x30, 0x64, 0x3a, 0x63, 0x39, 0x3a, 0x36, 0x33, 0x3a, + 0x63, 0x35, 0x3a, 0x33, 0x62, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, + 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, + 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, + 0x49, 0x44, 0x58, 0x7a, 0x43, 0x43, 0x41, 0x6b, 0x65, 0x67, 0x41, 0x77, + 0x49, 0x42, 0x41, 0x67, 0x49, 0x4c, 0x42, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x42, 0x49, 0x56, 0x68, 0x54, 0x43, 0x4b, 0x49, 0x77, 0x44, 0x51, + 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, + 0x45, 0x4c, 0x42, 0x51, 0x41, 0x77, 0x54, 0x44, 0x45, 0x67, 0x4d, 0x42, + 0x34, 0x47, 0x0a, 0x41, 0x31, 0x55, 0x45, 0x43, 0x78, 0x4d, 0x58, 0x52, + 0x32, 0x78, 0x76, 0x59, 0x6d, 0x46, 0x73, 0x55, 0x32, 0x6c, 0x6e, 0x62, + 0x69, 0x42, 0x53, 0x62, 0x32, 0x39, 0x30, 0x49, 0x45, 0x4e, 0x42, 0x49, + 0x43, 0x30, 0x67, 0x55, 0x6a, 0x4d, 0x78, 0x45, 0x7a, 0x41, 0x52, 0x42, + 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, 0x43, 0x6b, 0x64, 0x73, 0x62, + 0x32, 0x4a, 0x68, 0x62, 0x46, 0x4e, 0x70, 0x0a, 0x5a, 0x32, 0x34, 0x78, + 0x45, 0x7a, 0x41, 0x52, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x4d, 0x54, + 0x43, 0x6b, 0x64, 0x73, 0x62, 0x32, 0x4a, 0x68, 0x62, 0x46, 0x4e, 0x70, + 0x5a, 0x32, 0x34, 0x77, 0x48, 0x68, 0x63, 0x4e, 0x4d, 0x44, 0x6b, 0x77, + 0x4d, 0x7a, 0x45, 0x34, 0x4d, 0x54, 0x41, 0x77, 0x4d, 0x44, 0x41, 0x77, + 0x57, 0x68, 0x63, 0x4e, 0x4d, 0x6a, 0x6b, 0x77, 0x4d, 0x7a, 0x45, 0x34, + 0x0a, 0x4d, 0x54, 0x41, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x57, 0x6a, 0x42, + 0x4d, 0x4d, 0x53, 0x41, 0x77, 0x48, 0x67, 0x59, 0x44, 0x56, 0x51, 0x51, + 0x4c, 0x45, 0x78, 0x64, 0x48, 0x62, 0x47, 0x39, 0x69, 0x59, 0x57, 0x78, + 0x54, 0x61, 0x57, 0x64, 0x75, 0x49, 0x46, 0x4a, 0x76, 0x62, 0x33, 0x51, + 0x67, 0x51, 0x30, 0x45, 0x67, 0x4c, 0x53, 0x42, 0x53, 0x4d, 0x7a, 0x45, + 0x54, 0x4d, 0x42, 0x45, 0x47, 0x0a, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, + 0x4d, 0x4b, 0x52, 0x32, 0x78, 0x76, 0x59, 0x6d, 0x46, 0x73, 0x55, 0x32, + 0x6c, 0x6e, 0x62, 0x6a, 0x45, 0x54, 0x4d, 0x42, 0x45, 0x47, 0x41, 0x31, + 0x55, 0x45, 0x41, 0x78, 0x4d, 0x4b, 0x52, 0x32, 0x78, 0x76, 0x59, 0x6d, + 0x46, 0x73, 0x55, 0x32, 0x6c, 0x6e, 0x62, 0x6a, 0x43, 0x43, 0x41, 0x53, + 0x49, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x0a, 0x68, + 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x42, 0x42, 0x51, 0x41, 0x44, 0x67, + 0x67, 0x45, 0x50, 0x41, 0x44, 0x43, 0x43, 0x41, 0x51, 0x6f, 0x43, 0x67, + 0x67, 0x45, 0x42, 0x41, 0x4d, 0x77, 0x6c, 0x64, 0x70, 0x42, 0x35, 0x42, + 0x6e, 0x67, 0x69, 0x46, 0x76, 0x58, 0x41, 0x67, 0x37, 0x61, 0x45, 0x79, + 0x69, 0x69, 0x65, 0x2f, 0x51, 0x56, 0x32, 0x45, 0x63, 0x57, 0x74, 0x69, + 0x48, 0x4c, 0x38, 0x0a, 0x52, 0x67, 0x4a, 0x44, 0x78, 0x37, 0x4b, 0x4b, + 0x6e, 0x51, 0x52, 0x66, 0x4a, 0x4d, 0x73, 0x75, 0x53, 0x2b, 0x46, 0x67, + 0x67, 0x6b, 0x62, 0x68, 0x55, 0x71, 0x73, 0x4d, 0x67, 0x55, 0x64, 0x77, + 0x62, 0x4e, 0x31, 0x6b, 0x30, 0x65, 0x76, 0x31, 0x4c, 0x4b, 0x4d, 0x50, + 0x67, 0x6a, 0x30, 0x4d, 0x4b, 0x36, 0x36, 0x58, 0x31, 0x37, 0x59, 0x55, + 0x68, 0x68, 0x42, 0x35, 0x75, 0x7a, 0x73, 0x54, 0x0a, 0x67, 0x48, 0x65, + 0x4d, 0x43, 0x4f, 0x46, 0x4a, 0x30, 0x6d, 0x70, 0x69, 0x4c, 0x78, 0x39, + 0x65, 0x2b, 0x70, 0x5a, 0x6f, 0x33, 0x34, 0x6b, 0x6e, 0x6c, 0x54, 0x69, + 0x66, 0x42, 0x74, 0x63, 0x2b, 0x79, 0x63, 0x73, 0x6d, 0x57, 0x51, 0x31, + 0x7a, 0x33, 0x72, 0x44, 0x49, 0x36, 0x53, 0x59, 0x4f, 0x67, 0x78, 0x58, + 0x47, 0x37, 0x31, 0x75, 0x4c, 0x30, 0x67, 0x52, 0x67, 0x79, 0x6b, 0x6d, + 0x6d, 0x0a, 0x4b, 0x50, 0x5a, 0x70, 0x4f, 0x2f, 0x62, 0x4c, 0x79, 0x43, + 0x69, 0x52, 0x35, 0x5a, 0x32, 0x4b, 0x59, 0x56, 0x63, 0x33, 0x72, 0x48, + 0x51, 0x55, 0x33, 0x48, 0x54, 0x67, 0x4f, 0x75, 0x35, 0x79, 0x4c, 0x79, + 0x36, 0x63, 0x2b, 0x39, 0x43, 0x37, 0x76, 0x2f, 0x55, 0x39, 0x41, 0x4f, + 0x45, 0x47, 0x4d, 0x2b, 0x69, 0x43, 0x4b, 0x36, 0x35, 0x54, 0x70, 0x6a, + 0x6f, 0x57, 0x63, 0x34, 0x7a, 0x64, 0x0a, 0x51, 0x51, 0x34, 0x67, 0x4f, + 0x73, 0x43, 0x30, 0x70, 0x36, 0x48, 0x70, 0x73, 0x6b, 0x2b, 0x51, 0x4c, + 0x6a, 0x4a, 0x67, 0x36, 0x56, 0x66, 0x4c, 0x75, 0x51, 0x53, 0x53, 0x61, + 0x47, 0x6a, 0x6c, 0x4f, 0x43, 0x5a, 0x67, 0x64, 0x62, 0x4b, 0x66, 0x64, + 0x2f, 0x2b, 0x52, 0x46, 0x4f, 0x2b, 0x75, 0x49, 0x45, 0x6e, 0x38, 0x72, + 0x55, 0x41, 0x56, 0x53, 0x4e, 0x45, 0x43, 0x4d, 0x57, 0x45, 0x5a, 0x0a, + 0x58, 0x72, 0x69, 0x58, 0x37, 0x36, 0x31, 0x33, 0x74, 0x32, 0x53, 0x61, + 0x65, 0x72, 0x39, 0x66, 0x77, 0x52, 0x50, 0x76, 0x6d, 0x32, 0x4c, 0x37, + 0x44, 0x57, 0x7a, 0x67, 0x56, 0x47, 0x6b, 0x57, 0x71, 0x51, 0x50, 0x61, + 0x62, 0x75, 0x6d, 0x44, 0x6b, 0x33, 0x46, 0x32, 0x78, 0x6d, 0x6d, 0x46, + 0x67, 0x68, 0x63, 0x43, 0x41, 0x77, 0x45, 0x41, 0x41, 0x61, 0x4e, 0x43, + 0x4d, 0x45, 0x41, 0x77, 0x0a, 0x44, 0x67, 0x59, 0x44, 0x56, 0x52, 0x30, + 0x50, 0x41, 0x51, 0x48, 0x2f, 0x42, 0x41, 0x51, 0x44, 0x41, 0x67, 0x45, + 0x47, 0x4d, 0x41, 0x38, 0x47, 0x41, 0x31, 0x55, 0x64, 0x45, 0x77, 0x45, + 0x42, 0x2f, 0x77, 0x51, 0x46, 0x4d, 0x41, 0x4d, 0x42, 0x41, 0x66, 0x38, + 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, 0x52, 0x30, 0x4f, 0x42, 0x42, 0x59, + 0x45, 0x46, 0x49, 0x2f, 0x77, 0x53, 0x33, 0x2b, 0x6f, 0x0a, 0x4c, 0x6b, + 0x55, 0x6b, 0x72, 0x6b, 0x31, 0x51, 0x2b, 0x6d, 0x4f, 0x61, 0x69, 0x39, + 0x37, 0x69, 0x33, 0x52, 0x75, 0x38, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, + 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x43, 0x77, + 0x55, 0x41, 0x41, 0x34, 0x49, 0x42, 0x41, 0x51, 0x42, 0x4c, 0x51, 0x4e, + 0x76, 0x41, 0x55, 0x4b, 0x72, 0x2b, 0x79, 0x41, 0x7a, 0x76, 0x39, 0x35, + 0x5a, 0x55, 0x0a, 0x52, 0x55, 0x6d, 0x37, 0x6c, 0x67, 0x41, 0x4a, 0x51, + 0x61, 0x79, 0x7a, 0x45, 0x34, 0x61, 0x47, 0x4b, 0x41, 0x63, 0x7a, 0x79, + 0x6d, 0x76, 0x6d, 0x64, 0x4c, 0x6d, 0x36, 0x41, 0x43, 0x32, 0x75, 0x70, + 0x41, 0x72, 0x54, 0x39, 0x66, 0x48, 0x78, 0x44, 0x34, 0x71, 0x2f, 0x63, + 0x32, 0x64, 0x4b, 0x67, 0x38, 0x64, 0x45, 0x65, 0x33, 0x6a, 0x67, 0x72, + 0x32, 0x35, 0x73, 0x62, 0x77, 0x4d, 0x70, 0x0a, 0x6a, 0x6a, 0x4d, 0x35, + 0x52, 0x63, 0x4f, 0x4f, 0x35, 0x4c, 0x6c, 0x58, 0x62, 0x4b, 0x72, 0x38, + 0x45, 0x70, 0x62, 0x73, 0x55, 0x38, 0x59, 0x74, 0x35, 0x43, 0x52, 0x73, + 0x75, 0x5a, 0x52, 0x6a, 0x2b, 0x39, 0x78, 0x54, 0x61, 0x47, 0x64, 0x57, + 0x50, 0x6f, 0x4f, 0x34, 0x7a, 0x7a, 0x55, 0x68, 0x77, 0x38, 0x6c, 0x6f, + 0x2f, 0x73, 0x37, 0x61, 0x77, 0x6c, 0x4f, 0x71, 0x7a, 0x4a, 0x43, 0x4b, + 0x0a, 0x36, 0x66, 0x42, 0x64, 0x52, 0x6f, 0x79, 0x56, 0x33, 0x58, 0x70, + 0x59, 0x4b, 0x42, 0x6f, 0x76, 0x48, 0x64, 0x37, 0x4e, 0x41, 0x44, 0x64, + 0x42, 0x6a, 0x2b, 0x31, 0x45, 0x62, 0x64, 0x64, 0x54, 0x4b, 0x4a, 0x64, + 0x2b, 0x38, 0x32, 0x63, 0x45, 0x48, 0x68, 0x58, 0x58, 0x69, 0x70, 0x61, + 0x30, 0x30, 0x39, 0x35, 0x4d, 0x4a, 0x36, 0x52, 0x4d, 0x47, 0x33, 0x4e, + 0x7a, 0x64, 0x76, 0x51, 0x58, 0x0a, 0x6d, 0x63, 0x49, 0x66, 0x65, 0x67, + 0x37, 0x6a, 0x4c, 0x51, 0x69, 0x74, 0x43, 0x68, 0x77, 0x73, 0x2f, 0x7a, + 0x79, 0x72, 0x56, 0x51, 0x34, 0x50, 0x6b, 0x58, 0x34, 0x32, 0x36, 0x38, + 0x4e, 0x58, 0x53, 0x62, 0x37, 0x68, 0x4c, 0x69, 0x31, 0x38, 0x59, 0x49, + 0x76, 0x44, 0x51, 0x56, 0x45, 0x54, 0x49, 0x35, 0x33, 0x4f, 0x39, 0x7a, + 0x4a, 0x72, 0x6c, 0x41, 0x47, 0x6f, 0x6d, 0x65, 0x63, 0x73, 0x0a, 0x4d, + 0x78, 0x38, 0x36, 0x4f, 0x79, 0x58, 0x53, 0x68, 0x6b, 0x44, 0x4f, 0x4f, + 0x79, 0x79, 0x47, 0x65, 0x4d, 0x6c, 0x68, 0x4c, 0x78, 0x53, 0x36, 0x37, + 0x74, 0x74, 0x56, 0x62, 0x39, 0x2b, 0x45, 0x37, 0x67, 0x55, 0x4a, 0x54, + 0x62, 0x30, 0x6f, 0x32, 0x48, 0x4c, 0x4f, 0x30, 0x32, 0x4a, 0x51, 0x5a, + 0x52, 0x37, 0x72, 0x6b, 0x70, 0x65, 0x44, 0x4d, 0x64, 0x6d, 0x7a, 0x74, + 0x63, 0x70, 0x48, 0x0a, 0x57, 0x44, 0x39, 0x66, 0x0a, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, + 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, + 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, + 0x3d, 0x54, 0x43, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, + 0x74, 0x65, 0x72, 0x20, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, + 0x6c, 0x20, 0x43, 0x41, 0x20, 0x49, 0x49, 0x49, 0x20, 0x4f, 0x3d, 0x54, + 0x43, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, + 0x72, 0x20, 0x47, 0x6d, 0x62, 0x48, 0x20, 0x4f, 0x55, 0x3d, 0x54, 0x43, + 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, + 0x20, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, 0x20, 0x43, + 0x41, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, + 0x20, 0x43, 0x4e, 0x3d, 0x54, 0x43, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, + 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x55, 0x6e, 0x69, 0x76, 0x65, + 0x72, 0x73, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x49, 0x49, 0x49, 0x20, + 0x4f, 0x3d, 0x54, 0x43, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x43, 0x65, + 0x6e, 0x74, 0x65, 0x72, 0x20, 0x47, 0x6d, 0x62, 0x48, 0x20, 0x4f, 0x55, + 0x3d, 0x54, 0x43, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x43, 0x65, 0x6e, + 0x74, 0x65, 0x72, 0x20, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, + 0x6c, 0x20, 0x43, 0x41, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, + 0x3a, 0x20, 0x22, 0x54, 0x43, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x43, + 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, + 0x73, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x49, 0x49, 0x49, 0x22, 0x0a, + 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x32, 0x30, + 0x31, 0x30, 0x38, 0x38, 0x39, 0x39, 0x39, 0x33, 0x39, 0x38, 0x33, 0x35, + 0x30, 0x37, 0x33, 0x34, 0x36, 0x34, 0x36, 0x30, 0x35, 0x33, 0x33, 0x34, + 0x30, 0x37, 0x39, 0x30, 0x32, 0x39, 0x36, 0x34, 0x0a, 0x23, 0x20, 0x4d, + 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x3a, 0x20, 0x39, 0x66, 0x3a, 0x64, 0x64, 0x3a, 0x64, 0x62, + 0x3a, 0x61, 0x62, 0x3a, 0x66, 0x66, 0x3a, 0x38, 0x65, 0x3a, 0x66, 0x66, + 0x3a, 0x34, 0x35, 0x3a, 0x32, 0x31, 0x3a, 0x35, 0x66, 0x3a, 0x66, 0x30, + 0x3a, 0x36, 0x63, 0x3a, 0x39, 0x64, 0x3a, 0x38, 0x66, 0x3a, 0x66, 0x65, + 0x3a, 0x32, 0x62, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x39, 0x36, 0x3a, 0x35, 0x36, 0x3a, 0x63, 0x64, 0x3a, 0x37, 0x62, 0x3a, + 0x35, 0x37, 0x3a, 0x39, 0x36, 0x3a, 0x39, 0x38, 0x3a, 0x39, 0x35, 0x3a, + 0x64, 0x30, 0x3a, 0x65, 0x31, 0x3a, 0x34, 0x31, 0x3a, 0x34, 0x36, 0x3a, + 0x36, 0x38, 0x3a, 0x30, 0x36, 0x3a, 0x66, 0x62, 0x3a, 0x62, 0x38, 0x3a, + 0x63, 0x36, 0x3a, 0x31, 0x31, 0x3a, 0x30, 0x36, 0x3a, 0x38, 0x37, 0x0a, + 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, + 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x33, 0x30, + 0x3a, 0x39, 0x62, 0x3a, 0x34, 0x61, 0x3a, 0x38, 0x37, 0x3a, 0x66, 0x36, + 0x3a, 0x63, 0x61, 0x3a, 0x35, 0x36, 0x3a, 0x63, 0x39, 0x3a, 0x33, 0x31, + 0x3a, 0x36, 0x39, 0x3a, 0x61, 0x61, 0x3a, 0x61, 0x39, 0x3a, 0x39, 0x63, + 0x3a, 0x36, 0x64, 0x3a, 0x39, 0x38, 0x3a, 0x38, 0x38, 0x3a, 0x35, 0x34, + 0x3a, 0x64, 0x37, 0x3a, 0x38, 0x39, 0x3a, 0x32, 0x62, 0x3a, 0x64, 0x35, + 0x3a, 0x34, 0x33, 0x3a, 0x37, 0x65, 0x3a, 0x32, 0x64, 0x3a, 0x30, 0x37, + 0x3a, 0x62, 0x32, 0x3a, 0x39, 0x63, 0x3a, 0x62, 0x65, 0x3a, 0x64, 0x61, + 0x3a, 0x35, 0x35, 0x3a, 0x64, 0x33, 0x3a, 0x35, 0x64, 0x0a, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, + 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x44, 0x34, 0x54, 0x43, 0x43, 0x41, 0x73, + 0x6d, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x4f, 0x59, 0x79, + 0x55, 0x41, 0x41, 0x51, 0x41, 0x43, 0x46, 0x49, 0x30, 0x7a, 0x46, 0x51, + 0x4c, 0x6b, 0x62, 0x50, 0x51, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, + 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x46, 0x42, 0x51, + 0x41, 0x77, 0x65, 0x7a, 0x45, 0x4c, 0x0a, 0x4d, 0x41, 0x6b, 0x47, 0x41, + 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x52, 0x45, 0x55, 0x78, 0x48, + 0x44, 0x41, 0x61, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, 0x45, + 0x31, 0x52, 0x44, 0x49, 0x46, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x51, + 0x32, 0x56, 0x75, 0x64, 0x47, 0x56, 0x79, 0x49, 0x45, 0x64, 0x74, 0x59, + 0x6b, 0x67, 0x78, 0x4a, 0x44, 0x41, 0x69, 0x42, 0x67, 0x4e, 0x56, 0x0a, + 0x42, 0x41, 0x73, 0x54, 0x47, 0x31, 0x52, 0x44, 0x49, 0x46, 0x52, 0x79, + 0x64, 0x58, 0x4e, 0x30, 0x51, 0x32, 0x56, 0x75, 0x64, 0x47, 0x56, 0x79, + 0x49, 0x46, 0x56, 0x75, 0x61, 0x58, 0x5a, 0x6c, 0x63, 0x6e, 0x4e, 0x68, + 0x62, 0x43, 0x42, 0x44, 0x51, 0x54, 0x45, 0x6f, 0x4d, 0x43, 0x59, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x66, 0x56, 0x45, 0x4d, 0x67, + 0x56, 0x48, 0x4a, 0x31, 0x0a, 0x63, 0x33, 0x52, 0x44, 0x5a, 0x57, 0x35, + 0x30, 0x5a, 0x58, 0x49, 0x67, 0x56, 0x57, 0x35, 0x70, 0x64, 0x6d, 0x56, + 0x79, 0x63, 0x32, 0x46, 0x73, 0x49, 0x45, 0x4e, 0x42, 0x49, 0x45, 0x6c, + 0x4a, 0x53, 0x54, 0x41, 0x65, 0x46, 0x77, 0x30, 0x77, 0x4f, 0x54, 0x41, + 0x35, 0x4d, 0x44, 0x6b, 0x77, 0x4f, 0x44, 0x45, 0x31, 0x4d, 0x6a, 0x64, + 0x61, 0x46, 0x77, 0x30, 0x79, 0x4f, 0x54, 0x45, 0x79, 0x0a, 0x4d, 0x7a, + 0x45, 0x79, 0x4d, 0x7a, 0x55, 0x35, 0x4e, 0x54, 0x6c, 0x61, 0x4d, 0x48, + 0x73, 0x78, 0x43, 0x7a, 0x41, 0x4a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, + 0x59, 0x54, 0x41, 0x6b, 0x52, 0x46, 0x4d, 0x52, 0x77, 0x77, 0x47, 0x67, + 0x59, 0x44, 0x56, 0x51, 0x51, 0x4b, 0x45, 0x78, 0x4e, 0x55, 0x51, 0x79, + 0x42, 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x45, 0x4e, 0x6c, 0x62, 0x6e, + 0x52, 0x6c, 0x0a, 0x63, 0x69, 0x42, 0x48, 0x62, 0x57, 0x4a, 0x49, 0x4d, + 0x53, 0x51, 0x77, 0x49, 0x67, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, + 0x78, 0x74, 0x55, 0x51, 0x79, 0x42, 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x64, + 0x45, 0x4e, 0x6c, 0x62, 0x6e, 0x52, 0x6c, 0x63, 0x69, 0x42, 0x56, 0x62, + 0x6d, 0x6c, 0x32, 0x5a, 0x58, 0x4a, 0x7a, 0x59, 0x57, 0x77, 0x67, 0x51, + 0x30, 0x45, 0x78, 0x4b, 0x44, 0x41, 0x6d, 0x0a, 0x42, 0x67, 0x4e, 0x56, + 0x42, 0x41, 0x4d, 0x54, 0x48, 0x31, 0x52, 0x44, 0x49, 0x46, 0x52, 0x79, + 0x64, 0x58, 0x4e, 0x30, 0x51, 0x32, 0x56, 0x75, 0x64, 0x47, 0x56, 0x79, + 0x49, 0x46, 0x56, 0x75, 0x61, 0x58, 0x5a, 0x6c, 0x63, 0x6e, 0x4e, 0x68, + 0x62, 0x43, 0x42, 0x44, 0x51, 0x53, 0x42, 0x4a, 0x53, 0x55, 0x6b, 0x77, + 0x67, 0x67, 0x45, 0x69, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, + 0x0a, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x41, 0x51, 0x55, + 0x41, 0x41, 0x34, 0x49, 0x42, 0x44, 0x77, 0x41, 0x77, 0x67, 0x67, 0x45, + 0x4b, 0x41, 0x6f, 0x49, 0x42, 0x41, 0x51, 0x44, 0x43, 0x32, 0x70, 0x78, + 0x69, 0x73, 0x4c, 0x6c, 0x78, 0x45, 0x72, 0x41, 0x4c, 0x79, 0x42, 0x70, + 0x58, 0x73, 0x71, 0x36, 0x44, 0x46, 0x4a, 0x6d, 0x7a, 0x4e, 0x45, 0x75, + 0x62, 0x6b, 0x4b, 0x4c, 0x46, 0x0a, 0x35, 0x2b, 0x63, 0x76, 0x41, 0x71, + 0x42, 0x4e, 0x4c, 0x61, 0x54, 0x36, 0x68, 0x64, 0x71, 0x62, 0x4a, 0x59, + 0x55, 0x74, 0x51, 0x43, 0x67, 0x67, 0x62, 0x65, 0x72, 0x67, 0x76, 0x62, + 0x46, 0x49, 0x67, 0x79, 0x49, 0x70, 0x52, 0x4a, 0x39, 0x4f, 0x67, 0x2b, + 0x34, 0x31, 0x55, 0x52, 0x4e, 0x7a, 0x64, 0x4e, 0x57, 0x38, 0x38, 0x6a, + 0x42, 0x6d, 0x6c, 0x46, 0x50, 0x41, 0x51, 0x44, 0x59, 0x76, 0x0a, 0x44, + 0x49, 0x52, 0x6c, 0x7a, 0x67, 0x39, 0x75, 0x77, 0x6c, 0x69, 0x54, 0x36, + 0x43, 0x77, 0x4c, 0x4f, 0x75, 0x6e, 0x42, 0x6a, 0x76, 0x76, 0x79, 0x61, + 0x38, 0x6f, 0x38, 0x34, 0x70, 0x78, 0x4f, 0x6a, 0x75, 0x54, 0x35, 0x66, + 0x64, 0x4d, 0x6e, 0x6e, 0x78, 0x76, 0x56, 0x5a, 0x33, 0x69, 0x48, 0x4c, + 0x58, 0x38, 0x4c, 0x52, 0x37, 0x50, 0x48, 0x36, 0x4d, 0x6c, 0x49, 0x66, + 0x4b, 0x38, 0x76, 0x0a, 0x7a, 0x41, 0x72, 0x5a, 0x51, 0x65, 0x2b, 0x66, + 0x2f, 0x70, 0x72, 0x68, 0x73, 0x71, 0x37, 0x35, 0x55, 0x37, 0x58, 0x6c, + 0x36, 0x55, 0x61, 0x66, 0x59, 0x4f, 0x50, 0x66, 0x6a, 0x64, 0x4e, 0x2f, + 0x2b, 0x35, 0x5a, 0x2b, 0x73, 0x37, 0x56, 0x79, 0x2b, 0x45, 0x75, 0x74, + 0x43, 0x48, 0x6e, 0x4e, 0x61, 0x59, 0x6c, 0x41, 0x4a, 0x2f, 0x55, 0x71, + 0x77, 0x61, 0x31, 0x44, 0x37, 0x4b, 0x52, 0x54, 0x0a, 0x79, 0x47, 0x47, + 0x32, 0x39, 0x39, 0x4a, 0x35, 0x4b, 0x6d, 0x63, 0x59, 0x64, 0x6b, 0x68, + 0x74, 0x57, 0x79, 0x55, 0x42, 0x30, 0x53, 0x62, 0x46, 0x74, 0x31, 0x64, + 0x70, 0x49, 0x78, 0x56, 0x62, 0x59, 0x59, 0x71, 0x74, 0x38, 0x42, 0x73, + 0x74, 0x32, 0x61, 0x39, 0x63, 0x38, 0x53, 0x61, 0x51, 0x61, 0x61, 0x6e, + 0x56, 0x44, 0x45, 0x44, 0x31, 0x4d, 0x34, 0x42, 0x44, 0x6a, 0x35, 0x79, + 0x6a, 0x0a, 0x64, 0x69, 0x70, 0x46, 0x74, 0x4b, 0x2b, 0x2f, 0x66, 0x7a, + 0x36, 0x48, 0x50, 0x33, 0x62, 0x46, 0x7a, 0x53, 0x72, 0x65, 0x49, 0x4d, + 0x55, 0x57, 0x57, 0x4d, 0x76, 0x35, 0x47, 0x2f, 0x55, 0x50, 0x79, 0x77, + 0x30, 0x52, 0x55, 0x6d, 0x53, 0x34, 0x30, 0x6e, 0x5a, 0x69, 0x64, 0x34, + 0x50, 0x78, 0x57, 0x4a, 0x2f, 0x2f, 0x41, 0x67, 0x4d, 0x42, 0x41, 0x41, + 0x47, 0x6a, 0x59, 0x7a, 0x42, 0x68, 0x0a, 0x4d, 0x42, 0x38, 0x47, 0x41, + 0x31, 0x55, 0x64, 0x49, 0x77, 0x51, 0x59, 0x4d, 0x42, 0x61, 0x41, 0x46, + 0x46, 0x62, 0x6e, 0x34, 0x56, 0x73, 0x6c, 0x51, 0x34, 0x44, 0x67, 0x39, + 0x6f, 0x7a, 0x68, 0x63, 0x62, 0x79, 0x4f, 0x35, 0x59, 0x41, 0x76, 0x78, + 0x45, 0x6a, 0x69, 0x4d, 0x41, 0x38, 0x47, 0x41, 0x31, 0x55, 0x64, 0x45, + 0x77, 0x45, 0x42, 0x2f, 0x77, 0x51, 0x46, 0x4d, 0x41, 0x4d, 0x42, 0x0a, + 0x41, 0x66, 0x38, 0x77, 0x44, 0x67, 0x59, 0x44, 0x56, 0x52, 0x30, 0x50, + 0x41, 0x51, 0x48, 0x2f, 0x42, 0x41, 0x51, 0x44, 0x41, 0x67, 0x45, 0x47, + 0x4d, 0x42, 0x30, 0x47, 0x41, 0x31, 0x55, 0x64, 0x44, 0x67, 0x51, 0x57, + 0x42, 0x42, 0x52, 0x57, 0x35, 0x2b, 0x46, 0x62, 0x4a, 0x55, 0x4f, 0x41, + 0x34, 0x50, 0x61, 0x4d, 0x34, 0x58, 0x47, 0x38, 0x6a, 0x75, 0x57, 0x41, + 0x4c, 0x38, 0x52, 0x49, 0x0a, 0x34, 0x6a, 0x41, 0x4e, 0x42, 0x67, 0x6b, + 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, + 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x51, 0x45, 0x41, 0x67, 0x38, 0x65, + 0x76, 0x36, 0x6e, 0x39, 0x4e, 0x43, 0x6a, 0x77, 0x35, 0x73, 0x57, 0x69, + 0x2b, 0x65, 0x32, 0x32, 0x4a, 0x4c, 0x75, 0x6d, 0x7a, 0x43, 0x65, 0x63, + 0x59, 0x56, 0x34, 0x32, 0x46, 0x6d, 0x68, 0x66, 0x7a, 0x0a, 0x64, 0x6b, + 0x4a, 0x51, 0x45, 0x77, 0x2f, 0x48, 0x6b, 0x47, 0x38, 0x7a, 0x72, 0x63, + 0x56, 0x4a, 0x59, 0x43, 0x74, 0x73, 0x53, 0x56, 0x67, 0x5a, 0x31, 0x4f, + 0x4b, 0x2b, 0x74, 0x37, 0x2b, 0x72, 0x53, 0x62, 0x79, 0x55, 0x79, 0x4b, + 0x75, 0x2b, 0x4b, 0x47, 0x77, 0x57, 0x61, 0x4f, 0x44, 0x49, 0x6c, 0x30, + 0x59, 0x67, 0x6f, 0x47, 0x68, 0x6e, 0x59, 0x49, 0x67, 0x35, 0x49, 0x46, + 0x48, 0x59, 0x0a, 0x61, 0x41, 0x45, 0x52, 0x7a, 0x71, 0x66, 0x32, 0x45, + 0x51, 0x66, 0x32, 0x37, 0x4f, 0x79, 0x73, 0x47, 0x68, 0x2b, 0x79, 0x5a, + 0x6d, 0x35, 0x57, 0x5a, 0x32, 0x42, 0x36, 0x64, 0x46, 0x37, 0x41, 0x62, + 0x5a, 0x63, 0x32, 0x72, 0x72, 0x55, 0x4e, 0x58, 0x57, 0x5a, 0x7a, 0x77, + 0x43, 0x55, 0x79, 0x52, 0x64, 0x68, 0x4b, 0x42, 0x67, 0x65, 0x50, 0x78, + 0x4c, 0x63, 0x48, 0x73, 0x55, 0x30, 0x47, 0x0a, 0x44, 0x65, 0x47, 0x6c, + 0x36, 0x2f, 0x52, 0x31, 0x79, 0x72, 0x71, 0x63, 0x30, 0x4c, 0x32, 0x7a, + 0x30, 0x7a, 0x49, 0x6b, 0x54, 0x4f, 0x35, 0x2b, 0x34, 0x6e, 0x59, 0x45, + 0x53, 0x30, 0x6c, 0x54, 0x32, 0x50, 0x4c, 0x70, 0x56, 0x44, 0x50, 0x38, + 0x35, 0x58, 0x45, 0x66, 0x50, 0x52, 0x52, 0x63, 0x6c, 0x6b, 0x76, 0x78, + 0x4f, 0x76, 0x49, 0x41, 0x75, 0x32, 0x79, 0x30, 0x2b, 0x70, 0x5a, 0x56, + 0x0a, 0x43, 0x49, 0x67, 0x4a, 0x77, 0x63, 0x79, 0x52, 0x47, 0x53, 0x6d, + 0x77, 0x49, 0x43, 0x33, 0x2f, 0x79, 0x7a, 0x69, 0x6b, 0x51, 0x4f, 0x45, + 0x58, 0x76, 0x6e, 0x6c, 0x68, 0x67, 0x50, 0x38, 0x48, 0x41, 0x34, 0x5a, + 0x4d, 0x54, 0x6e, 0x73, 0x47, 0x6e, 0x78, 0x47, 0x47, 0x6a, 0x59, 0x6e, + 0x75, 0x4a, 0x38, 0x54, 0x62, 0x34, 0x72, 0x77, 0x5a, 0x6a, 0x67, 0x76, + 0x44, 0x77, 0x78, 0x50, 0x48, 0x0a, 0x4c, 0x51, 0x4e, 0x6a, 0x4f, 0x39, + 0x50, 0x6f, 0x35, 0x4b, 0x49, 0x71, 0x77, 0x6f, 0x49, 0x49, 0x6c, 0x42, + 0x5a, 0x55, 0x38, 0x4f, 0x38, 0x66, 0x4a, 0x35, 0x41, 0x6c, 0x75, 0x41, + 0x30, 0x4f, 0x4b, 0x42, 0x74, 0x48, 0x64, 0x30, 0x65, 0x39, 0x48, 0x4b, + 0x67, 0x6c, 0x38, 0x5a, 0x53, 0x30, 0x5a, 0x67, 0x3d, 0x3d, 0x0a, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, + 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, + 0x43, 0x4e, 0x3d, 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, 0x79, 0x20, + 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x20, 0x4f, 0x3d, 0x47, 0x6f, + 0x44, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x20, 0x49, + 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, + 0x64, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x20, 0x4f, + 0x3d, 0x47, 0x6f, 0x44, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, + 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, + 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, + 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x22, 0x0a, 0x23, + 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x30, 0x0a, 0x23, + 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, + 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x38, 0x30, 0x3a, 0x33, 0x61, 0x3a, + 0x62, 0x63, 0x3a, 0x32, 0x32, 0x3a, 0x63, 0x31, 0x3a, 0x65, 0x36, 0x3a, + 0x66, 0x62, 0x3a, 0x38, 0x64, 0x3a, 0x39, 0x62, 0x3a, 0x33, 0x62, 0x3a, + 0x32, 0x37, 0x3a, 0x34, 0x61, 0x3a, 0x33, 0x32, 0x3a, 0x31, 0x62, 0x3a, + 0x39, 0x61, 0x3a, 0x30, 0x31, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, + 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, + 0x3a, 0x20, 0x34, 0x37, 0x3a, 0x62, 0x65, 0x3a, 0x61, 0x62, 0x3a, 0x63, + 0x39, 0x3a, 0x32, 0x32, 0x3a, 0x65, 0x61, 0x3a, 0x65, 0x38, 0x3a, 0x30, + 0x65, 0x3a, 0x37, 0x38, 0x3a, 0x37, 0x38, 0x3a, 0x33, 0x34, 0x3a, 0x36, + 0x32, 0x3a, 0x61, 0x37, 0x3a, 0x39, 0x66, 0x3a, 0x34, 0x35, 0x3a, 0x63, + 0x32, 0x3a, 0x35, 0x34, 0x3a, 0x66, 0x64, 0x3a, 0x65, 0x36, 0x3a, 0x38, + 0x62, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x34, 0x35, 0x3a, 0x31, 0x34, 0x3a, 0x30, 0x62, 0x3a, 0x33, 0x32, 0x3a, + 0x34, 0x37, 0x3a, 0x65, 0x62, 0x3a, 0x39, 0x63, 0x3a, 0x63, 0x38, 0x3a, + 0x63, 0x35, 0x3a, 0x62, 0x34, 0x3a, 0x66, 0x30, 0x3a, 0x64, 0x37, 0x3a, + 0x62, 0x35, 0x3a, 0x33, 0x30, 0x3a, 0x39, 0x31, 0x3a, 0x66, 0x37, 0x3a, + 0x33, 0x32, 0x3a, 0x39, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x39, 0x65, 0x3a, + 0x36, 0x65, 0x3a, 0x35, 0x61, 0x3a, 0x36, 0x33, 0x3a, 0x65, 0x32, 0x3a, + 0x37, 0x34, 0x3a, 0x39, 0x64, 0x3a, 0x64, 0x33, 0x3a, 0x61, 0x63, 0x3a, + 0x61, 0x39, 0x3a, 0x31, 0x39, 0x3a, 0x38, 0x65, 0x3a, 0x64, 0x61, 0x0a, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, + 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x44, 0x78, 0x54, 0x43, 0x43, + 0x41, 0x71, 0x32, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x42, + 0x41, 0x44, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, + 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x73, 0x46, 0x41, 0x44, 0x43, 0x42, + 0x67, 0x7a, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x42, 0x68, 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, 0x0a, 0x45, 0x44, 0x41, + 0x4f, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x67, 0x54, 0x42, 0x30, 0x46, + 0x79, 0x61, 0x58, 0x70, 0x76, 0x62, 0x6d, 0x45, 0x78, 0x45, 0x7a, 0x41, + 0x52, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x63, 0x54, 0x43, 0x6c, 0x4e, + 0x6a, 0x62, 0x33, 0x52, 0x30, 0x63, 0x32, 0x52, 0x68, 0x62, 0x47, 0x55, + 0x78, 0x47, 0x6a, 0x41, 0x59, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, + 0x54, 0x0a, 0x45, 0x55, 0x64, 0x76, 0x52, 0x47, 0x46, 0x6b, 0x5a, 0x48, + 0x6b, 0x75, 0x59, 0x32, 0x39, 0x74, 0x4c, 0x43, 0x42, 0x4a, 0x62, 0x6d, + 0x4d, 0x75, 0x4d, 0x54, 0x45, 0x77, 0x4c, 0x77, 0x59, 0x44, 0x56, 0x51, + 0x51, 0x44, 0x45, 0x79, 0x68, 0x48, 0x62, 0x79, 0x42, 0x45, 0x59, 0x57, + 0x52, 0x6b, 0x65, 0x53, 0x42, 0x53, 0x62, 0x32, 0x39, 0x30, 0x49, 0x45, + 0x4e, 0x6c, 0x63, 0x6e, 0x52, 0x70, 0x0a, 0x5a, 0x6d, 0x6c, 0x6a, 0x59, + 0x58, 0x52, 0x6c, 0x49, 0x45, 0x46, 0x31, 0x64, 0x47, 0x68, 0x76, 0x63, + 0x6d, 0x6c, 0x30, 0x65, 0x53, 0x41, 0x74, 0x49, 0x45, 0x63, 0x79, 0x4d, + 0x42, 0x34, 0x58, 0x44, 0x54, 0x41, 0x35, 0x4d, 0x44, 0x6b, 0x77, 0x4d, + 0x54, 0x41, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x46, 0x6f, 0x58, 0x44, + 0x54, 0x4d, 0x33, 0x4d, 0x54, 0x49, 0x7a, 0x4d, 0x54, 0x49, 0x7a, 0x0a, + 0x4e, 0x54, 0x6b, 0x31, 0x4f, 0x56, 0x6f, 0x77, 0x67, 0x59, 0x4d, 0x78, + 0x43, 0x7a, 0x41, 0x4a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x59, 0x54, + 0x41, 0x6c, 0x56, 0x54, 0x4d, 0x52, 0x41, 0x77, 0x44, 0x67, 0x59, 0x44, + 0x56, 0x51, 0x51, 0x49, 0x45, 0x77, 0x64, 0x42, 0x63, 0x6d, 0x6c, 0x36, + 0x62, 0x32, 0x35, 0x68, 0x4d, 0x52, 0x4d, 0x77, 0x45, 0x51, 0x59, 0x44, + 0x56, 0x51, 0x51, 0x48, 0x0a, 0x45, 0x77, 0x70, 0x54, 0x59, 0x32, 0x39, + 0x30, 0x64, 0x48, 0x4e, 0x6b, 0x59, 0x57, 0x78, 0x6c, 0x4d, 0x52, 0x6f, + 0x77, 0x47, 0x41, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4b, 0x45, 0x78, 0x46, + 0x48, 0x62, 0x30, 0x52, 0x68, 0x5a, 0x47, 0x52, 0x35, 0x4c, 0x6d, 0x4e, + 0x76, 0x62, 0x53, 0x77, 0x67, 0x53, 0x57, 0x35, 0x6a, 0x4c, 0x6a, 0x45, + 0x78, 0x4d, 0x43, 0x38, 0x47, 0x41, 0x31, 0x55, 0x45, 0x0a, 0x41, 0x78, + 0x4d, 0x6f, 0x52, 0x32, 0x38, 0x67, 0x52, 0x47, 0x46, 0x6b, 0x5a, 0x48, + 0x6b, 0x67, 0x55, 0x6d, 0x39, 0x76, 0x64, 0x43, 0x42, 0x44, 0x5a, 0x58, + 0x4a, 0x30, 0x61, 0x57, 0x5a, 0x70, 0x59, 0x32, 0x46, 0x30, 0x5a, 0x53, + 0x42, 0x42, 0x64, 0x58, 0x52, 0x6f, 0x62, 0x33, 0x4a, 0x70, 0x64, 0x48, + 0x6b, 0x67, 0x4c, 0x53, 0x42, 0x48, 0x4d, 0x6a, 0x43, 0x43, 0x41, 0x53, + 0x49, 0x77, 0x0a, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, + 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x42, 0x42, 0x51, 0x41, 0x44, 0x67, + 0x67, 0x45, 0x50, 0x41, 0x44, 0x43, 0x43, 0x41, 0x51, 0x6f, 0x43, 0x67, + 0x67, 0x45, 0x42, 0x41, 0x4c, 0x39, 0x78, 0x59, 0x67, 0x6a, 0x78, 0x2b, + 0x6c, 0x6b, 0x30, 0x39, 0x78, 0x76, 0x4a, 0x47, 0x4b, 0x50, 0x33, 0x67, + 0x45, 0x6c, 0x59, 0x36, 0x53, 0x4b, 0x44, 0x0a, 0x45, 0x36, 0x62, 0x46, + 0x49, 0x45, 0x4d, 0x42, 0x4f, 0x34, 0x54, 0x78, 0x35, 0x6f, 0x56, 0x4a, + 0x6e, 0x79, 0x66, 0x71, 0x39, 0x6f, 0x51, 0x62, 0x54, 0x71, 0x43, 0x30, + 0x32, 0x33, 0x43, 0x59, 0x78, 0x7a, 0x49, 0x42, 0x73, 0x51, 0x55, 0x2b, + 0x42, 0x30, 0x37, 0x75, 0x39, 0x50, 0x70, 0x50, 0x4c, 0x31, 0x6b, 0x77, + 0x49, 0x75, 0x65, 0x72, 0x47, 0x56, 0x5a, 0x72, 0x34, 0x6f, 0x41, 0x48, + 0x0a, 0x2f, 0x50, 0x4d, 0x57, 0x64, 0x59, 0x41, 0x35, 0x55, 0x58, 0x76, + 0x6c, 0x2b, 0x54, 0x57, 0x32, 0x64, 0x45, 0x36, 0x70, 0x6a, 0x59, 0x49, + 0x54, 0x35, 0x4c, 0x59, 0x2f, 0x71, 0x51, 0x4f, 0x44, 0x2b, 0x71, 0x4b, + 0x2b, 0x69, 0x68, 0x56, 0x71, 0x66, 0x39, 0x34, 0x4c, 0x77, 0x37, 0x59, + 0x5a, 0x46, 0x41, 0x58, 0x4b, 0x36, 0x73, 0x4f, 0x6f, 0x42, 0x4a, 0x51, + 0x37, 0x52, 0x6e, 0x77, 0x79, 0x0a, 0x44, 0x66, 0x4d, 0x41, 0x5a, 0x69, + 0x4c, 0x49, 0x6a, 0x57, 0x6c, 0x74, 0x4e, 0x6f, 0x77, 0x52, 0x47, 0x4c, + 0x66, 0x54, 0x73, 0x68, 0x78, 0x67, 0x74, 0x44, 0x6a, 0x36, 0x41, 0x6f, + 0x7a, 0x4f, 0x30, 0x39, 0x31, 0x47, 0x42, 0x39, 0x34, 0x4b, 0x50, 0x75, + 0x74, 0x64, 0x66, 0x4d, 0x68, 0x38, 0x2b, 0x37, 0x41, 0x72, 0x55, 0x36, + 0x53, 0x53, 0x59, 0x6d, 0x6c, 0x52, 0x4a, 0x51, 0x56, 0x68, 0x0a, 0x47, + 0x6b, 0x53, 0x42, 0x6a, 0x43, 0x79, 0x70, 0x51, 0x35, 0x59, 0x6a, 0x33, + 0x36, 0x77, 0x36, 0x67, 0x5a, 0x6f, 0x4f, 0x4b, 0x63, 0x55, 0x63, 0x71, + 0x65, 0x6c, 0x64, 0x48, 0x72, 0x61, 0x65, 0x6e, 0x6a, 0x41, 0x4b, 0x4f, + 0x63, 0x37, 0x78, 0x69, 0x49, 0x44, 0x37, 0x53, 0x31, 0x33, 0x4d, 0x4d, + 0x75, 0x79, 0x46, 0x59, 0x6b, 0x4d, 0x6c, 0x4e, 0x41, 0x4a, 0x57, 0x4a, + 0x77, 0x47, 0x52, 0x0a, 0x74, 0x44, 0x74, 0x77, 0x4b, 0x6a, 0x39, 0x75, + 0x73, 0x65, 0x69, 0x63, 0x69, 0x41, 0x46, 0x39, 0x6e, 0x39, 0x54, 0x35, + 0x32, 0x31, 0x4e, 0x74, 0x59, 0x4a, 0x32, 0x2f, 0x4c, 0x4f, 0x64, 0x59, + 0x71, 0x37, 0x68, 0x66, 0x52, 0x76, 0x7a, 0x4f, 0x78, 0x42, 0x73, 0x44, + 0x50, 0x41, 0x6e, 0x72, 0x53, 0x54, 0x46, 0x63, 0x61, 0x55, 0x61, 0x7a, + 0x34, 0x45, 0x63, 0x43, 0x41, 0x77, 0x45, 0x41, 0x0a, 0x41, 0x61, 0x4e, + 0x43, 0x4d, 0x45, 0x41, 0x77, 0x44, 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, + 0x54, 0x41, 0x51, 0x48, 0x2f, 0x42, 0x41, 0x55, 0x77, 0x41, 0x77, 0x45, + 0x42, 0x2f, 0x7a, 0x41, 0x4f, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x38, + 0x42, 0x41, 0x66, 0x38, 0x45, 0x42, 0x41, 0x4d, 0x43, 0x41, 0x51, 0x59, + 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, 0x52, 0x30, 0x4f, 0x42, 0x42, 0x59, + 0x45, 0x0a, 0x46, 0x44, 0x71, 0x61, 0x68, 0x51, 0x63, 0x51, 0x5a, 0x79, + 0x69, 0x32, 0x37, 0x2f, 0x61, 0x39, 0x42, 0x55, 0x46, 0x75, 0x49, 0x4d, + 0x47, 0x55, 0x32, 0x67, 0x2f, 0x65, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, + 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x43, 0x77, + 0x55, 0x41, 0x41, 0x34, 0x49, 0x42, 0x41, 0x51, 0x43, 0x5a, 0x32, 0x31, + 0x31, 0x35, 0x31, 0x66, 0x6d, 0x58, 0x0a, 0x57, 0x57, 0x63, 0x44, 0x59, + 0x66, 0x46, 0x2b, 0x4f, 0x77, 0x59, 0x78, 0x64, 0x53, 0x32, 0x68, 0x49, + 0x49, 0x35, 0x50, 0x5a, 0x59, 0x65, 0x30, 0x39, 0x36, 0x61, 0x63, 0x76, + 0x4e, 0x6a, 0x70, 0x4c, 0x39, 0x44, 0x62, 0x57, 0x75, 0x37, 0x50, 0x64, + 0x49, 0x78, 0x7a, 0x74, 0x44, 0x68, 0x43, 0x32, 0x67, 0x56, 0x37, 0x2b, + 0x41, 0x4a, 0x31, 0x75, 0x50, 0x32, 0x6c, 0x73, 0x64, 0x65, 0x75, 0x0a, + 0x39, 0x74, 0x66, 0x65, 0x45, 0x38, 0x74, 0x54, 0x45, 0x48, 0x36, 0x4b, + 0x52, 0x74, 0x47, 0x58, 0x2b, 0x72, 0x63, 0x75, 0x4b, 0x78, 0x47, 0x72, + 0x6b, 0x4c, 0x41, 0x6e, 0x67, 0x50, 0x6e, 0x6f, 0x6e, 0x31, 0x72, 0x70, + 0x4e, 0x35, 0x2b, 0x72, 0x35, 0x4e, 0x39, 0x73, 0x73, 0x34, 0x55, 0x58, + 0x6e, 0x54, 0x33, 0x5a, 0x4a, 0x45, 0x39, 0x35, 0x6b, 0x54, 0x58, 0x57, + 0x58, 0x77, 0x54, 0x72, 0x0a, 0x67, 0x49, 0x4f, 0x72, 0x6d, 0x67, 0x49, + 0x74, 0x74, 0x52, 0x44, 0x30, 0x32, 0x4a, 0x44, 0x48, 0x42, 0x48, 0x4e, + 0x41, 0x37, 0x58, 0x49, 0x6c, 0x6f, 0x4b, 0x6d, 0x66, 0x37, 0x4a, 0x36, + 0x72, 0x61, 0x42, 0x4b, 0x5a, 0x56, 0x38, 0x61, 0x50, 0x45, 0x6a, 0x6f, + 0x4a, 0x70, 0x4c, 0x31, 0x45, 0x2f, 0x51, 0x59, 0x56, 0x4e, 0x38, 0x47, + 0x62, 0x35, 0x44, 0x4b, 0x6a, 0x37, 0x54, 0x6a, 0x6f, 0x0a, 0x32, 0x47, + 0x54, 0x7a, 0x4c, 0x48, 0x34, 0x55, 0x2f, 0x41, 0x4c, 0x71, 0x6e, 0x38, + 0x33, 0x2f, 0x42, 0x32, 0x67, 0x58, 0x32, 0x79, 0x4b, 0x51, 0x4f, 0x43, + 0x31, 0x36, 0x6a, 0x64, 0x46, 0x55, 0x38, 0x57, 0x6e, 0x6a, 0x58, 0x7a, + 0x50, 0x4b, 0x65, 0x6a, 0x31, 0x37, 0x43, 0x75, 0x50, 0x4b, 0x66, 0x31, + 0x38, 0x35, 0x35, 0x65, 0x4a, 0x31, 0x75, 0x73, 0x56, 0x32, 0x47, 0x44, + 0x50, 0x4f, 0x0a, 0x4c, 0x50, 0x41, 0x76, 0x54, 0x4b, 0x33, 0x33, 0x73, + 0x65, 0x66, 0x4f, 0x54, 0x36, 0x6a, 0x45, 0x6d, 0x30, 0x70, 0x55, 0x42, + 0x73, 0x56, 0x2f, 0x66, 0x64, 0x55, 0x49, 0x44, 0x2b, 0x49, 0x63, 0x2f, + 0x6e, 0x34, 0x58, 0x75, 0x4b, 0x78, 0x65, 0x39, 0x74, 0x51, 0x57, 0x73, + 0x6b, 0x4d, 0x4a, 0x44, 0x45, 0x33, 0x32, 0x70, 0x32, 0x75, 0x30, 0x6d, + 0x59, 0x52, 0x6c, 0x79, 0x6e, 0x71, 0x49, 0x0a, 0x34, 0x75, 0x4a, 0x45, + 0x76, 0x6c, 0x7a, 0x33, 0x36, 0x68, 0x7a, 0x31, 0x0a, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, + 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, + 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, + 0x3d, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x52, + 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, + 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x20, 0x4f, 0x3d, 0x53, 0x74, 0x61, + 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, + 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, + 0x2e, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, + 0x20, 0x43, 0x4e, 0x3d, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x20, 0x4f, 0x3d, + 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, 0x65, + 0x63, 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, 0x20, + 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, + 0x3a, 0x20, 0x22, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x22, 0x0a, 0x23, 0x20, + 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x30, 0x0a, 0x23, 0x20, + 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, + 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x64, 0x36, 0x3a, 0x33, 0x39, 0x3a, 0x38, + 0x31, 0x3a, 0x63, 0x36, 0x3a, 0x35, 0x32, 0x3a, 0x37, 0x65, 0x3a, 0x39, + 0x36, 0x3a, 0x36, 0x39, 0x3a, 0x66, 0x63, 0x3a, 0x66, 0x63, 0x3a, 0x63, + 0x61, 0x3a, 0x36, 0x36, 0x3a, 0x65, 0x64, 0x3a, 0x30, 0x35, 0x3a, 0x66, + 0x32, 0x3a, 0x39, 0x36, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, + 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, + 0x20, 0x62, 0x35, 0x3a, 0x31, 0x63, 0x3a, 0x30, 0x36, 0x3a, 0x37, 0x63, + 0x3a, 0x65, 0x65, 0x3a, 0x32, 0x62, 0x3a, 0x30, 0x63, 0x3a, 0x33, 0x64, + 0x3a, 0x66, 0x38, 0x3a, 0x35, 0x35, 0x3a, 0x61, 0x62, 0x3a, 0x32, 0x64, + 0x3a, 0x39, 0x32, 0x3a, 0x66, 0x34, 0x3a, 0x66, 0x65, 0x3a, 0x33, 0x39, + 0x3a, 0x64, 0x34, 0x3a, 0x65, 0x37, 0x3a, 0x30, 0x66, 0x3a, 0x30, 0x65, + 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, + 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x32, + 0x63, 0x3a, 0x65, 0x31, 0x3a, 0x63, 0x62, 0x3a, 0x30, 0x62, 0x3a, 0x66, + 0x39, 0x3a, 0x64, 0x32, 0x3a, 0x66, 0x39, 0x3a, 0x65, 0x31, 0x3a, 0x30, + 0x32, 0x3a, 0x39, 0x39, 0x3a, 0x33, 0x66, 0x3a, 0x62, 0x65, 0x3a, 0x32, + 0x31, 0x3a, 0x35, 0x31, 0x3a, 0x35, 0x32, 0x3a, 0x63, 0x33, 0x3a, 0x62, + 0x32, 0x3a, 0x64, 0x64, 0x3a, 0x30, 0x63, 0x3a, 0x61, 0x62, 0x3a, 0x64, + 0x65, 0x3a, 0x31, 0x63, 0x3a, 0x36, 0x38, 0x3a, 0x65, 0x35, 0x3a, 0x33, + 0x31, 0x3a, 0x39, 0x62, 0x3a, 0x38, 0x33, 0x3a, 0x39, 0x31, 0x3a, 0x35, + 0x34, 0x3a, 0x64, 0x62, 0x3a, 0x62, 0x37, 0x3a, 0x66, 0x35, 0x0a, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, + 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x44, 0x33, 0x54, 0x43, 0x43, 0x41, + 0x73, 0x57, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x42, 0x41, + 0x44, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, + 0x77, 0x30, 0x42, 0x41, 0x51, 0x73, 0x46, 0x41, 0x44, 0x43, 0x42, 0x6a, + 0x7a, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, + 0x68, 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, 0x0a, 0x45, 0x44, 0x41, 0x4f, + 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x67, 0x54, 0x42, 0x30, 0x46, 0x79, + 0x61, 0x58, 0x70, 0x76, 0x62, 0x6d, 0x45, 0x78, 0x45, 0x7a, 0x41, 0x52, + 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x63, 0x54, 0x43, 0x6c, 0x4e, 0x6a, + 0x62, 0x33, 0x52, 0x30, 0x63, 0x32, 0x52, 0x68, 0x62, 0x47, 0x55, 0x78, + 0x4a, 0x54, 0x41, 0x6a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, + 0x0a, 0x48, 0x46, 0x4e, 0x30, 0x59, 0x58, 0x4a, 0x6d, 0x61, 0x57, 0x56, + 0x73, 0x5a, 0x43, 0x42, 0x55, 0x5a, 0x57, 0x4e, 0x6f, 0x62, 0x6d, 0x39, + 0x73, 0x62, 0x32, 0x64, 0x70, 0x5a, 0x58, 0x4d, 0x73, 0x49, 0x45, 0x6c, + 0x75, 0x59, 0x79, 0x34, 0x78, 0x4d, 0x6a, 0x41, 0x77, 0x42, 0x67, 0x4e, + 0x56, 0x42, 0x41, 0x4d, 0x54, 0x4b, 0x56, 0x4e, 0x30, 0x59, 0x58, 0x4a, + 0x6d, 0x61, 0x57, 0x56, 0x73, 0x0a, 0x5a, 0x43, 0x42, 0x53, 0x62, 0x32, + 0x39, 0x30, 0x49, 0x45, 0x4e, 0x6c, 0x63, 0x6e, 0x52, 0x70, 0x5a, 0x6d, + 0x6c, 0x6a, 0x59, 0x58, 0x52, 0x6c, 0x49, 0x45, 0x46, 0x31, 0x64, 0x47, + 0x68, 0x76, 0x63, 0x6d, 0x6c, 0x30, 0x65, 0x53, 0x41, 0x74, 0x49, 0x45, + 0x63, 0x79, 0x4d, 0x42, 0x34, 0x58, 0x44, 0x54, 0x41, 0x35, 0x4d, 0x44, + 0x6b, 0x77, 0x4d, 0x54, 0x41, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x0a, 0x4d, + 0x46, 0x6f, 0x58, 0x44, 0x54, 0x4d, 0x33, 0x4d, 0x54, 0x49, 0x7a, 0x4d, + 0x54, 0x49, 0x7a, 0x4e, 0x54, 0x6b, 0x31, 0x4f, 0x56, 0x6f, 0x77, 0x67, + 0x59, 0x38, 0x78, 0x43, 0x7a, 0x41, 0x4a, 0x42, 0x67, 0x4e, 0x56, 0x42, + 0x41, 0x59, 0x54, 0x41, 0x6c, 0x56, 0x54, 0x4d, 0x52, 0x41, 0x77, 0x44, + 0x67, 0x59, 0x44, 0x56, 0x51, 0x51, 0x49, 0x45, 0x77, 0x64, 0x42, 0x63, + 0x6d, 0x6c, 0x36, 0x0a, 0x62, 0x32, 0x35, 0x68, 0x4d, 0x52, 0x4d, 0x77, + 0x45, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x48, 0x45, 0x77, 0x70, 0x54, + 0x59, 0x32, 0x39, 0x30, 0x64, 0x48, 0x4e, 0x6b, 0x59, 0x57, 0x78, 0x6c, + 0x4d, 0x53, 0x55, 0x77, 0x49, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4b, + 0x45, 0x78, 0x78, 0x54, 0x64, 0x47, 0x46, 0x79, 0x5a, 0x6d, 0x6c, 0x6c, + 0x62, 0x47, 0x51, 0x67, 0x56, 0x47, 0x56, 0x6a, 0x0a, 0x61, 0x47, 0x35, + 0x76, 0x62, 0x47, 0x39, 0x6e, 0x61, 0x57, 0x56, 0x7a, 0x4c, 0x43, 0x42, + 0x4a, 0x62, 0x6d, 0x4d, 0x75, 0x4d, 0x54, 0x49, 0x77, 0x4d, 0x41, 0x59, + 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, 0x79, 0x6c, 0x54, 0x64, 0x47, 0x46, + 0x79, 0x5a, 0x6d, 0x6c, 0x6c, 0x62, 0x47, 0x51, 0x67, 0x55, 0x6d, 0x39, + 0x76, 0x64, 0x43, 0x42, 0x44, 0x5a, 0x58, 0x4a, 0x30, 0x61, 0x57, 0x5a, + 0x70, 0x0a, 0x59, 0x32, 0x46, 0x30, 0x5a, 0x53, 0x42, 0x42, 0x64, 0x58, + 0x52, 0x6f, 0x62, 0x33, 0x4a, 0x70, 0x64, 0x48, 0x6b, 0x67, 0x4c, 0x53, + 0x42, 0x48, 0x4d, 0x6a, 0x43, 0x43, 0x41, 0x53, 0x49, 0x77, 0x44, 0x51, + 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, + 0x45, 0x42, 0x42, 0x51, 0x41, 0x44, 0x67, 0x67, 0x45, 0x50, 0x41, 0x44, + 0x43, 0x43, 0x41, 0x51, 0x6f, 0x43, 0x0a, 0x67, 0x67, 0x45, 0x42, 0x41, + 0x4c, 0x33, 0x74, 0x77, 0x51, 0x50, 0x38, 0x39, 0x6f, 0x2f, 0x38, 0x41, + 0x72, 0x46, 0x76, 0x57, 0x35, 0x39, 0x49, 0x32, 0x5a, 0x31, 0x35, 0x34, + 0x71, 0x4b, 0x33, 0x41, 0x32, 0x46, 0x57, 0x47, 0x4d, 0x4e, 0x48, 0x74, + 0x74, 0x66, 0x4b, 0x50, 0x54, 0x55, 0x75, 0x69, 0x55, 0x50, 0x33, 0x6f, + 0x57, 0x6d, 0x62, 0x33, 0x6f, 0x6f, 0x61, 0x2f, 0x52, 0x4d, 0x67, 0x0a, + 0x6e, 0x4c, 0x52, 0x4a, 0x64, 0x7a, 0x49, 0x70, 0x56, 0x76, 0x32, 0x35, + 0x37, 0x49, 0x7a, 0x64, 0x49, 0x76, 0x70, 0x79, 0x33, 0x43, 0x64, 0x68, + 0x6c, 0x2b, 0x37, 0x32, 0x57, 0x6f, 0x54, 0x73, 0x62, 0x68, 0x6d, 0x35, + 0x69, 0x53, 0x7a, 0x63, 0x68, 0x46, 0x76, 0x56, 0x64, 0x50, 0x74, 0x72, + 0x58, 0x38, 0x57, 0x4a, 0x70, 0x52, 0x42, 0x53, 0x69, 0x55, 0x5a, 0x56, + 0x39, 0x4c, 0x68, 0x31, 0x0a, 0x48, 0x4f, 0x5a, 0x2f, 0x35, 0x46, 0x53, + 0x75, 0x53, 0x2f, 0x68, 0x56, 0x63, 0x6c, 0x63, 0x43, 0x47, 0x66, 0x67, + 0x58, 0x63, 0x56, 0x6e, 0x72, 0x48, 0x69, 0x67, 0x48, 0x64, 0x4d, 0x57, + 0x64, 0x53, 0x4c, 0x35, 0x73, 0x74, 0x50, 0x53, 0x6b, 0x73, 0x50, 0x4e, + 0x6b, 0x4e, 0x33, 0x6d, 0x53, 0x77, 0x4f, 0x78, 0x47, 0x58, 0x6e, 0x2f, + 0x68, 0x62, 0x56, 0x4e, 0x4d, 0x59, 0x71, 0x2f, 0x4e, 0x0a, 0x48, 0x77, + 0x74, 0x6a, 0x75, 0x7a, 0x71, 0x64, 0x2b, 0x2f, 0x78, 0x35, 0x41, 0x4a, + 0x68, 0x68, 0x64, 0x4d, 0x38, 0x6d, 0x67, 0x6b, 0x42, 0x6a, 0x38, 0x37, + 0x4a, 0x79, 0x61, 0x68, 0x6b, 0x4e, 0x6d, 0x63, 0x72, 0x55, 0x44, 0x6e, + 0x58, 0x4d, 0x4e, 0x2f, 0x75, 0x4c, 0x69, 0x63, 0x46, 0x5a, 0x38, 0x57, + 0x4a, 0x2f, 0x58, 0x37, 0x4e, 0x66, 0x5a, 0x54, 0x44, 0x34, 0x70, 0x37, + 0x64, 0x4e, 0x0a, 0x64, 0x6c, 0x6f, 0x65, 0x64, 0x6c, 0x34, 0x30, 0x77, + 0x4f, 0x69, 0x57, 0x56, 0x70, 0x6d, 0x4b, 0x73, 0x2f, 0x42, 0x2f, 0x70, + 0x4d, 0x32, 0x39, 0x33, 0x44, 0x49, 0x78, 0x66, 0x4a, 0x48, 0x50, 0x34, + 0x46, 0x38, 0x52, 0x2b, 0x47, 0x75, 0x71, 0x53, 0x56, 0x7a, 0x52, 0x6d, + 0x5a, 0x54, 0x52, 0x6f, 0x75, 0x4e, 0x6a, 0x57, 0x77, 0x6c, 0x32, 0x74, + 0x56, 0x5a, 0x69, 0x34, 0x55, 0x74, 0x30, 0x0a, 0x48, 0x5a, 0x62, 0x55, + 0x4a, 0x74, 0x51, 0x49, 0x42, 0x46, 0x6e, 0x51, 0x6d, 0x41, 0x34, 0x4f, + 0x35, 0x74, 0x37, 0x38, 0x77, 0x2b, 0x77, 0x66, 0x6b, 0x50, 0x45, 0x43, + 0x41, 0x77, 0x45, 0x41, 0x41, 0x61, 0x4e, 0x43, 0x4d, 0x45, 0x41, 0x77, + 0x44, 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, 0x54, 0x41, 0x51, 0x48, 0x2f, + 0x42, 0x41, 0x55, 0x77, 0x41, 0x77, 0x45, 0x42, 0x2f, 0x7a, 0x41, 0x4f, + 0x0a, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x38, 0x42, 0x41, 0x66, 0x38, + 0x45, 0x42, 0x41, 0x4d, 0x43, 0x41, 0x51, 0x59, 0x77, 0x48, 0x51, 0x59, + 0x44, 0x56, 0x52, 0x30, 0x4f, 0x42, 0x42, 0x59, 0x45, 0x46, 0x48, 0x77, + 0x4d, 0x4d, 0x68, 0x2b, 0x6e, 0x32, 0x54, 0x42, 0x2f, 0x78, 0x48, 0x31, + 0x6f, 0x6f, 0x32, 0x4b, 0x6f, 0x6f, 0x63, 0x36, 0x72, 0x42, 0x31, 0x73, + 0x6e, 0x4d, 0x41, 0x30, 0x47, 0x0a, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, + 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x43, 0x77, 0x55, 0x41, 0x41, 0x34, + 0x49, 0x42, 0x41, 0x51, 0x41, 0x52, 0x57, 0x66, 0x6f, 0x6c, 0x54, 0x77, + 0x4e, 0x76, 0x6c, 0x4a, 0x6b, 0x37, 0x6d, 0x68, 0x2b, 0x43, 0x68, 0x54, + 0x6e, 0x55, 0x64, 0x67, 0x57, 0x55, 0x58, 0x75, 0x45, 0x6f, 0x6b, 0x32, + 0x31, 0x69, 0x58, 0x51, 0x6e, 0x43, 0x6f, 0x4b, 0x6a, 0x55, 0x0a, 0x73, + 0x48, 0x55, 0x34, 0x38, 0x54, 0x52, 0x71, 0x6e, 0x65, 0x53, 0x66, 0x69, + 0x6f, 0x59, 0x6d, 0x55, 0x65, 0x59, 0x73, 0x30, 0x63, 0x59, 0x74, 0x62, + 0x70, 0x55, 0x67, 0x53, 0x70, 0x49, 0x42, 0x37, 0x4c, 0x69, 0x4b, 0x5a, + 0x33, 0x73, 0x78, 0x34, 0x6d, 0x63, 0x75, 0x6a, 0x4a, 0x55, 0x44, 0x4a, + 0x69, 0x35, 0x44, 0x6e, 0x55, 0x6f, 0x78, 0x39, 0x67, 0x36, 0x31, 0x44, + 0x4c, 0x75, 0x33, 0x0a, 0x34, 0x6a, 0x64, 0x2f, 0x49, 0x72, 0x6f, 0x41, + 0x6f, 0x77, 0x35, 0x37, 0x55, 0x76, 0x74, 0x72, 0x75, 0x7a, 0x76, 0x45, + 0x30, 0x33, 0x6c, 0x52, 0x54, 0x73, 0x32, 0x51, 0x39, 0x47, 0x63, 0x48, + 0x47, 0x63, 0x67, 0x38, 0x52, 0x6e, 0x6f, 0x4e, 0x41, 0x58, 0x33, 0x46, + 0x57, 0x4f, 0x64, 0x74, 0x35, 0x6f, 0x55, 0x77, 0x46, 0x35, 0x6f, 0x6b, + 0x78, 0x42, 0x44, 0x67, 0x42, 0x50, 0x66, 0x67, 0x0a, 0x38, 0x6e, 0x2f, + 0x55, 0x71, 0x67, 0x72, 0x2f, 0x51, 0x68, 0x30, 0x33, 0x37, 0x5a, 0x54, + 0x6c, 0x5a, 0x46, 0x6b, 0x53, 0x49, 0x48, 0x63, 0x34, 0x30, 0x7a, 0x49, + 0x2b, 0x4f, 0x49, 0x46, 0x31, 0x6c, 0x6e, 0x50, 0x36, 0x61, 0x49, 0x2b, + 0x78, 0x79, 0x38, 0x34, 0x66, 0x78, 0x65, 0x7a, 0x36, 0x6e, 0x48, 0x37, + 0x50, 0x66, 0x72, 0x48, 0x78, 0x42, 0x79, 0x32, 0x32, 0x2f, 0x4c, 0x2f, + 0x4b, 0x0a, 0x70, 0x4c, 0x2f, 0x51, 0x6c, 0x77, 0x56, 0x4b, 0x76, 0x4f, + 0x6f, 0x59, 0x4b, 0x41, 0x4b, 0x51, 0x76, 0x56, 0x52, 0x34, 0x43, 0x53, + 0x46, 0x78, 0x30, 0x39, 0x46, 0x39, 0x48, 0x64, 0x6b, 0x57, 0x73, 0x4b, + 0x6c, 0x68, 0x50, 0x64, 0x41, 0x4b, 0x41, 0x43, 0x4c, 0x38, 0x78, 0x33, + 0x76, 0x4c, 0x43, 0x57, 0x52, 0x46, 0x43, 0x7a, 0x74, 0x41, 0x67, 0x66, + 0x64, 0x39, 0x66, 0x44, 0x4c, 0x31, 0x0a, 0x6d, 0x4d, 0x70, 0x59, 0x6a, + 0x6e, 0x30, 0x71, 0x37, 0x70, 0x42, 0x5a, 0x63, 0x32, 0x54, 0x35, 0x4e, + 0x6e, 0x52, 0x65, 0x4a, 0x61, 0x48, 0x31, 0x5a, 0x67, 0x55, 0x75, 0x66, + 0x7a, 0x6b, 0x56, 0x71, 0x53, 0x72, 0x37, 0x55, 0x49, 0x75, 0x4f, 0x68, + 0x57, 0x6e, 0x30, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, + 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, + 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x53, 0x74, 0x61, 0x72, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x20, 0x4f, + 0x3d, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, + 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, + 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x53, 0x74, 0x61, 0x72, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x20, 0x4f, + 0x3d, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, + 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, + 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x52, + 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, + 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, + 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x30, 0x0a, 0x23, 0x20, 0x4d, 0x44, + 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, + 0x74, 0x3a, 0x20, 0x31, 0x37, 0x3a, 0x33, 0x35, 0x3a, 0x37, 0x34, 0x3a, + 0x61, 0x66, 0x3a, 0x37, 0x62, 0x3a, 0x36, 0x31, 0x3a, 0x31, 0x63, 0x3a, + 0x65, 0x62, 0x3a, 0x66, 0x34, 0x3a, 0x66, 0x39, 0x3a, 0x33, 0x63, 0x3a, + 0x65, 0x32, 0x3a, 0x65, 0x65, 0x3a, 0x34, 0x30, 0x3a, 0x66, 0x39, 0x3a, + 0x61, 0x32, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, + 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x39, + 0x32, 0x3a, 0x35, 0x61, 0x3a, 0x38, 0x66, 0x3a, 0x38, 0x64, 0x3a, 0x32, + 0x63, 0x3a, 0x36, 0x64, 0x3a, 0x30, 0x34, 0x3a, 0x65, 0x30, 0x3a, 0x36, + 0x36, 0x3a, 0x35, 0x66, 0x3a, 0x35, 0x39, 0x3a, 0x36, 0x61, 0x3a, 0x66, + 0x66, 0x3a, 0x32, 0x32, 0x3a, 0x64, 0x38, 0x3a, 0x36, 0x33, 0x3a, 0x65, + 0x38, 0x3a, 0x32, 0x35, 0x3a, 0x36, 0x66, 0x3a, 0x33, 0x66, 0x0a, 0x23, + 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, + 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x35, 0x36, 0x3a, + 0x38, 0x64, 0x3a, 0x36, 0x39, 0x3a, 0x30, 0x35, 0x3a, 0x61, 0x32, 0x3a, + 0x63, 0x38, 0x3a, 0x38, 0x37, 0x3a, 0x30, 0x38, 0x3a, 0x61, 0x34, 0x3a, + 0x62, 0x33, 0x3a, 0x30, 0x32, 0x3a, 0x35, 0x31, 0x3a, 0x39, 0x30, 0x3a, + 0x65, 0x64, 0x3a, 0x63, 0x66, 0x3a, 0x65, 0x64, 0x3a, 0x62, 0x31, 0x3a, + 0x39, 0x37, 0x3a, 0x34, 0x61, 0x3a, 0x36, 0x30, 0x3a, 0x36, 0x61, 0x3a, + 0x31, 0x33, 0x3a, 0x63, 0x36, 0x3a, 0x65, 0x35, 0x3a, 0x32, 0x39, 0x3a, + 0x30, 0x66, 0x3a, 0x63, 0x62, 0x3a, 0x32, 0x61, 0x3a, 0x65, 0x36, 0x3a, + 0x33, 0x65, 0x3a, 0x64, 0x61, 0x3a, 0x62, 0x35, 0x0a, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, + 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x0a, 0x4d, 0x49, 0x49, 0x44, 0x37, 0x7a, 0x43, 0x43, 0x41, 0x74, 0x65, + 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x42, 0x41, 0x44, 0x41, + 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, + 0x42, 0x41, 0x51, 0x73, 0x46, 0x41, 0x44, 0x43, 0x42, 0x6d, 0x44, 0x45, + 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, + 0x43, 0x56, 0x56, 0x4d, 0x78, 0x0a, 0x45, 0x44, 0x41, 0x4f, 0x42, 0x67, + 0x4e, 0x56, 0x42, 0x41, 0x67, 0x54, 0x42, 0x30, 0x46, 0x79, 0x61, 0x58, + 0x70, 0x76, 0x62, 0x6d, 0x45, 0x78, 0x45, 0x7a, 0x41, 0x52, 0x42, 0x67, + 0x4e, 0x56, 0x42, 0x41, 0x63, 0x54, 0x43, 0x6c, 0x4e, 0x6a, 0x62, 0x33, + 0x52, 0x30, 0x63, 0x32, 0x52, 0x68, 0x62, 0x47, 0x55, 0x78, 0x4a, 0x54, + 0x41, 0x6a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, 0x0a, 0x48, + 0x46, 0x4e, 0x30, 0x59, 0x58, 0x4a, 0x6d, 0x61, 0x57, 0x56, 0x73, 0x5a, + 0x43, 0x42, 0x55, 0x5a, 0x57, 0x4e, 0x6f, 0x62, 0x6d, 0x39, 0x73, 0x62, + 0x32, 0x64, 0x70, 0x5a, 0x58, 0x4d, 0x73, 0x49, 0x45, 0x6c, 0x75, 0x59, + 0x79, 0x34, 0x78, 0x4f, 0x7a, 0x41, 0x35, 0x42, 0x67, 0x4e, 0x56, 0x42, + 0x41, 0x4d, 0x54, 0x4d, 0x6c, 0x4e, 0x30, 0x59, 0x58, 0x4a, 0x6d, 0x61, + 0x57, 0x56, 0x73, 0x0a, 0x5a, 0x43, 0x42, 0x54, 0x5a, 0x58, 0x4a, 0x32, + 0x61, 0x57, 0x4e, 0x6c, 0x63, 0x79, 0x42, 0x53, 0x62, 0x32, 0x39, 0x30, + 0x49, 0x45, 0x4e, 0x6c, 0x63, 0x6e, 0x52, 0x70, 0x5a, 0x6d, 0x6c, 0x6a, + 0x59, 0x58, 0x52, 0x6c, 0x49, 0x45, 0x46, 0x31, 0x64, 0x47, 0x68, 0x76, + 0x63, 0x6d, 0x6c, 0x30, 0x65, 0x53, 0x41, 0x74, 0x49, 0x45, 0x63, 0x79, + 0x4d, 0x42, 0x34, 0x58, 0x44, 0x54, 0x41, 0x35, 0x0a, 0x4d, 0x44, 0x6b, + 0x77, 0x4d, 0x54, 0x41, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x46, 0x6f, + 0x58, 0x44, 0x54, 0x4d, 0x33, 0x4d, 0x54, 0x49, 0x7a, 0x4d, 0x54, 0x49, + 0x7a, 0x4e, 0x54, 0x6b, 0x31, 0x4f, 0x56, 0x6f, 0x77, 0x67, 0x5a, 0x67, + 0x78, 0x43, 0x7a, 0x41, 0x4a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x59, + 0x54, 0x41, 0x6c, 0x56, 0x54, 0x4d, 0x52, 0x41, 0x77, 0x44, 0x67, 0x59, + 0x44, 0x0a, 0x56, 0x51, 0x51, 0x49, 0x45, 0x77, 0x64, 0x42, 0x63, 0x6d, + 0x6c, 0x36, 0x62, 0x32, 0x35, 0x68, 0x4d, 0x52, 0x4d, 0x77, 0x45, 0x51, + 0x59, 0x44, 0x56, 0x51, 0x51, 0x48, 0x45, 0x77, 0x70, 0x54, 0x59, 0x32, + 0x39, 0x30, 0x64, 0x48, 0x4e, 0x6b, 0x59, 0x57, 0x78, 0x6c, 0x4d, 0x53, + 0x55, 0x77, 0x49, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4b, 0x45, 0x78, + 0x78, 0x54, 0x64, 0x47, 0x46, 0x79, 0x0a, 0x5a, 0x6d, 0x6c, 0x6c, 0x62, + 0x47, 0x51, 0x67, 0x56, 0x47, 0x56, 0x6a, 0x61, 0x47, 0x35, 0x76, 0x62, + 0x47, 0x39, 0x6e, 0x61, 0x57, 0x56, 0x7a, 0x4c, 0x43, 0x42, 0x4a, 0x62, + 0x6d, 0x4d, 0x75, 0x4d, 0x54, 0x73, 0x77, 0x4f, 0x51, 0x59, 0x44, 0x56, + 0x51, 0x51, 0x44, 0x45, 0x7a, 0x4a, 0x54, 0x64, 0x47, 0x46, 0x79, 0x5a, + 0x6d, 0x6c, 0x6c, 0x62, 0x47, 0x51, 0x67, 0x55, 0x32, 0x56, 0x79, 0x0a, + 0x64, 0x6d, 0x6c, 0x6a, 0x5a, 0x58, 0x4d, 0x67, 0x55, 0x6d, 0x39, 0x76, + 0x64, 0x43, 0x42, 0x44, 0x5a, 0x58, 0x4a, 0x30, 0x61, 0x57, 0x5a, 0x70, + 0x59, 0x32, 0x46, 0x30, 0x5a, 0x53, 0x42, 0x42, 0x64, 0x58, 0x52, 0x6f, + 0x62, 0x33, 0x4a, 0x70, 0x64, 0x48, 0x6b, 0x67, 0x4c, 0x53, 0x42, 0x48, + 0x4d, 0x6a, 0x43, 0x43, 0x41, 0x53, 0x49, 0x77, 0x44, 0x51, 0x59, 0x4a, + 0x4b, 0x6f, 0x5a, 0x49, 0x0a, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, + 0x42, 0x42, 0x51, 0x41, 0x44, 0x67, 0x67, 0x45, 0x50, 0x41, 0x44, 0x43, + 0x43, 0x41, 0x51, 0x6f, 0x43, 0x67, 0x67, 0x45, 0x42, 0x41, 0x4e, 0x55, + 0x4d, 0x4f, 0x73, 0x51, 0x71, 0x2b, 0x55, 0x37, 0x69, 0x39, 0x62, 0x34, + 0x5a, 0x6c, 0x31, 0x2b, 0x4f, 0x69, 0x46, 0x4f, 0x78, 0x48, 0x7a, 0x2f, + 0x4c, 0x7a, 0x35, 0x38, 0x67, 0x45, 0x32, 0x30, 0x70, 0x0a, 0x4f, 0x73, + 0x67, 0x50, 0x66, 0x54, 0x7a, 0x33, 0x61, 0x33, 0x59, 0x34, 0x59, 0x39, + 0x6b, 0x32, 0x59, 0x4b, 0x69, 0x62, 0x58, 0x6c, 0x77, 0x41, 0x67, 0x4c, + 0x49, 0x76, 0x57, 0x58, 0x2f, 0x32, 0x68, 0x2f, 0x6b, 0x6c, 0x51, 0x34, + 0x62, 0x6e, 0x61, 0x52, 0x74, 0x53, 0x6d, 0x70, 0x44, 0x68, 0x63, 0x65, + 0x50, 0x59, 0x4c, 0x51, 0x31, 0x4f, 0x62, 0x2f, 0x62, 0x49, 0x53, 0x64, + 0x6d, 0x32, 0x0a, 0x38, 0x78, 0x70, 0x57, 0x72, 0x69, 0x75, 0x32, 0x64, + 0x42, 0x54, 0x72, 0x7a, 0x2f, 0x73, 0x6d, 0x34, 0x78, 0x71, 0x36, 0x48, + 0x5a, 0x59, 0x75, 0x61, 0x6a, 0x74, 0x59, 0x6c, 0x49, 0x6c, 0x48, 0x56, + 0x76, 0x38, 0x6c, 0x6f, 0x4a, 0x4e, 0x77, 0x55, 0x34, 0x50, 0x61, 0x68, + 0x48, 0x51, 0x55, 0x77, 0x32, 0x65, 0x65, 0x42, 0x47, 0x67, 0x36, 0x33, + 0x34, 0x35, 0x41, 0x57, 0x68, 0x31, 0x4b, 0x0a, 0x54, 0x73, 0x39, 0x44, + 0x6b, 0x54, 0x76, 0x6e, 0x56, 0x74, 0x59, 0x41, 0x63, 0x4d, 0x74, 0x53, + 0x37, 0x6e, 0x74, 0x39, 0x72, 0x6a, 0x72, 0x6e, 0x76, 0x44, 0x48, 0x35, + 0x52, 0x66, 0x62, 0x43, 0x59, 0x4d, 0x38, 0x54, 0x57, 0x51, 0x49, 0x72, + 0x67, 0x4d, 0x77, 0x30, 0x52, 0x39, 0x2b, 0x35, 0x33, 0x70, 0x42, 0x6c, + 0x62, 0x51, 0x4c, 0x50, 0x4c, 0x4a, 0x47, 0x6d, 0x70, 0x75, 0x66, 0x65, + 0x0a, 0x68, 0x52, 0x68, 0x4a, 0x66, 0x47, 0x5a, 0x4f, 0x6f, 0x7a, 0x70, + 0x74, 0x71, 0x62, 0x58, 0x75, 0x4e, 0x43, 0x36, 0x36, 0x44, 0x51, 0x4f, + 0x34, 0x4d, 0x39, 0x39, 0x48, 0x36, 0x37, 0x46, 0x72, 0x6a, 0x53, 0x58, + 0x5a, 0x6d, 0x38, 0x36, 0x42, 0x30, 0x55, 0x56, 0x47, 0x4d, 0x70, 0x5a, + 0x77, 0x68, 0x39, 0x34, 0x43, 0x44, 0x6b, 0x6c, 0x44, 0x68, 0x62, 0x5a, + 0x73, 0x63, 0x37, 0x74, 0x6b, 0x0a, 0x36, 0x6d, 0x46, 0x42, 0x72, 0x4d, + 0x6e, 0x55, 0x56, 0x4e, 0x2b, 0x48, 0x4c, 0x38, 0x63, 0x69, 0x73, 0x69, + 0x62, 0x4d, 0x6e, 0x31, 0x6c, 0x55, 0x61, 0x4a, 0x2f, 0x38, 0x76, 0x69, + 0x6f, 0x76, 0x78, 0x46, 0x55, 0x63, 0x64, 0x55, 0x42, 0x67, 0x46, 0x34, + 0x55, 0x43, 0x56, 0x54, 0x6d, 0x4c, 0x66, 0x77, 0x55, 0x43, 0x41, 0x77, + 0x45, 0x41, 0x41, 0x61, 0x4e, 0x43, 0x4d, 0x45, 0x41, 0x77, 0x0a, 0x44, + 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, 0x54, 0x41, 0x51, 0x48, 0x2f, 0x42, + 0x41, 0x55, 0x77, 0x41, 0x77, 0x45, 0x42, 0x2f, 0x7a, 0x41, 0x4f, 0x42, + 0x67, 0x4e, 0x56, 0x48, 0x51, 0x38, 0x42, 0x41, 0x66, 0x38, 0x45, 0x42, + 0x41, 0x4d, 0x43, 0x41, 0x51, 0x59, 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, + 0x52, 0x30, 0x4f, 0x42, 0x42, 0x59, 0x45, 0x46, 0x4a, 0x78, 0x66, 0x41, + 0x4e, 0x2b, 0x71, 0x0a, 0x41, 0x64, 0x63, 0x77, 0x4b, 0x7a, 0x69, 0x49, + 0x6f, 0x72, 0x68, 0x74, 0x53, 0x70, 0x7a, 0x79, 0x45, 0x5a, 0x47, 0x44, + 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, + 0x44, 0x51, 0x45, 0x42, 0x43, 0x77, 0x55, 0x41, 0x41, 0x34, 0x49, 0x42, + 0x41, 0x51, 0x42, 0x4c, 0x4e, 0x71, 0x61, 0x45, 0x64, 0x32, 0x6e, 0x64, + 0x4f, 0x78, 0x6d, 0x66, 0x5a, 0x79, 0x4d, 0x49, 0x0a, 0x62, 0x77, 0x35, + 0x68, 0x79, 0x66, 0x32, 0x45, 0x33, 0x46, 0x2f, 0x59, 0x4e, 0x6f, 0x48, + 0x4e, 0x32, 0x42, 0x74, 0x42, 0x4c, 0x5a, 0x39, 0x67, 0x33, 0x63, 0x63, + 0x61, 0x61, 0x4e, 0x6e, 0x52, 0x62, 0x6f, 0x62, 0x68, 0x69, 0x43, 0x50, + 0x50, 0x45, 0x39, 0x35, 0x44, 0x7a, 0x2b, 0x49, 0x30, 0x73, 0x77, 0x53, + 0x64, 0x48, 0x79, 0x6e, 0x56, 0x76, 0x2f, 0x68, 0x65, 0x79, 0x4e, 0x58, + 0x42, 0x0a, 0x76, 0x65, 0x36, 0x53, 0x62, 0x7a, 0x4a, 0x30, 0x38, 0x70, + 0x47, 0x43, 0x4c, 0x37, 0x32, 0x43, 0x51, 0x6e, 0x71, 0x74, 0x4b, 0x72, + 0x63, 0x67, 0x66, 0x55, 0x32, 0x38, 0x65, 0x6c, 0x55, 0x53, 0x77, 0x68, + 0x58, 0x71, 0x76, 0x66, 0x64, 0x71, 0x6c, 0x53, 0x35, 0x73, 0x64, 0x4a, + 0x2f, 0x50, 0x48, 0x4c, 0x54, 0x79, 0x78, 0x51, 0x47, 0x6a, 0x68, 0x64, + 0x42, 0x79, 0x50, 0x71, 0x31, 0x7a, 0x0a, 0x71, 0x77, 0x75, 0x62, 0x64, + 0x51, 0x78, 0x74, 0x52, 0x62, 0x65, 0x4f, 0x6c, 0x4b, 0x79, 0x57, 0x4e, + 0x37, 0x57, 0x67, 0x30, 0x49, 0x38, 0x56, 0x52, 0x77, 0x37, 0x6a, 0x36, + 0x49, 0x50, 0x64, 0x6a, 0x2f, 0x33, 0x76, 0x51, 0x51, 0x46, 0x33, 0x7a, + 0x43, 0x65, 0x70, 0x59, 0x6f, 0x55, 0x7a, 0x38, 0x6a, 0x63, 0x49, 0x37, + 0x33, 0x48, 0x50, 0x64, 0x77, 0x62, 0x65, 0x79, 0x42, 0x6b, 0x64, 0x0a, + 0x69, 0x45, 0x44, 0x50, 0x66, 0x55, 0x59, 0x64, 0x2f, 0x78, 0x37, 0x48, + 0x34, 0x63, 0x37, 0x2f, 0x49, 0x39, 0x76, 0x47, 0x2b, 0x6f, 0x31, 0x56, + 0x54, 0x71, 0x6b, 0x43, 0x35, 0x30, 0x63, 0x52, 0x52, 0x6a, 0x37, 0x30, + 0x2f, 0x62, 0x31, 0x37, 0x4b, 0x53, 0x61, 0x37, 0x71, 0x57, 0x46, 0x69, + 0x4e, 0x79, 0x69, 0x32, 0x4c, 0x53, 0x72, 0x32, 0x45, 0x49, 0x5a, 0x6b, + 0x79, 0x58, 0x43, 0x6e, 0x0a, 0x30, 0x71, 0x32, 0x33, 0x4b, 0x58, 0x42, + 0x35, 0x36, 0x6a, 0x7a, 0x61, 0x59, 0x79, 0x57, 0x66, 0x2f, 0x57, 0x69, + 0x33, 0x4d, 0x4f, 0x78, 0x77, 0x2b, 0x33, 0x57, 0x4b, 0x74, 0x32, 0x31, + 0x67, 0x5a, 0x37, 0x49, 0x65, 0x79, 0x4c, 0x6e, 0x70, 0x32, 0x4b, 0x68, + 0x76, 0x41, 0x6f, 0x74, 0x6e, 0x44, 0x55, 0x30, 0x6d, 0x56, 0x33, 0x48, + 0x61, 0x49, 0x50, 0x7a, 0x42, 0x53, 0x6c, 0x43, 0x4e, 0x0a, 0x73, 0x53, + 0x69, 0x36, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, + 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, + 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x41, 0x66, 0x66, 0x69, 0x72, + 0x6d, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x43, 0x6f, 0x6d, 0x6d, 0x65, + 0x72, 0x63, 0x69, 0x61, 0x6c, 0x20, 0x4f, 0x3d, 0x41, 0x66, 0x66, 0x69, + 0x72, 0x6d, 0x54, 0x72, 0x75, 0x73, 0x74, 0x0a, 0x23, 0x20, 0x53, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x41, 0x66, + 0x66, 0x69, 0x72, 0x6d, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x43, 0x6f, + 0x6d, 0x6d, 0x65, 0x72, 0x63, 0x69, 0x61, 0x6c, 0x20, 0x4f, 0x3d, 0x41, + 0x66, 0x66, 0x69, 0x72, 0x6d, 0x54, 0x72, 0x75, 0x73, 0x74, 0x0a, 0x23, + 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x41, 0x66, 0x66, + 0x69, 0x72, 0x6d, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x43, 0x6f, 0x6d, + 0x6d, 0x65, 0x72, 0x63, 0x69, 0x61, 0x6c, 0x22, 0x0a, 0x23, 0x20, 0x53, + 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x38, 0x36, 0x30, 0x38, 0x33, + 0x35, 0x35, 0x39, 0x37, 0x37, 0x39, 0x36, 0x34, 0x31, 0x33, 0x38, 0x38, + 0x37, 0x36, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, + 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x38, 0x32, + 0x3a, 0x39, 0x32, 0x3a, 0x62, 0x61, 0x3a, 0x35, 0x62, 0x3a, 0x65, 0x66, + 0x3a, 0x63, 0x64, 0x3a, 0x38, 0x61, 0x3a, 0x36, 0x66, 0x3a, 0x61, 0x36, + 0x3a, 0x33, 0x64, 0x3a, 0x35, 0x35, 0x3a, 0x66, 0x39, 0x3a, 0x38, 0x34, + 0x3a, 0x66, 0x36, 0x3a, 0x64, 0x36, 0x3a, 0x62, 0x37, 0x0a, 0x23, 0x20, + 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, + 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x66, 0x39, 0x3a, 0x62, 0x35, 0x3a, + 0x62, 0x36, 0x3a, 0x33, 0x32, 0x3a, 0x34, 0x35, 0x3a, 0x35, 0x66, 0x3a, + 0x39, 0x63, 0x3a, 0x62, 0x65, 0x3a, 0x65, 0x63, 0x3a, 0x35, 0x37, 0x3a, + 0x35, 0x66, 0x3a, 0x38, 0x30, 0x3a, 0x64, 0x63, 0x3a, 0x65, 0x39, 0x3a, + 0x36, 0x65, 0x3a, 0x32, 0x63, 0x3a, 0x63, 0x37, 0x3a, 0x62, 0x32, 0x3a, + 0x37, 0x38, 0x3a, 0x62, 0x37, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, + 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x3a, 0x20, 0x30, 0x33, 0x3a, 0x37, 0x36, 0x3a, 0x61, 0x62, + 0x3a, 0x31, 0x64, 0x3a, 0x35, 0x34, 0x3a, 0x63, 0x35, 0x3a, 0x66, 0x39, + 0x3a, 0x38, 0x30, 0x3a, 0x33, 0x63, 0x3a, 0x65, 0x34, 0x3a, 0x62, 0x32, + 0x3a, 0x65, 0x32, 0x3a, 0x30, 0x31, 0x3a, 0x61, 0x30, 0x3a, 0x65, 0x65, + 0x3a, 0x37, 0x65, 0x3a, 0x65, 0x66, 0x3a, 0x37, 0x62, 0x3a, 0x35, 0x37, + 0x3a, 0x62, 0x36, 0x3a, 0x33, 0x36, 0x3a, 0x65, 0x38, 0x3a, 0x61, 0x39, + 0x3a, 0x33, 0x63, 0x3a, 0x39, 0x62, 0x3a, 0x38, 0x64, 0x3a, 0x34, 0x38, + 0x3a, 0x36, 0x30, 0x3a, 0x63, 0x39, 0x3a, 0x36, 0x66, 0x3a, 0x35, 0x66, + 0x3a, 0x61, 0x37, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, + 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, + 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x44, + 0x54, 0x44, 0x43, 0x43, 0x41, 0x6a, 0x53, 0x67, 0x41, 0x77, 0x49, 0x42, + 0x41, 0x67, 0x49, 0x49, 0x64, 0x33, 0x63, 0x47, 0x4a, 0x79, 0x61, 0x70, + 0x73, 0x58, 0x77, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, + 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x4c, 0x42, 0x51, 0x41, 0x77, + 0x52, 0x44, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x0a, 0x42, 0x68, 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, 0x46, 0x44, 0x41, + 0x53, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x4d, 0x43, 0x30, 0x46, + 0x6d, 0x5a, 0x6d, 0x6c, 0x79, 0x62, 0x56, 0x52, 0x79, 0x64, 0x58, 0x4e, + 0x30, 0x4d, 0x52, 0x38, 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, + 0x44, 0x44, 0x42, 0x5a, 0x42, 0x5a, 0x6d, 0x5a, 0x70, 0x63, 0x6d, 0x31, + 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x0a, 0x64, 0x43, 0x42, 0x44, 0x62, 0x32, + 0x31, 0x74, 0x5a, 0x58, 0x4a, 0x6a, 0x61, 0x57, 0x46, 0x73, 0x4d, 0x42, + 0x34, 0x58, 0x44, 0x54, 0x45, 0x77, 0x4d, 0x44, 0x45, 0x79, 0x4f, 0x54, + 0x45, 0x30, 0x4d, 0x44, 0x59, 0x77, 0x4e, 0x6c, 0x6f, 0x58, 0x44, 0x54, + 0x4d, 0x77, 0x4d, 0x54, 0x49, 0x7a, 0x4d, 0x54, 0x45, 0x30, 0x4d, 0x44, + 0x59, 0x77, 0x4e, 0x6c, 0x6f, 0x77, 0x52, 0x44, 0x45, 0x4c, 0x0a, 0x4d, + 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x56, + 0x56, 0x4d, 0x78, 0x46, 0x44, 0x41, 0x53, 0x42, 0x67, 0x4e, 0x56, 0x42, + 0x41, 0x6f, 0x4d, 0x43, 0x30, 0x46, 0x6d, 0x5a, 0x6d, 0x6c, 0x79, 0x62, + 0x56, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x4d, 0x52, 0x38, 0x77, 0x48, + 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, 0x44, 0x42, 0x5a, 0x42, 0x5a, + 0x6d, 0x5a, 0x70, 0x0a, 0x63, 0x6d, 0x31, 0x55, 0x63, 0x6e, 0x56, 0x7a, + 0x64, 0x43, 0x42, 0x44, 0x62, 0x32, 0x31, 0x74, 0x5a, 0x58, 0x4a, 0x6a, + 0x61, 0x57, 0x46, 0x73, 0x4d, 0x49, 0x49, 0x42, 0x49, 0x6a, 0x41, 0x4e, + 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, + 0x41, 0x51, 0x45, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x51, 0x38, 0x41, + 0x4d, 0x49, 0x49, 0x42, 0x43, 0x67, 0x4b, 0x43, 0x0a, 0x41, 0x51, 0x45, + 0x41, 0x39, 0x68, 0x74, 0x50, 0x5a, 0x77, 0x63, 0x72, 0x6f, 0x52, 0x58, + 0x31, 0x42, 0x69, 0x4c, 0x4c, 0x48, 0x77, 0x47, 0x79, 0x34, 0x33, 0x4e, + 0x46, 0x42, 0x6b, 0x52, 0x4a, 0x4c, 0x4c, 0x74, 0x4a, 0x4a, 0x52, 0x54, + 0x57, 0x7a, 0x73, 0x4f, 0x33, 0x71, 0x79, 0x78, 0x50, 0x78, 0x6b, 0x45, + 0x79, 0x6c, 0x46, 0x66, 0x36, 0x45, 0x71, 0x64, 0x62, 0x44, 0x75, 0x4b, + 0x50, 0x0a, 0x48, 0x78, 0x36, 0x47, 0x47, 0x61, 0x65, 0x71, 0x74, 0x53, + 0x32, 0x35, 0x58, 0x77, 0x32, 0x4b, 0x77, 0x71, 0x2b, 0x46, 0x4e, 0x58, + 0x6b, 0x79, 0x4c, 0x62, 0x73, 0x63, 0x59, 0x6a, 0x66, 0x79, 0x73, 0x56, + 0x74, 0x4b, 0x50, 0x63, 0x72, 0x4e, 0x63, 0x56, 0x2f, 0x70, 0x51, 0x72, + 0x36, 0x55, 0x36, 0x4d, 0x6a, 0x65, 0x2b, 0x53, 0x4a, 0x49, 0x5a, 0x4d, + 0x62, 0x6c, 0x71, 0x38, 0x59, 0x72, 0x0a, 0x62, 0x61, 0x30, 0x46, 0x38, + 0x50, 0x72, 0x56, 0x43, 0x38, 0x2b, 0x61, 0x35, 0x66, 0x42, 0x51, 0x70, + 0x49, 0x73, 0x37, 0x52, 0x36, 0x55, 0x6a, 0x57, 0x33, 0x70, 0x36, 0x2b, + 0x44, 0x4d, 0x2f, 0x75, 0x4f, 0x2b, 0x5a, 0x6c, 0x2b, 0x4d, 0x67, 0x77, + 0x64, 0x59, 0x6f, 0x69, 0x63, 0x2b, 0x55, 0x2b, 0x37, 0x6c, 0x46, 0x37, + 0x65, 0x4e, 0x41, 0x46, 0x78, 0x48, 0x55, 0x64, 0x50, 0x41, 0x4c, 0x0a, + 0x4d, 0x65, 0x49, 0x72, 0x4a, 0x6d, 0x71, 0x62, 0x54, 0x46, 0x65, 0x75, + 0x72, 0x43, 0x41, 0x2b, 0x75, 0x6b, 0x56, 0x36, 0x42, 0x66, 0x4f, 0x39, + 0x6d, 0x32, 0x6b, 0x56, 0x72, 0x6e, 0x31, 0x4f, 0x49, 0x47, 0x50, 0x45, + 0x4e, 0x58, 0x59, 0x36, 0x42, 0x77, 0x4c, 0x4a, 0x4e, 0x2f, 0x33, 0x48, + 0x52, 0x2b, 0x37, 0x6f, 0x38, 0x58, 0x59, 0x64, 0x63, 0x78, 0x58, 0x79, + 0x6c, 0x36, 0x53, 0x31, 0x0a, 0x79, 0x48, 0x70, 0x35, 0x32, 0x55, 0x4b, + 0x71, 0x4b, 0x33, 0x39, 0x63, 0x2f, 0x73, 0x34, 0x6d, 0x54, 0x36, 0x4e, + 0x6d, 0x67, 0x54, 0x57, 0x76, 0x52, 0x4c, 0x70, 0x55, 0x48, 0x68, 0x77, + 0x77, 0x4d, 0x6d, 0x57, 0x64, 0x35, 0x6a, 0x79, 0x54, 0x58, 0x6c, 0x42, + 0x4f, 0x65, 0x75, 0x4d, 0x36, 0x31, 0x47, 0x37, 0x4d, 0x47, 0x76, 0x76, + 0x35, 0x30, 0x6a, 0x65, 0x75, 0x4a, 0x43, 0x71, 0x72, 0x0a, 0x56, 0x77, + 0x4d, 0x69, 0x4b, 0x41, 0x31, 0x4a, 0x64, 0x58, 0x2b, 0x33, 0x4b, 0x4e, + 0x70, 0x31, 0x76, 0x34, 0x37, 0x6a, 0x33, 0x41, 0x35, 0x35, 0x4d, 0x51, + 0x49, 0x44, 0x41, 0x51, 0x41, 0x42, 0x6f, 0x30, 0x49, 0x77, 0x51, 0x44, + 0x41, 0x64, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x34, 0x45, 0x46, 0x67, + 0x51, 0x55, 0x6e, 0x5a, 0x50, 0x47, 0x55, 0x34, 0x74, 0x65, 0x79, 0x71, + 0x38, 0x2f, 0x0a, 0x6e, 0x78, 0x34, 0x50, 0x35, 0x5a, 0x6d, 0x56, 0x76, + 0x43, 0x54, 0x32, 0x6c, 0x49, 0x38, 0x77, 0x44, 0x77, 0x59, 0x44, 0x56, + 0x52, 0x30, 0x54, 0x41, 0x51, 0x48, 0x2f, 0x42, 0x41, 0x55, 0x77, 0x41, + 0x77, 0x45, 0x42, 0x2f, 0x7a, 0x41, 0x4f, 0x42, 0x67, 0x4e, 0x56, 0x48, + 0x51, 0x38, 0x42, 0x41, 0x66, 0x38, 0x45, 0x42, 0x41, 0x4d, 0x43, 0x41, + 0x51, 0x59, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x0a, 0x4b, 0x6f, 0x5a, 0x49, + 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x4c, 0x42, 0x51, 0x41, 0x44, + 0x67, 0x67, 0x45, 0x42, 0x41, 0x46, 0x69, 0x73, 0x39, 0x41, 0x51, 0x4f, + 0x7a, 0x63, 0x41, 0x4e, 0x2f, 0x77, 0x72, 0x39, 0x31, 0x4c, 0x6f, 0x57, + 0x58, 0x79, 0x6d, 0x39, 0x65, 0x32, 0x69, 0x5a, 0x57, 0x45, 0x6e, 0x53, + 0x74, 0x42, 0x30, 0x33, 0x54, 0x58, 0x38, 0x6e, 0x66, 0x55, 0x59, 0x47, + 0x0a, 0x58, 0x55, 0x50, 0x47, 0x68, 0x69, 0x34, 0x2b, 0x63, 0x37, 0x49, + 0x6d, 0x66, 0x55, 0x2b, 0x54, 0x71, 0x62, 0x62, 0x45, 0x4b, 0x70, 0x71, + 0x72, 0x49, 0x5a, 0x63, 0x55, 0x73, 0x64, 0x36, 0x4d, 0x30, 0x36, 0x75, + 0x4a, 0x46, 0x64, 0x68, 0x72, 0x4a, 0x4e, 0x54, 0x78, 0x46, 0x71, 0x37, + 0x59, 0x70, 0x46, 0x7a, 0x55, 0x66, 0x31, 0x47, 0x4f, 0x37, 0x52, 0x67, + 0x42, 0x73, 0x5a, 0x4e, 0x6a, 0x0a, 0x76, 0x62, 0x7a, 0x34, 0x59, 0x59, + 0x43, 0x61, 0x6e, 0x72, 0x48, 0x4f, 0x51, 0x6e, 0x44, 0x69, 0x71, 0x58, + 0x30, 0x47, 0x4a, 0x58, 0x30, 0x6e, 0x6f, 0x66, 0x35, 0x76, 0x37, 0x4c, + 0x4d, 0x65, 0x4a, 0x4e, 0x72, 0x6a, 0x53, 0x31, 0x55, 0x61, 0x41, 0x44, + 0x73, 0x31, 0x74, 0x44, 0x76, 0x5a, 0x31, 0x31, 0x30, 0x77, 0x2f, 0x59, + 0x45, 0x54, 0x69, 0x66, 0x4c, 0x43, 0x42, 0x69, 0x76, 0x74, 0x0a, 0x5a, + 0x38, 0x53, 0x4f, 0x79, 0x55, 0x4f, 0x79, 0x58, 0x47, 0x73, 0x56, 0x69, + 0x51, 0x4b, 0x38, 0x59, 0x76, 0x78, 0x4f, 0x38, 0x72, 0x55, 0x7a, 0x71, + 0x72, 0x4a, 0x76, 0x30, 0x77, 0x71, 0x69, 0x55, 0x4f, 0x50, 0x32, 0x4f, + 0x2b, 0x67, 0x75, 0x52, 0x4d, 0x4c, 0x62, 0x5a, 0x6a, 0x69, 0x70, 0x4d, + 0x31, 0x5a, 0x49, 0x38, 0x57, 0x30, 0x62, 0x4d, 0x34, 0x30, 0x4e, 0x6a, + 0x44, 0x39, 0x67, 0x0a, 0x4e, 0x35, 0x33, 0x54, 0x79, 0x6d, 0x31, 0x2b, + 0x4e, 0x48, 0x34, 0x4e, 0x6e, 0x33, 0x4a, 0x32, 0x69, 0x78, 0x75, 0x66, + 0x63, 0x76, 0x31, 0x53, 0x4e, 0x55, 0x46, 0x46, 0x41, 0x70, 0x59, 0x76, + 0x48, 0x4c, 0x4b, 0x61, 0x63, 0x30, 0x6b, 0x68, 0x73, 0x55, 0x6c, 0x48, + 0x52, 0x55, 0x65, 0x30, 0x37, 0x32, 0x6f, 0x30, 0x45, 0x63, 0x6c, 0x4e, + 0x6d, 0x73, 0x78, 0x5a, 0x74, 0x39, 0x59, 0x43, 0x0a, 0x6e, 0x6c, 0x70, + 0x4f, 0x5a, 0x62, 0x57, 0x55, 0x72, 0x68, 0x76, 0x66, 0x4b, 0x62, 0x41, + 0x57, 0x38, 0x62, 0x38, 0x41, 0x6e, 0x67, 0x63, 0x36, 0x46, 0x32, 0x53, + 0x31, 0x42, 0x4c, 0x55, 0x6a, 0x49, 0x5a, 0x6b, 0x4b, 0x6c, 0x54, 0x75, + 0x58, 0x66, 0x4f, 0x38, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, + 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, + 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, + 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x41, 0x66, + 0x66, 0x69, 0x72, 0x6d, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, + 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x20, 0x4f, 0x3d, 0x41, + 0x66, 0x66, 0x69, 0x72, 0x6d, 0x54, 0x72, 0x75, 0x73, 0x74, 0x0a, 0x23, + 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, + 0x3d, 0x41, 0x66, 0x66, 0x69, 0x72, 0x6d, 0x54, 0x72, 0x75, 0x73, 0x74, + 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x20, + 0x4f, 0x3d, 0x41, 0x66, 0x66, 0x69, 0x72, 0x6d, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, + 0x41, 0x66, 0x66, 0x69, 0x72, 0x6d, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, + 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x22, 0x0a, + 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x38, 0x39, + 0x35, 0x37, 0x33, 0x38, 0x32, 0x38, 0x32, 0x37, 0x32, 0x30, 0x36, 0x35, + 0x34, 0x37, 0x37, 0x35, 0x37, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, + 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, + 0x20, 0x34, 0x32, 0x3a, 0x36, 0x35, 0x3a, 0x63, 0x61, 0x3a, 0x62, 0x65, + 0x3a, 0x30, 0x31, 0x3a, 0x39, 0x61, 0x3a, 0x39, 0x61, 0x3a, 0x34, 0x63, + 0x3a, 0x61, 0x39, 0x3a, 0x38, 0x63, 0x3a, 0x34, 0x31, 0x3a, 0x34, 0x39, + 0x3a, 0x63, 0x64, 0x3a, 0x63, 0x30, 0x3a, 0x64, 0x35, 0x3a, 0x37, 0x66, + 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, + 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x32, 0x39, 0x3a, + 0x33, 0x36, 0x3a, 0x32, 0x31, 0x3a, 0x30, 0x32, 0x3a, 0x38, 0x62, 0x3a, + 0x32, 0x30, 0x3a, 0x65, 0x64, 0x3a, 0x30, 0x32, 0x3a, 0x66, 0x35, 0x3a, + 0x36, 0x36, 0x3a, 0x63, 0x35, 0x3a, 0x33, 0x32, 0x3a, 0x64, 0x31, 0x3a, + 0x64, 0x36, 0x3a, 0x65, 0x64, 0x3a, 0x39, 0x30, 0x3a, 0x39, 0x66, 0x3a, + 0x34, 0x35, 0x3a, 0x30, 0x30, 0x3a, 0x32, 0x66, 0x0a, 0x23, 0x20, 0x53, + 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x30, 0x61, 0x3a, 0x38, 0x31, + 0x3a, 0x65, 0x63, 0x3a, 0x35, 0x61, 0x3a, 0x39, 0x32, 0x3a, 0x39, 0x37, + 0x3a, 0x37, 0x37, 0x3a, 0x66, 0x31, 0x3a, 0x34, 0x35, 0x3a, 0x39, 0x30, + 0x3a, 0x34, 0x61, 0x3a, 0x66, 0x33, 0x3a, 0x38, 0x64, 0x3a, 0x35, 0x64, + 0x3a, 0x35, 0x30, 0x3a, 0x39, 0x66, 0x3a, 0x36, 0x36, 0x3a, 0x62, 0x35, + 0x3a, 0x65, 0x32, 0x3a, 0x63, 0x35, 0x3a, 0x38, 0x66, 0x3a, 0x63, 0x64, + 0x3a, 0x62, 0x35, 0x3a, 0x33, 0x31, 0x3a, 0x30, 0x35, 0x3a, 0x38, 0x62, + 0x3a, 0x30, 0x65, 0x3a, 0x31, 0x37, 0x3a, 0x66, 0x33, 0x3a, 0x66, 0x30, + 0x3a, 0x62, 0x34, 0x3a, 0x31, 0x62, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, + 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, + 0x49, 0x49, 0x44, 0x54, 0x44, 0x43, 0x43, 0x41, 0x6a, 0x53, 0x67, 0x41, + 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x49, 0x66, 0x45, 0x38, 0x45, 0x4f, + 0x52, 0x7a, 0x55, 0x6d, 0x53, 0x30, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, + 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x46, 0x42, + 0x51, 0x41, 0x77, 0x52, 0x44, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, + 0x31, 0x55, 0x45, 0x0a, 0x42, 0x68, 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, + 0x46, 0x44, 0x41, 0x53, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x4d, + 0x43, 0x30, 0x46, 0x6d, 0x5a, 0x6d, 0x6c, 0x79, 0x62, 0x56, 0x52, 0x79, + 0x64, 0x58, 0x4e, 0x30, 0x4d, 0x52, 0x38, 0x77, 0x48, 0x51, 0x59, 0x44, + 0x56, 0x51, 0x51, 0x44, 0x44, 0x42, 0x5a, 0x42, 0x5a, 0x6d, 0x5a, 0x70, + 0x63, 0x6d, 0x31, 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x0a, 0x64, 0x43, 0x42, + 0x4f, 0x5a, 0x58, 0x52, 0x33, 0x62, 0x33, 0x4a, 0x72, 0x61, 0x57, 0x35, + 0x6e, 0x4d, 0x42, 0x34, 0x58, 0x44, 0x54, 0x45, 0x77, 0x4d, 0x44, 0x45, + 0x79, 0x4f, 0x54, 0x45, 0x30, 0x4d, 0x44, 0x67, 0x79, 0x4e, 0x46, 0x6f, + 0x58, 0x44, 0x54, 0x4d, 0x77, 0x4d, 0x54, 0x49, 0x7a, 0x4d, 0x54, 0x45, + 0x30, 0x4d, 0x44, 0x67, 0x79, 0x4e, 0x46, 0x6f, 0x77, 0x52, 0x44, 0x45, + 0x4c, 0x0a, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x42, 0x68, + 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, 0x46, 0x44, 0x41, 0x53, 0x42, 0x67, + 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x4d, 0x43, 0x30, 0x46, 0x6d, 0x5a, 0x6d, + 0x6c, 0x79, 0x62, 0x56, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x4d, 0x52, + 0x38, 0x77, 0x48, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, 0x44, 0x42, + 0x5a, 0x42, 0x5a, 0x6d, 0x5a, 0x70, 0x0a, 0x63, 0x6d, 0x31, 0x55, 0x63, + 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x4f, 0x5a, 0x58, 0x52, 0x33, 0x62, + 0x33, 0x4a, 0x72, 0x61, 0x57, 0x35, 0x6e, 0x4d, 0x49, 0x49, 0x42, 0x49, + 0x6a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, + 0x77, 0x30, 0x42, 0x41, 0x51, 0x45, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, + 0x51, 0x38, 0x41, 0x4d, 0x49, 0x49, 0x42, 0x43, 0x67, 0x4b, 0x43, 0x0a, + 0x41, 0x51, 0x45, 0x41, 0x74, 0x49, 0x54, 0x4d, 0x4d, 0x78, 0x63, 0x75, + 0x61, 0x35, 0x52, 0x73, 0x61, 0x32, 0x46, 0x53, 0x6f, 0x4f, 0x75, 0x6a, + 0x7a, 0x33, 0x6d, 0x55, 0x54, 0x4f, 0x57, 0x55, 0x67, 0x4a, 0x6e, 0x4c, + 0x56, 0x57, 0x52, 0x45, 0x5a, 0x59, 0x39, 0x6e, 0x5a, 0x4f, 0x49, 0x47, + 0x34, 0x31, 0x77, 0x33, 0x53, 0x66, 0x59, 0x76, 0x6d, 0x34, 0x53, 0x45, + 0x48, 0x69, 0x33, 0x79, 0x0a, 0x59, 0x4a, 0x30, 0x77, 0x54, 0x73, 0x79, + 0x45, 0x68, 0x65, 0x49, 0x73, 0x7a, 0x78, 0x36, 0x65, 0x2f, 0x6a, 0x61, + 0x72, 0x4d, 0x33, 0x63, 0x31, 0x52, 0x4e, 0x67, 0x31, 0x6c, 0x68, 0x6f, + 0x39, 0x4e, 0x75, 0x68, 0x36, 0x44, 0x74, 0x6a, 0x56, 0x52, 0x36, 0x46, + 0x71, 0x61, 0x59, 0x76, 0x5a, 0x2f, 0x4c, 0x73, 0x36, 0x72, 0x6e, 0x6c, + 0x61, 0x31, 0x66, 0x54, 0x57, 0x63, 0x62, 0x75, 0x61, 0x0a, 0x6b, 0x43, + 0x4e, 0x72, 0x6d, 0x72, 0x65, 0x49, 0x64, 0x49, 0x63, 0x4d, 0x48, 0x6c, + 0x2b, 0x35, 0x6e, 0x69, 0x33, 0x36, 0x71, 0x31, 0x4d, 0x72, 0x33, 0x4c, + 0x74, 0x32, 0x50, 0x70, 0x4e, 0x4d, 0x43, 0x41, 0x69, 0x4d, 0x48, 0x71, + 0x49, 0x6a, 0x48, 0x4e, 0x52, 0x71, 0x72, 0x53, 0x4b, 0x36, 0x6d, 0x51, + 0x45, 0x75, 0x62, 0x57, 0x58, 0x4c, 0x76, 0x69, 0x52, 0x6d, 0x56, 0x53, + 0x52, 0x4c, 0x0a, 0x51, 0x45, 0x53, 0x78, 0x47, 0x39, 0x66, 0x68, 0x77, + 0x6f, 0x58, 0x41, 0x33, 0x68, 0x41, 0x2f, 0x50, 0x65, 0x32, 0x34, 0x2f, + 0x50, 0x48, 0x78, 0x49, 0x31, 0x50, 0x63, 0x76, 0x32, 0x57, 0x58, 0x62, + 0x39, 0x6e, 0x35, 0x51, 0x48, 0x47, 0x4e, 0x66, 0x62, 0x32, 0x56, 0x31, + 0x4d, 0x36, 0x2b, 0x6f, 0x46, 0x34, 0x6e, 0x49, 0x39, 0x37, 0x39, 0x70, + 0x74, 0x41, 0x6d, 0x44, 0x67, 0x41, 0x70, 0x0a, 0x36, 0x7a, 0x78, 0x47, + 0x38, 0x44, 0x31, 0x67, 0x76, 0x7a, 0x39, 0x51, 0x30, 0x74, 0x77, 0x6d, + 0x51, 0x56, 0x47, 0x65, 0x46, 0x44, 0x64, 0x43, 0x42, 0x4b, 0x4e, 0x77, + 0x56, 0x36, 0x67, 0x62, 0x68, 0x2b, 0x30, 0x74, 0x2b, 0x6e, 0x76, 0x75, + 0x6a, 0x41, 0x72, 0x6a, 0x71, 0x57, 0x61, 0x4a, 0x47, 0x63, 0x74, 0x42, + 0x2b, 0x64, 0x31, 0x45, 0x4e, 0x6d, 0x48, 0x50, 0x34, 0x6e, 0x64, 0x47, + 0x0a, 0x79, 0x48, 0x33, 0x32, 0x39, 0x4a, 0x4b, 0x42, 0x4e, 0x76, 0x33, + 0x62, 0x4e, 0x50, 0x46, 0x79, 0x66, 0x76, 0x4d, 0x4d, 0x46, 0x72, 0x32, + 0x30, 0x46, 0x51, 0x49, 0x44, 0x41, 0x51, 0x41, 0x42, 0x6f, 0x30, 0x49, + 0x77, 0x51, 0x44, 0x41, 0x64, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x34, + 0x45, 0x46, 0x67, 0x51, 0x55, 0x42, 0x78, 0x2f, 0x53, 0x35, 0x35, 0x7a, + 0x61, 0x77, 0x6d, 0x36, 0x69, 0x0a, 0x51, 0x4c, 0x53, 0x77, 0x65, 0x6c, + 0x41, 0x51, 0x55, 0x48, 0x54, 0x45, 0x79, 0x4c, 0x30, 0x77, 0x44, 0x77, + 0x59, 0x44, 0x56, 0x52, 0x30, 0x54, 0x41, 0x51, 0x48, 0x2f, 0x42, 0x41, + 0x55, 0x77, 0x41, 0x77, 0x45, 0x42, 0x2f, 0x7a, 0x41, 0x4f, 0x42, 0x67, + 0x4e, 0x56, 0x48, 0x51, 0x38, 0x42, 0x41, 0x66, 0x38, 0x45, 0x42, 0x41, + 0x4d, 0x43, 0x41, 0x51, 0x59, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x0a, 0x4b, + 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x46, 0x42, + 0x51, 0x41, 0x44, 0x67, 0x67, 0x45, 0x42, 0x41, 0x49, 0x6c, 0x58, 0x73, + 0x68, 0x5a, 0x36, 0x71, 0x4d, 0x4c, 0x39, 0x31, 0x74, 0x6d, 0x62, 0x6d, + 0x7a, 0x54, 0x43, 0x6e, 0x4c, 0x51, 0x79, 0x46, 0x45, 0x32, 0x6e, 0x70, + 0x4e, 0x2f, 0x73, 0x76, 0x71, 0x65, 0x2b, 0x2b, 0x45, 0x50, 0x62, 0x6b, + 0x54, 0x66, 0x4f, 0x0a, 0x74, 0x44, 0x49, 0x75, 0x55, 0x46, 0x55, 0x61, + 0x4e, 0x55, 0x35, 0x32, 0x51, 0x33, 0x45, 0x67, 0x37, 0x35, 0x4e, 0x33, + 0x54, 0x68, 0x56, 0x77, 0x4c, 0x6f, 0x66, 0x44, 0x77, 0x52, 0x31, 0x74, + 0x33, 0x4d, 0x75, 0x31, 0x4a, 0x39, 0x51, 0x73, 0x56, 0x74, 0x46, 0x53, + 0x55, 0x7a, 0x70, 0x45, 0x30, 0x6e, 0x50, 0x49, 0x78, 0x42, 0x73, 0x46, + 0x5a, 0x56, 0x70, 0x69, 0x6b, 0x70, 0x7a, 0x75, 0x0a, 0x51, 0x59, 0x30, + 0x78, 0x32, 0x2b, 0x63, 0x30, 0x36, 0x6c, 0x6b, 0x68, 0x31, 0x51, 0x46, + 0x36, 0x31, 0x32, 0x53, 0x34, 0x5a, 0x44, 0x6e, 0x4e, 0x79, 0x65, 0x32, + 0x76, 0x37, 0x55, 0x73, 0x44, 0x53, 0x4b, 0x65, 0x67, 0x6d, 0x51, 0x47, + 0x41, 0x33, 0x47, 0x57, 0x6a, 0x4e, 0x71, 0x35, 0x6c, 0x57, 0x55, 0x68, + 0x50, 0x67, 0x6b, 0x76, 0x49, 0x5a, 0x66, 0x46, 0x58, 0x48, 0x65, 0x56, + 0x5a, 0x0a, 0x4c, 0x67, 0x6f, 0x2f, 0x62, 0x4e, 0x6a, 0x52, 0x39, 0x65, + 0x55, 0x4a, 0x74, 0x47, 0x78, 0x55, 0x41, 0x41, 0x72, 0x67, 0x46, 0x55, + 0x32, 0x48, 0x64, 0x57, 0x32, 0x33, 0x57, 0x4a, 0x5a, 0x61, 0x33, 0x57, + 0x33, 0x53, 0x41, 0x4b, 0x44, 0x30, 0x6d, 0x30, 0x69, 0x2b, 0x77, 0x7a, + 0x65, 0x6b, 0x75, 0x6a, 0x62, 0x67, 0x66, 0x49, 0x65, 0x46, 0x6c, 0x78, + 0x6f, 0x56, 0x6f, 0x74, 0x34, 0x75, 0x0a, 0x6f, 0x6c, 0x75, 0x39, 0x72, + 0x78, 0x6a, 0x35, 0x6b, 0x46, 0x44, 0x4e, 0x63, 0x46, 0x6e, 0x34, 0x4a, + 0x32, 0x64, 0x48, 0x79, 0x38, 0x65, 0x67, 0x42, 0x7a, 0x70, 0x39, 0x30, + 0x53, 0x78, 0x64, 0x62, 0x42, 0x6b, 0x36, 0x5a, 0x72, 0x56, 0x39, 0x2f, + 0x5a, 0x46, 0x76, 0x67, 0x72, 0x47, 0x2b, 0x43, 0x4a, 0x50, 0x62, 0x46, + 0x45, 0x66, 0x78, 0x6f, 0x6a, 0x66, 0x48, 0x52, 0x5a, 0x34, 0x38, 0x0a, + 0x78, 0x33, 0x65, 0x76, 0x5a, 0x4b, 0x69, 0x54, 0x33, 0x2f, 0x5a, 0x70, + 0x67, 0x34, 0x4a, 0x67, 0x38, 0x6b, 0x6c, 0x43, 0x4e, 0x4f, 0x31, 0x61, + 0x41, 0x46, 0x53, 0x46, 0x48, 0x42, 0x59, 0x32, 0x6b, 0x67, 0x78, 0x63, + 0x2b, 0x71, 0x61, 0x74, 0x76, 0x39, 0x73, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, + 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, + 0x23, 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, + 0x3d, 0x41, 0x66, 0x66, 0x69, 0x72, 0x6d, 0x54, 0x72, 0x75, 0x73, 0x74, + 0x20, 0x50, 0x72, 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x20, 0x4f, 0x3d, 0x41, + 0x66, 0x66, 0x69, 0x72, 0x6d, 0x54, 0x72, 0x75, 0x73, 0x74, 0x0a, 0x23, + 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, 0x4e, + 0x3d, 0x41, 0x66, 0x66, 0x69, 0x72, 0x6d, 0x54, 0x72, 0x75, 0x73, 0x74, + 0x20, 0x50, 0x72, 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x20, 0x4f, 0x3d, 0x41, + 0x66, 0x66, 0x69, 0x72, 0x6d, 0x54, 0x72, 0x75, 0x73, 0x74, 0x0a, 0x23, + 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x41, 0x66, 0x66, + 0x69, 0x72, 0x6d, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x50, 0x72, 0x65, + 0x6d, 0x69, 0x75, 0x6d, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, + 0x61, 0x6c, 0x3a, 0x20, 0x37, 0x38, 0x39, 0x33, 0x37, 0x30, 0x36, 0x35, + 0x34, 0x30, 0x37, 0x33, 0x34, 0x33, 0x35, 0x32, 0x31, 0x31, 0x30, 0x0a, + 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x63, 0x34, 0x3a, 0x35, 0x64, + 0x3a, 0x30, 0x65, 0x3a, 0x34, 0x38, 0x3a, 0x62, 0x36, 0x3a, 0x61, 0x63, + 0x3a, 0x32, 0x38, 0x3a, 0x33, 0x30, 0x3a, 0x34, 0x65, 0x3a, 0x30, 0x61, + 0x3a, 0x62, 0x63, 0x3a, 0x66, 0x39, 0x3a, 0x33, 0x38, 0x3a, 0x31, 0x36, + 0x3a, 0x38, 0x37, 0x3a, 0x35, 0x37, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, + 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, + 0x74, 0x3a, 0x20, 0x64, 0x38, 0x3a, 0x61, 0x36, 0x3a, 0x33, 0x33, 0x3a, + 0x32, 0x63, 0x3a, 0x65, 0x30, 0x3a, 0x30, 0x33, 0x3a, 0x36, 0x66, 0x3a, + 0x62, 0x31, 0x3a, 0x38, 0x35, 0x3a, 0x66, 0x36, 0x3a, 0x36, 0x33, 0x3a, + 0x34, 0x66, 0x3a, 0x37, 0x64, 0x3a, 0x36, 0x61, 0x3a, 0x30, 0x36, 0x3a, + 0x36, 0x35, 0x3a, 0x32, 0x36, 0x3a, 0x33, 0x32, 0x3a, 0x32, 0x38, 0x3a, + 0x32, 0x37, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, + 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, + 0x20, 0x37, 0x30, 0x3a, 0x61, 0x37, 0x3a, 0x33, 0x66, 0x3a, 0x37, 0x66, + 0x3a, 0x33, 0x37, 0x3a, 0x36, 0x62, 0x3a, 0x36, 0x30, 0x3a, 0x30, 0x37, + 0x3a, 0x34, 0x32, 0x3a, 0x34, 0x38, 0x3a, 0x39, 0x30, 0x3a, 0x34, 0x35, + 0x3a, 0x33, 0x34, 0x3a, 0x62, 0x31, 0x3a, 0x31, 0x34, 0x3a, 0x38, 0x32, + 0x3a, 0x64, 0x35, 0x3a, 0x62, 0x66, 0x3a, 0x30, 0x65, 0x3a, 0x36, 0x39, + 0x3a, 0x38, 0x65, 0x3a, 0x63, 0x63, 0x3a, 0x34, 0x39, 0x3a, 0x38, 0x64, + 0x3a, 0x66, 0x35, 0x3a, 0x32, 0x35, 0x3a, 0x37, 0x37, 0x3a, 0x65, 0x62, + 0x3a, 0x66, 0x32, 0x3a, 0x65, 0x39, 0x3a, 0x33, 0x62, 0x3a, 0x39, 0x61, + 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, + 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x46, 0x52, 0x6a, 0x43, + 0x43, 0x41, 0x79, 0x36, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, + 0x49, 0x62, 0x59, 0x77, 0x55, 0x52, 0x72, 0x47, 0x6d, 0x43, 0x75, 0x34, + 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, + 0x4e, 0x41, 0x51, 0x45, 0x4d, 0x42, 0x51, 0x41, 0x77, 0x51, 0x54, 0x45, + 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, 0x45, 0x0a, 0x42, 0x68, + 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, 0x46, 0x44, 0x41, 0x53, 0x42, 0x67, + 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x4d, 0x43, 0x30, 0x46, 0x6d, 0x5a, 0x6d, + 0x6c, 0x79, 0x62, 0x56, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, 0x4d, 0x52, + 0x77, 0x77, 0x47, 0x67, 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, 0x44, 0x42, + 0x4e, 0x42, 0x5a, 0x6d, 0x5a, 0x70, 0x63, 0x6d, 0x31, 0x55, 0x63, 0x6e, + 0x56, 0x7a, 0x0a, 0x64, 0x43, 0x42, 0x51, 0x63, 0x6d, 0x56, 0x74, 0x61, + 0x58, 0x56, 0x74, 0x4d, 0x42, 0x34, 0x58, 0x44, 0x54, 0x45, 0x77, 0x4d, + 0x44, 0x45, 0x79, 0x4f, 0x54, 0x45, 0x30, 0x4d, 0x54, 0x41, 0x7a, 0x4e, + 0x6c, 0x6f, 0x58, 0x44, 0x54, 0x51, 0x77, 0x4d, 0x54, 0x49, 0x7a, 0x4d, + 0x54, 0x45, 0x30, 0x4d, 0x54, 0x41, 0x7a, 0x4e, 0x6c, 0x6f, 0x77, 0x51, + 0x54, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x0a, 0x41, 0x31, 0x55, 0x45, + 0x42, 0x68, 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, 0x46, 0x44, 0x41, 0x53, + 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x4d, 0x43, 0x30, 0x46, 0x6d, + 0x5a, 0x6d, 0x6c, 0x79, 0x62, 0x56, 0x52, 0x79, 0x64, 0x58, 0x4e, 0x30, + 0x4d, 0x52, 0x77, 0x77, 0x47, 0x67, 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, + 0x44, 0x42, 0x4e, 0x42, 0x5a, 0x6d, 0x5a, 0x70, 0x63, 0x6d, 0x31, 0x55, + 0x0a, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x51, 0x63, 0x6d, 0x56, + 0x74, 0x61, 0x58, 0x56, 0x74, 0x4d, 0x49, 0x49, 0x43, 0x49, 0x6a, 0x41, + 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, + 0x42, 0x41, 0x51, 0x45, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x67, 0x38, + 0x41, 0x4d, 0x49, 0x49, 0x43, 0x43, 0x67, 0x4b, 0x43, 0x41, 0x67, 0x45, + 0x41, 0x78, 0x42, 0x4c, 0x66, 0x0a, 0x71, 0x56, 0x2f, 0x2b, 0x51, 0x64, + 0x33, 0x64, 0x39, 0x5a, 0x2b, 0x4b, 0x34, 0x2f, 0x61, 0x73, 0x34, 0x54, + 0x78, 0x34, 0x6d, 0x72, 0x7a, 0x59, 0x38, 0x48, 0x39, 0x36, 0x6f, 0x44, + 0x4d, 0x71, 0x33, 0x49, 0x30, 0x67, 0x57, 0x36, 0x34, 0x74, 0x62, 0x2b, + 0x65, 0x54, 0x32, 0x54, 0x5a, 0x77, 0x61, 0x6d, 0x6a, 0x50, 0x6a, 0x6c, + 0x47, 0x6a, 0x68, 0x56, 0x74, 0x6e, 0x42, 0x4b, 0x41, 0x51, 0x0a, 0x4a, + 0x47, 0x39, 0x64, 0x4b, 0x49, 0x4c, 0x42, 0x6c, 0x31, 0x66, 0x59, 0x53, + 0x43, 0x6b, 0x54, 0x74, 0x75, 0x47, 0x2b, 0x6b, 0x55, 0x33, 0x66, 0x68, + 0x51, 0x78, 0x54, 0x47, 0x4a, 0x6f, 0x65, 0x4a, 0x4b, 0x4a, 0x50, 0x6a, + 0x2f, 0x43, 0x69, 0x68, 0x51, 0x76, 0x4c, 0x39, 0x43, 0x6c, 0x2f, 0x30, + 0x71, 0x52, 0x59, 0x37, 0x69, 0x5a, 0x4e, 0x79, 0x61, 0x71, 0x6f, 0x65, + 0x35, 0x72, 0x5a, 0x0a, 0x2b, 0x6a, 0x6a, 0x65, 0x52, 0x46, 0x63, 0x56, + 0x35, 0x66, 0x69, 0x4d, 0x79, 0x4e, 0x6c, 0x49, 0x34, 0x67, 0x30, 0x57, + 0x4a, 0x78, 0x30, 0x65, 0x79, 0x49, 0x4f, 0x46, 0x4a, 0x62, 0x65, 0x36, + 0x71, 0x6c, 0x56, 0x42, 0x7a, 0x41, 0x4d, 0x69, 0x53, 0x79, 0x32, 0x52, + 0x6a, 0x59, 0x76, 0x6d, 0x69, 0x61, 0x39, 0x6d, 0x78, 0x2b, 0x6e, 0x2f, + 0x4b, 0x2b, 0x6b, 0x38, 0x72, 0x4e, 0x72, 0x53, 0x0a, 0x73, 0x38, 0x50, + 0x68, 0x61, 0x4a, 0x79, 0x4a, 0x2b, 0x48, 0x6f, 0x41, 0x56, 0x74, 0x37, + 0x30, 0x56, 0x5a, 0x56, 0x73, 0x2b, 0x37, 0x70, 0x6b, 0x33, 0x57, 0x4b, + 0x4c, 0x33, 0x77, 0x74, 0x33, 0x4d, 0x75, 0x74, 0x69, 0x7a, 0x43, 0x61, + 0x61, 0x6d, 0x37, 0x75, 0x71, 0x59, 0x6f, 0x4e, 0x4d, 0x74, 0x41, 0x5a, + 0x36, 0x4d, 0x4d, 0x67, 0x70, 0x76, 0x2b, 0x30, 0x47, 0x54, 0x5a, 0x65, + 0x35, 0x0a, 0x48, 0x4d, 0x51, 0x78, 0x4b, 0x39, 0x56, 0x66, 0x76, 0x46, + 0x4d, 0x53, 0x46, 0x35, 0x79, 0x5a, 0x56, 0x79, 0x6c, 0x6d, 0x64, 0x32, + 0x45, 0x68, 0x4d, 0x51, 0x63, 0x75, 0x4a, 0x55, 0x6d, 0x64, 0x47, 0x50, + 0x4c, 0x75, 0x38, 0x79, 0x74, 0x78, 0x6a, 0x4c, 0x57, 0x36, 0x4f, 0x51, + 0x64, 0x4a, 0x64, 0x2f, 0x7a, 0x76, 0x4c, 0x70, 0x4b, 0x51, 0x42, 0x59, + 0x30, 0x74, 0x4c, 0x33, 0x64, 0x37, 0x0a, 0x37, 0x30, 0x4f, 0x2f, 0x4e, + 0x62, 0x75, 0x61, 0x32, 0x50, 0x6c, 0x7a, 0x70, 0x79, 0x7a, 0x79, 0x30, + 0x46, 0x66, 0x75, 0x4b, 0x45, 0x34, 0x6d, 0x58, 0x34, 0x2b, 0x51, 0x61, + 0x41, 0x6b, 0x76, 0x75, 0x50, 0x6a, 0x63, 0x42, 0x75, 0x6b, 0x75, 0x6d, + 0x6a, 0x35, 0x52, 0x70, 0x39, 0x45, 0x69, 0x78, 0x41, 0x71, 0x6e, 0x4f, + 0x45, 0x68, 0x73, 0x73, 0x2f, 0x6e, 0x2f, 0x66, 0x61, 0x75, 0x47, 0x0a, + 0x56, 0x2b, 0x4f, 0x36, 0x31, 0x6f, 0x56, 0x34, 0x64, 0x37, 0x70, 0x44, + 0x36, 0x6b, 0x68, 0x2f, 0x39, 0x74, 0x69, 0x2b, 0x49, 0x32, 0x30, 0x65, + 0x76, 0x39, 0x45, 0x32, 0x62, 0x46, 0x68, 0x63, 0x38, 0x65, 0x36, 0x6b, + 0x47, 0x56, 0x51, 0x61, 0x39, 0x51, 0x50, 0x53, 0x64, 0x75, 0x62, 0x68, + 0x6a, 0x4c, 0x30, 0x38, 0x73, 0x39, 0x4e, 0x49, 0x53, 0x2b, 0x4c, 0x49, + 0x2b, 0x48, 0x2b, 0x53, 0x0a, 0x71, 0x48, 0x5a, 0x47, 0x6e, 0x45, 0x4a, + 0x6c, 0x50, 0x71, 0x51, 0x65, 0x77, 0x51, 0x63, 0x44, 0x57, 0x6b, 0x59, + 0x74, 0x75, 0x4a, 0x66, 0x7a, 0x74, 0x39, 0x57, 0x79, 0x56, 0x53, 0x48, + 0x76, 0x75, 0x74, 0x78, 0x4d, 0x41, 0x4a, 0x66, 0x37, 0x46, 0x4a, 0x55, + 0x6e, 0x4d, 0x37, 0x2f, 0x6f, 0x51, 0x30, 0x64, 0x47, 0x30, 0x67, 0x69, + 0x5a, 0x46, 0x6d, 0x41, 0x37, 0x6d, 0x6e, 0x37, 0x53, 0x0a, 0x35, 0x75, + 0x30, 0x34, 0x36, 0x75, 0x77, 0x42, 0x48, 0x6a, 0x78, 0x49, 0x56, 0x6b, + 0x6b, 0x4a, 0x78, 0x30, 0x77, 0x33, 0x41, 0x4a, 0x36, 0x49, 0x44, 0x73, + 0x42, 0x7a, 0x34, 0x57, 0x39, 0x6d, 0x36, 0x58, 0x4a, 0x48, 0x4d, 0x44, + 0x34, 0x51, 0x35, 0x51, 0x73, 0x44, 0x79, 0x5a, 0x70, 0x43, 0x41, 0x47, + 0x7a, 0x46, 0x6c, 0x48, 0x35, 0x68, 0x78, 0x49, 0x72, 0x66, 0x66, 0x34, + 0x49, 0x61, 0x0a, 0x43, 0x31, 0x6e, 0x45, 0x57, 0x54, 0x4a, 0x33, 0x73, + 0x37, 0x78, 0x67, 0x61, 0x56, 0x59, 0x35, 0x2f, 0x62, 0x51, 0x47, 0x65, + 0x79, 0x7a, 0x57, 0x5a, 0x44, 0x62, 0x5a, 0x76, 0x55, 0x6a, 0x74, 0x68, + 0x42, 0x39, 0x2b, 0x70, 0x53, 0x4b, 0x50, 0x4b, 0x72, 0x68, 0x43, 0x39, + 0x49, 0x4b, 0x33, 0x31, 0x46, 0x4f, 0x51, 0x65, 0x45, 0x34, 0x74, 0x47, + 0x76, 0x32, 0x42, 0x62, 0x30, 0x54, 0x58, 0x0a, 0x4f, 0x77, 0x46, 0x30, + 0x6c, 0x6b, 0x4c, 0x67, 0x41, 0x4f, 0x49, 0x75, 0x61, 0x2b, 0x72, 0x46, + 0x37, 0x6e, 0x4b, 0x73, 0x75, 0x37, 0x2f, 0x2b, 0x36, 0x71, 0x71, 0x6f, + 0x2b, 0x4e, 0x7a, 0x32, 0x73, 0x6e, 0x6d, 0x4b, 0x74, 0x6d, 0x63, 0x43, + 0x41, 0x77, 0x45, 0x41, 0x41, 0x61, 0x4e, 0x43, 0x4d, 0x45, 0x41, 0x77, + 0x48, 0x51, 0x59, 0x44, 0x56, 0x52, 0x30, 0x4f, 0x42, 0x42, 0x59, 0x45, + 0x0a, 0x46, 0x4a, 0x33, 0x41, 0x5a, 0x36, 0x59, 0x4d, 0x49, 0x74, 0x6b, + 0x6d, 0x39, 0x55, 0x57, 0x72, 0x70, 0x6d, 0x56, 0x53, 0x45, 0x53, 0x66, + 0x59, 0x52, 0x61, 0x78, 0x6a, 0x4d, 0x41, 0x38, 0x47, 0x41, 0x31, 0x55, + 0x64, 0x45, 0x77, 0x45, 0x42, 0x2f, 0x77, 0x51, 0x46, 0x4d, 0x41, 0x4d, + 0x42, 0x41, 0x66, 0x38, 0x77, 0x44, 0x67, 0x59, 0x44, 0x56, 0x52, 0x30, + 0x50, 0x41, 0x51, 0x48, 0x2f, 0x0a, 0x42, 0x41, 0x51, 0x44, 0x41, 0x67, + 0x45, 0x47, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, + 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x44, 0x41, 0x55, 0x41, 0x41, 0x34, + 0x49, 0x43, 0x41, 0x51, 0x43, 0x7a, 0x56, 0x30, 0x30, 0x51, 0x59, 0x6b, + 0x34, 0x36, 0x35, 0x4b, 0x7a, 0x71, 0x75, 0x42, 0x79, 0x76, 0x4d, 0x69, + 0x50, 0x49, 0x73, 0x30, 0x6c, 0x61, 0x55, 0x5a, 0x78, 0x32, 0x0a, 0x4b, + 0x49, 0x31, 0x35, 0x71, 0x6c, 0x64, 0x47, 0x46, 0x39, 0x58, 0x31, 0x55, + 0x76, 0x61, 0x33, 0x52, 0x4f, 0x67, 0x49, 0x52, 0x4c, 0x38, 0x59, 0x68, + 0x4e, 0x49, 0x4c, 0x67, 0x4d, 0x33, 0x46, 0x45, 0x76, 0x30, 0x41, 0x56, + 0x51, 0x56, 0x68, 0x68, 0x30, 0x48, 0x63, 0x74, 0x53, 0x53, 0x65, 0x50, + 0x4d, 0x54, 0x59, 0x79, 0x50, 0x74, 0x77, 0x6e, 0x69, 0x39, 0x34, 0x6c, + 0x6f, 0x4d, 0x67, 0x0a, 0x4e, 0x74, 0x35, 0x38, 0x44, 0x32, 0x6b, 0x54, + 0x69, 0x4b, 0x56, 0x31, 0x4e, 0x70, 0x67, 0x49, 0x70, 0x73, 0x62, 0x66, + 0x72, 0x4d, 0x37, 0x6a, 0x57, 0x4e, 0x61, 0x33, 0x50, 0x74, 0x36, 0x36, + 0x38, 0x2b, 0x73, 0x30, 0x51, 0x4e, 0x69, 0x69, 0x67, 0x66, 0x56, 0x34, + 0x50, 0x79, 0x2f, 0x56, 0x70, 0x66, 0x7a, 0x5a, 0x6f, 0x74, 0x52, 0x65, + 0x42, 0x41, 0x34, 0x58, 0x72, 0x66, 0x35, 0x42, 0x0a, 0x38, 0x4f, 0x57, + 0x79, 0x63, 0x76, 0x70, 0x45, 0x67, 0x6a, 0x4e, 0x43, 0x36, 0x43, 0x31, + 0x59, 0x39, 0x31, 0x61, 0x4d, 0x59, 0x6a, 0x2b, 0x36, 0x51, 0x72, 0x43, + 0x63, 0x44, 0x46, 0x78, 0x2b, 0x4c, 0x6d, 0x55, 0x6d, 0x58, 0x46, 0x4e, + 0x50, 0x41, 0x4c, 0x4a, 0x34, 0x66, 0x71, 0x45, 0x4e, 0x6d, 0x53, 0x32, + 0x4e, 0x75, 0x42, 0x32, 0x4f, 0x6f, 0x73, 0x53, 0x77, 0x2f, 0x57, 0x44, + 0x51, 0x0a, 0x4d, 0x4b, 0x53, 0x4f, 0x79, 0x41, 0x52, 0x69, 0x71, 0x63, + 0x54, 0x74, 0x4e, 0x64, 0x35, 0x36, 0x6c, 0x2b, 0x30, 0x4f, 0x4f, 0x46, + 0x36, 0x53, 0x4c, 0x35, 0x4e, 0x77, 0x70, 0x61, 0x6d, 0x63, 0x62, 0x36, + 0x64, 0x39, 0x45, 0x78, 0x31, 0x2b, 0x78, 0x67, 0x68, 0x49, 0x73, 0x56, + 0x35, 0x6e, 0x36, 0x31, 0x45, 0x49, 0x4a, 0x65, 0x6e, 0x6d, 0x4a, 0x57, + 0x74, 0x53, 0x4b, 0x5a, 0x47, 0x63, 0x0a, 0x30, 0x6a, 0x6c, 0x7a, 0x43, + 0x46, 0x66, 0x65, 0x6d, 0x51, 0x61, 0x30, 0x57, 0x35, 0x30, 0x51, 0x42, + 0x75, 0x48, 0x43, 0x41, 0x4b, 0x69, 0x34, 0x48, 0x45, 0x6f, 0x43, 0x43, + 0x68, 0x54, 0x51, 0x77, 0x55, 0x48, 0x4b, 0x2b, 0x34, 0x77, 0x31, 0x49, + 0x58, 0x32, 0x43, 0x4f, 0x50, 0x4b, 0x70, 0x56, 0x4a, 0x45, 0x5a, 0x4e, + 0x5a, 0x4f, 0x55, 0x62, 0x57, 0x6f, 0x36, 0x78, 0x62, 0x4c, 0x51, 0x0a, + 0x75, 0x34, 0x6d, 0x47, 0x6b, 0x2b, 0x69, 0x62, 0x79, 0x51, 0x38, 0x36, + 0x70, 0x33, 0x71, 0x34, 0x6f, 0x66, 0x42, 0x34, 0x52, 0x76, 0x72, 0x38, + 0x4e, 0x79, 0x2f, 0x6c, 0x69, 0x6f, 0x54, 0x7a, 0x33, 0x2f, 0x34, 0x45, + 0x32, 0x61, 0x46, 0x6f, 0x6f, 0x43, 0x38, 0x6b, 0x34, 0x67, 0x6d, 0x56, + 0x42, 0x74, 0x57, 0x56, 0x79, 0x75, 0x45, 0x6b, 0x6c, 0x75, 0x74, 0x38, + 0x39, 0x70, 0x4d, 0x46, 0x0a, 0x75, 0x2b, 0x31, 0x7a, 0x36, 0x53, 0x33, + 0x52, 0x64, 0x54, 0x6e, 0x58, 0x35, 0x79, 0x54, 0x62, 0x32, 0x45, 0x35, + 0x66, 0x51, 0x34, 0x2b, 0x65, 0x30, 0x42, 0x51, 0x35, 0x76, 0x31, 0x56, + 0x77, 0x53, 0x4a, 0x6c, 0x58, 0x4d, 0x62, 0x53, 0x63, 0x37, 0x6b, 0x71, + 0x59, 0x41, 0x35, 0x59, 0x77, 0x48, 0x32, 0x41, 0x47, 0x37, 0x68, 0x73, + 0x6a, 0x2f, 0x6f, 0x46, 0x67, 0x49, 0x78, 0x70, 0x48, 0x0a, 0x59, 0x6f, + 0x57, 0x6c, 0x7a, 0x42, 0x6b, 0x30, 0x67, 0x47, 0x2b, 0x7a, 0x72, 0x42, + 0x72, 0x6a, 0x6e, 0x2f, 0x42, 0x37, 0x53, 0x4b, 0x33, 0x56, 0x41, 0x64, + 0x6c, 0x6e, 0x74, 0x71, 0x6c, 0x79, 0x6b, 0x2b, 0x6f, 0x74, 0x5a, 0x72, + 0x57, 0x79, 0x75, 0x4f, 0x51, 0x39, 0x50, 0x4c, 0x4c, 0x76, 0x54, 0x49, + 0x7a, 0x71, 0x36, 0x77, 0x65, 0x2f, 0x71, 0x7a, 0x57, 0x61, 0x56, 0x59, + 0x61, 0x38, 0x0a, 0x47, 0x4b, 0x61, 0x31, 0x71, 0x46, 0x36, 0x30, 0x67, + 0x32, 0x78, 0x72, 0x61, 0x55, 0x44, 0x54, 0x6e, 0x39, 0x7a, 0x78, 0x77, + 0x32, 0x6c, 0x72, 0x75, 0x65, 0x46, 0x74, 0x43, 0x66, 0x54, 0x78, 0x71, + 0x6c, 0x42, 0x32, 0x43, 0x6e, 0x70, 0x39, 0x65, 0x68, 0x65, 0x68, 0x56, + 0x5a, 0x5a, 0x43, 0x6d, 0x54, 0x45, 0x4a, 0x33, 0x57, 0x41, 0x52, 0x6a, + 0x51, 0x55, 0x77, 0x66, 0x75, 0x61, 0x4f, 0x0a, 0x52, 0x74, 0x47, 0x64, + 0x46, 0x4e, 0x72, 0x48, 0x46, 0x2b, 0x51, 0x46, 0x6c, 0x6f, 0x7a, 0x45, + 0x4a, 0x4c, 0x55, 0x62, 0x7a, 0x78, 0x51, 0x48, 0x73, 0x6b, 0x44, 0x34, + 0x6f, 0x35, 0x35, 0x42, 0x68, 0x72, 0x77, 0x45, 0x30, 0x47, 0x75, 0x57, + 0x79, 0x43, 0x71, 0x41, 0x4e, 0x50, 0x32, 0x2f, 0x37, 0x77, 0x61, 0x6a, + 0x33, 0x56, 0x6a, 0x46, 0x68, 0x54, 0x30, 0x2b, 0x6a, 0x2f, 0x36, 0x65, + 0x0a, 0x4b, 0x65, 0x43, 0x32, 0x75, 0x41, 0x6c, 0x6f, 0x47, 0x52, 0x77, + 0x59, 0x51, 0x77, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, + 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, + 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, + 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x41, 0x66, + 0x66, 0x69, 0x72, 0x6d, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x50, 0x72, + 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x20, 0x45, 0x43, 0x43, 0x20, 0x4f, 0x3d, + 0x41, 0x66, 0x66, 0x69, 0x72, 0x6d, 0x54, 0x72, 0x75, 0x73, 0x74, 0x0a, + 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, + 0x4e, 0x3d, 0x41, 0x66, 0x66, 0x69, 0x72, 0x6d, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x20, 0x50, 0x72, 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x20, 0x45, 0x43, + 0x43, 0x20, 0x4f, 0x3d, 0x41, 0x66, 0x66, 0x69, 0x72, 0x6d, 0x54, 0x72, + 0x75, 0x73, 0x74, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x41, 0x66, 0x66, 0x69, 0x72, 0x6d, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x20, 0x50, 0x72, 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x20, 0x45, 0x43, + 0x43, 0x22, 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, + 0x20, 0x38, 0x34, 0x30, 0x31, 0x32, 0x32, 0x34, 0x39, 0x30, 0x37, 0x38, + 0x36, 0x31, 0x34, 0x39, 0x30, 0x32, 0x36, 0x30, 0x0a, 0x23, 0x20, 0x4d, + 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x3a, 0x20, 0x36, 0x34, 0x3a, 0x62, 0x30, 0x3a, 0x30, 0x39, + 0x3a, 0x35, 0x35, 0x3a, 0x63, 0x66, 0x3a, 0x62, 0x31, 0x3a, 0x64, 0x35, + 0x3a, 0x39, 0x39, 0x3a, 0x65, 0x32, 0x3a, 0x62, 0x65, 0x3a, 0x31, 0x33, + 0x3a, 0x61, 0x62, 0x3a, 0x61, 0x36, 0x3a, 0x35, 0x64, 0x3a, 0x65, 0x61, + 0x3a, 0x34, 0x64, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, + 0x62, 0x38, 0x3a, 0x32, 0x33, 0x3a, 0x36, 0x62, 0x3a, 0x30, 0x30, 0x3a, + 0x32, 0x66, 0x3a, 0x31, 0x64, 0x3a, 0x31, 0x36, 0x3a, 0x38, 0x36, 0x3a, + 0x35, 0x33, 0x3a, 0x30, 0x31, 0x3a, 0x35, 0x35, 0x3a, 0x36, 0x63, 0x3a, + 0x31, 0x31, 0x3a, 0x61, 0x34, 0x3a, 0x33, 0x37, 0x3a, 0x63, 0x61, 0x3a, + 0x65, 0x62, 0x3a, 0x66, 0x66, 0x3a, 0x63, 0x33, 0x3a, 0x62, 0x62, 0x0a, + 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, 0x6e, + 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x62, 0x64, + 0x3a, 0x37, 0x31, 0x3a, 0x66, 0x64, 0x3a, 0x66, 0x36, 0x3a, 0x64, 0x61, + 0x3a, 0x39, 0x37, 0x3a, 0x65, 0x34, 0x3a, 0x63, 0x66, 0x3a, 0x36, 0x32, + 0x3a, 0x64, 0x31, 0x3a, 0x36, 0x34, 0x3a, 0x37, 0x61, 0x3a, 0x64, 0x64, + 0x3a, 0x32, 0x35, 0x3a, 0x38, 0x31, 0x3a, 0x62, 0x30, 0x3a, 0x37, 0x64, + 0x3a, 0x37, 0x39, 0x3a, 0x61, 0x64, 0x3a, 0x66, 0x38, 0x3a, 0x33, 0x39, + 0x3a, 0x37, 0x65, 0x3a, 0x62, 0x34, 0x3a, 0x65, 0x63, 0x3a, 0x62, 0x61, + 0x3a, 0x39, 0x63, 0x3a, 0x35, 0x65, 0x3a, 0x38, 0x34, 0x3a, 0x38, 0x38, + 0x3a, 0x38, 0x32, 0x3a, 0x31, 0x34, 0x3a, 0x32, 0x33, 0x0a, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, + 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x42, 0x2f, 0x6a, 0x43, 0x43, 0x41, 0x59, + 0x57, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x49, 0x64, 0x4a, + 0x63, 0x6c, 0x69, 0x73, 0x63, 0x2f, 0x65, 0x6c, 0x51, 0x77, 0x43, 0x67, + 0x59, 0x49, 0x4b, 0x6f, 0x5a, 0x49, 0x7a, 0x6a, 0x30, 0x45, 0x41, 0x77, + 0x4d, 0x77, 0x52, 0x54, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, + 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x0a, 0x56, 0x56, 0x4d, 0x78, 0x46, + 0x44, 0x41, 0x53, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x4d, 0x43, + 0x30, 0x46, 0x6d, 0x5a, 0x6d, 0x6c, 0x79, 0x62, 0x56, 0x52, 0x79, 0x64, + 0x58, 0x4e, 0x30, 0x4d, 0x53, 0x41, 0x77, 0x48, 0x67, 0x59, 0x44, 0x56, + 0x51, 0x51, 0x44, 0x44, 0x42, 0x64, 0x42, 0x5a, 0x6d, 0x5a, 0x70, 0x63, + 0x6d, 0x31, 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x43, 0x42, 0x51, 0x0a, + 0x63, 0x6d, 0x56, 0x74, 0x61, 0x58, 0x56, 0x74, 0x49, 0x45, 0x56, 0x44, + 0x51, 0x7a, 0x41, 0x65, 0x46, 0x77, 0x30, 0x78, 0x4d, 0x44, 0x41, 0x78, + 0x4d, 0x6a, 0x6b, 0x78, 0x4e, 0x44, 0x49, 0x77, 0x4d, 0x6a, 0x52, 0x61, + 0x46, 0x77, 0x30, 0x30, 0x4d, 0x44, 0x45, 0x79, 0x4d, 0x7a, 0x45, 0x78, + 0x4e, 0x44, 0x49, 0x77, 0x4d, 0x6a, 0x52, 0x61, 0x4d, 0x45, 0x55, 0x78, + 0x43, 0x7a, 0x41, 0x4a, 0x0a, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x59, + 0x54, 0x41, 0x6c, 0x56, 0x54, 0x4d, 0x52, 0x51, 0x77, 0x45, 0x67, 0x59, + 0x44, 0x56, 0x51, 0x51, 0x4b, 0x44, 0x41, 0x74, 0x42, 0x5a, 0x6d, 0x5a, + 0x70, 0x63, 0x6d, 0x31, 0x55, 0x63, 0x6e, 0x56, 0x7a, 0x64, 0x44, 0x45, + 0x67, 0x4d, 0x42, 0x34, 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x77, 0x77, + 0x58, 0x51, 0x57, 0x5a, 0x6d, 0x61, 0x58, 0x4a, 0x74, 0x0a, 0x56, 0x48, + 0x4a, 0x31, 0x63, 0x33, 0x51, 0x67, 0x55, 0x48, 0x4a, 0x6c, 0x62, 0x57, + 0x6c, 0x31, 0x62, 0x53, 0x42, 0x46, 0x51, 0x30, 0x4d, 0x77, 0x64, 0x6a, + 0x41, 0x51, 0x42, 0x67, 0x63, 0x71, 0x68, 0x6b, 0x6a, 0x4f, 0x50, 0x51, + 0x49, 0x42, 0x42, 0x67, 0x55, 0x72, 0x67, 0x51, 0x51, 0x41, 0x49, 0x67, + 0x4e, 0x69, 0x41, 0x41, 0x51, 0x4e, 0x4d, 0x46, 0x34, 0x62, 0x46, 0x5a, + 0x30, 0x44, 0x0a, 0x30, 0x4b, 0x46, 0x35, 0x4e, 0x62, 0x63, 0x36, 0x50, + 0x4a, 0x4a, 0x36, 0x79, 0x68, 0x55, 0x63, 0x7a, 0x57, 0x4c, 0x7a, 0x6e, + 0x43, 0x5a, 0x63, 0x42, 0x7a, 0x33, 0x6c, 0x56, 0x50, 0x71, 0x6a, 0x31, + 0x73, 0x77, 0x53, 0x36, 0x76, 0x51, 0x55, 0x58, 0x2b, 0x69, 0x4f, 0x47, + 0x61, 0x73, 0x76, 0x4c, 0x6b, 0x6a, 0x6d, 0x72, 0x42, 0x68, 0x44, 0x65, + 0x4b, 0x7a, 0x51, 0x4e, 0x38, 0x4f, 0x39, 0x0a, 0x73, 0x73, 0x30, 0x73, + 0x35, 0x6b, 0x66, 0x69, 0x47, 0x75, 0x5a, 0x6a, 0x75, 0x44, 0x30, 0x75, + 0x4c, 0x33, 0x6a, 0x45, 0x54, 0x39, 0x76, 0x30, 0x44, 0x36, 0x52, 0x6f, + 0x54, 0x46, 0x56, 0x79, 0x61, 0x35, 0x55, 0x64, 0x54, 0x68, 0x68, 0x43, + 0x6c, 0x58, 0x6a, 0x4d, 0x4e, 0x7a, 0x79, 0x52, 0x34, 0x70, 0x74, 0x6c, + 0x4b, 0x79, 0x6d, 0x6a, 0x51, 0x6a, 0x42, 0x41, 0x4d, 0x42, 0x30, 0x47, + 0x0a, 0x41, 0x31, 0x55, 0x64, 0x44, 0x67, 0x51, 0x57, 0x42, 0x42, 0x53, + 0x61, 0x72, 0x79, 0x6c, 0x36, 0x77, 0x42, 0x45, 0x31, 0x4e, 0x53, 0x5a, + 0x52, 0x4d, 0x41, 0x44, 0x44, 0x61, 0x76, 0x35, 0x41, 0x31, 0x61, 0x37, + 0x57, 0x50, 0x44, 0x41, 0x50, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x52, 0x4d, + 0x42, 0x41, 0x66, 0x38, 0x45, 0x42, 0x54, 0x41, 0x44, 0x41, 0x51, 0x48, + 0x2f, 0x4d, 0x41, 0x34, 0x47, 0x0a, 0x41, 0x31, 0x55, 0x64, 0x44, 0x77, + 0x45, 0x42, 0x2f, 0x77, 0x51, 0x45, 0x41, 0x77, 0x49, 0x42, 0x42, 0x6a, + 0x41, 0x4b, 0x42, 0x67, 0x67, 0x71, 0x68, 0x6b, 0x6a, 0x4f, 0x50, 0x51, + 0x51, 0x44, 0x41, 0x77, 0x4e, 0x6e, 0x41, 0x44, 0x42, 0x6b, 0x41, 0x6a, + 0x41, 0x58, 0x43, 0x66, 0x4f, 0x48, 0x69, 0x46, 0x42, 0x61, 0x72, 0x38, + 0x6a, 0x41, 0x51, 0x72, 0x39, 0x48, 0x58, 0x2f, 0x56, 0x73, 0x0a, 0x61, + 0x6f, 0x62, 0x67, 0x78, 0x43, 0x64, 0x30, 0x35, 0x44, 0x68, 0x54, 0x31, + 0x77, 0x56, 0x2f, 0x47, 0x7a, 0x54, 0x6a, 0x78, 0x69, 0x2b, 0x7a, 0x79, + 0x67, 0x6b, 0x38, 0x4e, 0x35, 0x33, 0x58, 0x35, 0x37, 0x68, 0x47, 0x38, + 0x66, 0x32, 0x68, 0x34, 0x6e, 0x45, 0x43, 0x4d, 0x45, 0x4a, 0x5a, 0x68, + 0x30, 0x50, 0x55, 0x55, 0x64, 0x2b, 0x36, 0x30, 0x77, 0x6b, 0x79, 0x57, + 0x73, 0x36, 0x49, 0x0a, 0x66, 0x6c, 0x63, 0x39, 0x6e, 0x46, 0x39, 0x43, + 0x61, 0x2f, 0x55, 0x48, 0x4c, 0x62, 0x58, 0x77, 0x67, 0x70, 0x50, 0x35, + 0x57, 0x57, 0x2b, 0x75, 0x5a, 0x50, 0x70, 0x59, 0x35, 0x59, 0x73, 0x65, + 0x34, 0x32, 0x4f, 0x2b, 0x74, 0x59, 0x48, 0x4e, 0x62, 0x77, 0x4b, 0x4d, + 0x65, 0x51, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, + 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, + 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, 0x20, 0x49, 0x73, + 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x53, 0x74, 0x61, + 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x4f, 0x3d, 0x53, 0x74, 0x61, 0x72, + 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x4c, 0x74, 0x64, 0x2e, 0x20, 0x4f, 0x55, + 0x3d, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x44, 0x69, 0x67, 0x69, + 0x74, 0x61, 0x6c, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x0a, + 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x43, + 0x4e, 0x3d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x4f, + 0x3d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x4c, 0x74, + 0x64, 0x2e, 0x20, 0x4f, 0x55, 0x3d, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, + 0x20, 0x44, 0x69, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x20, 0x43, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x53, 0x69, 0x67, + 0x6e, 0x69, 0x6e, 0x67, 0x0a, 0x23, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, + 0x3a, 0x20, 0x22, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, + 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, + 0x0a, 0x23, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x34, + 0x35, 0x0a, 0x23, 0x20, 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, + 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x63, 0x39, 0x3a, + 0x33, 0x62, 0x3a, 0x30, 0x64, 0x3a, 0x38, 0x34, 0x3a, 0x34, 0x31, 0x3a, + 0x66, 0x63, 0x3a, 0x61, 0x34, 0x3a, 0x37, 0x36, 0x3a, 0x37, 0x39, 0x3a, + 0x32, 0x33, 0x3a, 0x30, 0x38, 0x3a, 0x35, 0x37, 0x3a, 0x64, 0x65, 0x3a, + 0x31, 0x30, 0x3a, 0x31, 0x39, 0x3a, 0x31, 0x36, 0x0a, 0x23, 0x20, 0x53, + 0x48, 0x41, 0x31, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, + 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x61, 0x33, 0x3a, 0x66, 0x31, 0x3a, 0x33, + 0x33, 0x3a, 0x33, 0x66, 0x3a, 0x65, 0x32, 0x3a, 0x34, 0x32, 0x3a, 0x62, + 0x66, 0x3a, 0x63, 0x66, 0x3a, 0x63, 0x35, 0x3a, 0x64, 0x31, 0x3a, 0x34, + 0x65, 0x3a, 0x38, 0x66, 0x3a, 0x33, 0x39, 0x3a, 0x34, 0x32, 0x3a, 0x39, + 0x38, 0x3a, 0x34, 0x30, 0x3a, 0x36, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x64, + 0x31, 0x3a, 0x61, 0x30, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, + 0x36, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, + 0x74, 0x3a, 0x20, 0x65, 0x31, 0x3a, 0x37, 0x38, 0x3a, 0x39, 0x30, 0x3a, + 0x65, 0x65, 0x3a, 0x30, 0x39, 0x3a, 0x61, 0x33, 0x3a, 0x66, 0x62, 0x3a, + 0x66, 0x34, 0x3a, 0x66, 0x34, 0x3a, 0x38, 0x62, 0x3a, 0x39, 0x63, 0x3a, + 0x34, 0x31, 0x3a, 0x34, 0x61, 0x3a, 0x31, 0x37, 0x3a, 0x64, 0x36, 0x3a, + 0x33, 0x37, 0x3a, 0x62, 0x37, 0x3a, 0x61, 0x35, 0x3a, 0x30, 0x36, 0x3a, + 0x34, 0x37, 0x3a, 0x65, 0x39, 0x3a, 0x62, 0x63, 0x3a, 0x37, 0x35, 0x3a, + 0x32, 0x33, 0x3a, 0x32, 0x32, 0x3a, 0x37, 0x32, 0x3a, 0x37, 0x66, 0x3a, + 0x63, 0x63, 0x3a, 0x31, 0x37, 0x3a, 0x34, 0x32, 0x3a, 0x61, 0x39, 0x3a, + 0x31, 0x31, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, + 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, + 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x48, 0x68, + 0x7a, 0x43, 0x43, 0x42, 0x57, 0x2b, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, + 0x67, 0x49, 0x42, 0x4c, 0x54, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, + 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x73, 0x46, 0x41, + 0x44, 0x42, 0x39, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, + 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x4a, 0x54, 0x44, 0x45, 0x57, 0x0a, + 0x4d, 0x42, 0x51, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4e, + 0x55, 0x33, 0x52, 0x68, 0x63, 0x6e, 0x52, 0x44, 0x62, 0x32, 0x30, 0x67, + 0x54, 0x48, 0x52, 0x6b, 0x4c, 0x6a, 0x45, 0x72, 0x4d, 0x43, 0x6b, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x43, 0x78, 0x4d, 0x69, 0x55, 0x32, 0x56, 0x6a, + 0x64, 0x58, 0x4a, 0x6c, 0x49, 0x45, 0x52, 0x70, 0x5a, 0x32, 0x6c, 0x30, + 0x59, 0x57, 0x77, 0x67, 0x0a, 0x51, 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, + 0x6d, 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, 0x55, 0x67, 0x55, 0x32, 0x6c, + 0x6e, 0x62, 0x6d, 0x6c, 0x75, 0x5a, 0x7a, 0x45, 0x70, 0x4d, 0x43, 0x63, + 0x47, 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x67, 0x55, 0x33, 0x52, + 0x68, 0x63, 0x6e, 0x52, 0x44, 0x62, 0x32, 0x30, 0x67, 0x51, 0x32, 0x56, + 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, 0x0a, 0x64, 0x47, + 0x6c, 0x76, 0x62, 0x69, 0x42, 0x42, 0x64, 0x58, 0x52, 0x6f, 0x62, 0x33, + 0x4a, 0x70, 0x64, 0x48, 0x6b, 0x77, 0x48, 0x68, 0x63, 0x4e, 0x4d, 0x44, + 0x59, 0x77, 0x4f, 0x54, 0x45, 0x33, 0x4d, 0x54, 0x6b, 0x30, 0x4e, 0x6a, + 0x4d, 0x33, 0x57, 0x68, 0x63, 0x4e, 0x4d, 0x7a, 0x59, 0x77, 0x4f, 0x54, + 0x45, 0x33, 0x4d, 0x54, 0x6b, 0x30, 0x4e, 0x6a, 0x4d, 0x32, 0x57, 0x6a, + 0x42, 0x39, 0x0a, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, + 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x4a, 0x54, 0x44, 0x45, 0x57, 0x4d, + 0x42, 0x51, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4e, 0x55, + 0x33, 0x52, 0x68, 0x63, 0x6e, 0x52, 0x44, 0x62, 0x32, 0x30, 0x67, 0x54, + 0x48, 0x52, 0x6b, 0x4c, 0x6a, 0x45, 0x72, 0x4d, 0x43, 0x6b, 0x47, 0x41, + 0x31, 0x55, 0x45, 0x43, 0x78, 0x4d, 0x69, 0x0a, 0x55, 0x32, 0x56, 0x6a, + 0x64, 0x58, 0x4a, 0x6c, 0x49, 0x45, 0x52, 0x70, 0x5a, 0x32, 0x6c, 0x30, + 0x59, 0x57, 0x77, 0x67, 0x51, 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, 0x6d, + 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, 0x55, 0x67, 0x55, 0x32, 0x6c, 0x6e, + 0x62, 0x6d, 0x6c, 0x75, 0x5a, 0x7a, 0x45, 0x70, 0x4d, 0x43, 0x63, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x67, 0x55, 0x33, 0x52, 0x68, + 0x0a, 0x63, 0x6e, 0x52, 0x44, 0x62, 0x32, 0x30, 0x67, 0x51, 0x32, 0x56, + 0x79, 0x64, 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, 0x6c, + 0x76, 0x62, 0x69, 0x42, 0x42, 0x64, 0x58, 0x52, 0x6f, 0x62, 0x33, 0x4a, + 0x70, 0x64, 0x48, 0x6b, 0x77, 0x67, 0x67, 0x49, 0x69, 0x4d, 0x41, 0x30, + 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, + 0x42, 0x41, 0x51, 0x55, 0x41, 0x0a, 0x41, 0x34, 0x49, 0x43, 0x44, 0x77, + 0x41, 0x77, 0x67, 0x67, 0x49, 0x4b, 0x41, 0x6f, 0x49, 0x43, 0x41, 0x51, + 0x44, 0x42, 0x69, 0x4e, 0x73, 0x4a, 0x76, 0x47, 0x78, 0x47, 0x66, 0x48, + 0x69, 0x66, 0x6c, 0x58, 0x75, 0x31, 0x4d, 0x35, 0x44, 0x79, 0x63, 0x6d, + 0x4c, 0x57, 0x77, 0x54, 0x59, 0x67, 0x49, 0x69, 0x52, 0x65, 0x7a, 0x75, + 0x6c, 0x33, 0x38, 0x6b, 0x4d, 0x4b, 0x6f, 0x67, 0x5a, 0x6b, 0x0a, 0x70, + 0x4d, 0x79, 0x4f, 0x4e, 0x76, 0x67, 0x34, 0x35, 0x69, 0x50, 0x77, 0x62, + 0x6d, 0x32, 0x78, 0x50, 0x4e, 0x31, 0x79, 0x6f, 0x34, 0x55, 0x63, 0x6f, + 0x64, 0x4d, 0x39, 0x74, 0x44, 0x4d, 0x72, 0x30, 0x79, 0x2b, 0x76, 0x2f, + 0x75, 0x71, 0x77, 0x51, 0x56, 0x6c, 0x6e, 0x74, 0x73, 0x51, 0x47, 0x66, + 0x51, 0x71, 0x65, 0x64, 0x49, 0x58, 0x57, 0x65, 0x55, 0x79, 0x41, 0x4e, + 0x33, 0x72, 0x66, 0x0a, 0x4f, 0x51, 0x56, 0x53, 0x57, 0x66, 0x66, 0x30, + 0x47, 0x30, 0x5a, 0x44, 0x70, 0x4e, 0x4b, 0x46, 0x68, 0x64, 0x4c, 0x44, + 0x63, 0x66, 0x4e, 0x31, 0x59, 0x6a, 0x53, 0x36, 0x4c, 0x49, 0x70, 0x2f, + 0x48, 0x6f, 0x2f, 0x75, 0x37, 0x54, 0x54, 0x51, 0x45, 0x63, 0x65, 0x57, + 0x7a, 0x56, 0x49, 0x39, 0x75, 0x6a, 0x50, 0x57, 0x33, 0x55, 0x33, 0x65, + 0x43, 0x7a, 0x74, 0x4b, 0x53, 0x35, 0x2f, 0x43, 0x0a, 0x4a, 0x69, 0x2f, + 0x36, 0x74, 0x52, 0x59, 0x63, 0x63, 0x6a, 0x56, 0x33, 0x79, 0x6a, 0x78, + 0x64, 0x35, 0x73, 0x72, 0x68, 0x4a, 0x6f, 0x73, 0x61, 0x4e, 0x6e, 0x5a, + 0x63, 0x41, 0x64, 0x74, 0x30, 0x46, 0x43, 0x58, 0x2b, 0x37, 0x62, 0x57, + 0x67, 0x69, 0x41, 0x2f, 0x64, 0x65, 0x4d, 0x6f, 0x74, 0x48, 0x77, 0x65, + 0x58, 0x4d, 0x41, 0x45, 0x74, 0x63, 0x6e, 0x6e, 0x36, 0x52, 0x74, 0x59, + 0x54, 0x0a, 0x4b, 0x71, 0x69, 0x35, 0x70, 0x71, 0x75, 0x44, 0x53, 0x52, + 0x33, 0x6c, 0x38, 0x75, 0x2f, 0x64, 0x35, 0x41, 0x47, 0x4f, 0x47, 0x41, + 0x71, 0x50, 0x59, 0x31, 0x4d, 0x57, 0x68, 0x57, 0x4b, 0x70, 0x44, 0x68, + 0x6b, 0x36, 0x7a, 0x4c, 0x56, 0x6d, 0x70, 0x73, 0x4a, 0x72, 0x64, 0x41, + 0x66, 0x6b, 0x4b, 0x2b, 0x46, 0x32, 0x50, 0x72, 0x52, 0x74, 0x32, 0x50, + 0x5a, 0x45, 0x34, 0x58, 0x4e, 0x69, 0x0a, 0x48, 0x7a, 0x76, 0x45, 0x76, + 0x71, 0x42, 0x54, 0x56, 0x69, 0x56, 0x73, 0x55, 0x51, 0x6e, 0x33, 0x71, + 0x71, 0x76, 0x4b, 0x76, 0x33, 0x62, 0x39, 0x62, 0x5a, 0x76, 0x7a, 0x6e, + 0x64, 0x75, 0x2f, 0x50, 0x57, 0x61, 0x38, 0x44, 0x46, 0x61, 0x71, 0x72, + 0x35, 0x68, 0x49, 0x6c, 0x54, 0x70, 0x4c, 0x33, 0x36, 0x64, 0x59, 0x55, + 0x4e, 0x6b, 0x34, 0x64, 0x61, 0x6c, 0x62, 0x36, 0x6b, 0x4d, 0x4d, 0x0a, + 0x41, 0x76, 0x2b, 0x5a, 0x36, 0x2b, 0x68, 0x73, 0x54, 0x58, 0x42, 0x62, + 0x4b, 0x57, 0x57, 0x63, 0x33, 0x61, 0x70, 0x64, 0x7a, 0x4b, 0x38, 0x42, + 0x4d, 0x65, 0x77, 0x4d, 0x36, 0x39, 0x4b, 0x4e, 0x36, 0x4f, 0x71, 0x63, + 0x65, 0x2b, 0x5a, 0x75, 0x39, 0x79, 0x64, 0x6d, 0x44, 0x42, 0x70, 0x49, + 0x31, 0x32, 0x35, 0x43, 0x34, 0x7a, 0x2f, 0x65, 0x49, 0x54, 0x35, 0x37, + 0x34, 0x51, 0x31, 0x77, 0x0a, 0x2b, 0x32, 0x4f, 0x71, 0x71, 0x47, 0x77, + 0x61, 0x56, 0x4c, 0x52, 0x63, 0x4a, 0x58, 0x72, 0x4a, 0x6f, 0x73, 0x6d, + 0x4c, 0x46, 0x71, 0x61, 0x37, 0x4c, 0x48, 0x34, 0x58, 0x58, 0x67, 0x56, + 0x4e, 0x57, 0x47, 0x34, 0x53, 0x48, 0x51, 0x48, 0x75, 0x45, 0x68, 0x41, + 0x4e, 0x78, 0x6a, 0x4a, 0x2f, 0x47, 0x50, 0x2f, 0x38, 0x39, 0x50, 0x72, + 0x4e, 0x62, 0x70, 0x48, 0x6f, 0x4e, 0x6b, 0x6d, 0x2b, 0x0a, 0x47, 0x6b, + 0x68, 0x70, 0x69, 0x38, 0x4b, 0x57, 0x54, 0x52, 0x6f, 0x53, 0x73, 0x6d, + 0x6b, 0x58, 0x77, 0x51, 0x71, 0x51, 0x31, 0x76, 0x70, 0x35, 0x49, 0x6b, + 0x69, 0x2f, 0x75, 0x6e, 0x74, 0x70, 0x2b, 0x48, 0x44, 0x48, 0x2b, 0x6e, + 0x6f, 0x33, 0x32, 0x4e, 0x67, 0x4e, 0x30, 0x6e, 0x5a, 0x50, 0x56, 0x2f, + 0x2b, 0x51, 0x74, 0x2b, 0x4f, 0x52, 0x30, 0x74, 0x33, 0x76, 0x77, 0x6d, + 0x43, 0x33, 0x0a, 0x5a, 0x7a, 0x72, 0x64, 0x2f, 0x71, 0x71, 0x63, 0x38, + 0x4e, 0x53, 0x4c, 0x66, 0x33, 0x49, 0x69, 0x7a, 0x73, 0x61, 0x66, 0x6c, + 0x37, 0x62, 0x34, 0x72, 0x34, 0x71, 0x67, 0x45, 0x4b, 0x6a, 0x5a, 0x2b, + 0x78, 0x6a, 0x47, 0x74, 0x72, 0x56, 0x63, 0x55, 0x6a, 0x79, 0x4a, 0x74, + 0x68, 0x6b, 0x71, 0x63, 0x77, 0x45, 0x4b, 0x44, 0x77, 0x4f, 0x7a, 0x45, + 0x6d, 0x44, 0x79, 0x65, 0x69, 0x2b, 0x42, 0x0a, 0x32, 0x36, 0x4e, 0x75, + 0x2f, 0x79, 0x59, 0x77, 0x6c, 0x2f, 0x57, 0x4c, 0x33, 0x59, 0x6c, 0x58, + 0x74, 0x71, 0x30, 0x39, 0x73, 0x36, 0x38, 0x72, 0x78, 0x62, 0x64, 0x32, + 0x41, 0x76, 0x43, 0x6c, 0x31, 0x69, 0x75, 0x61, 0x68, 0x68, 0x51, 0x71, + 0x63, 0x76, 0x62, 0x6a, 0x4d, 0x34, 0x78, 0x64, 0x43, 0x55, 0x73, 0x54, + 0x33, 0x37, 0x75, 0x4d, 0x64, 0x42, 0x4e, 0x53, 0x53, 0x77, 0x49, 0x44, + 0x0a, 0x41, 0x51, 0x41, 0x42, 0x6f, 0x34, 0x49, 0x43, 0x45, 0x44, 0x43, + 0x43, 0x41, 0x67, 0x77, 0x77, 0x44, 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, + 0x54, 0x41, 0x51, 0x48, 0x2f, 0x42, 0x41, 0x55, 0x77, 0x41, 0x77, 0x45, + 0x42, 0x2f, 0x7a, 0x41, 0x4f, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x38, + 0x42, 0x41, 0x66, 0x38, 0x45, 0x42, 0x41, 0x4d, 0x43, 0x41, 0x51, 0x59, + 0x77, 0x48, 0x51, 0x59, 0x44, 0x0a, 0x56, 0x52, 0x30, 0x4f, 0x42, 0x42, + 0x59, 0x45, 0x46, 0x45, 0x34, 0x4c, 0x37, 0x78, 0x71, 0x6b, 0x51, 0x46, + 0x75, 0x6c, 0x46, 0x32, 0x6d, 0x48, 0x4d, 0x4d, 0x6f, 0x30, 0x61, 0x45, + 0x50, 0x51, 0x51, 0x61, 0x37, 0x79, 0x4d, 0x42, 0x38, 0x47, 0x41, 0x31, + 0x55, 0x64, 0x49, 0x77, 0x51, 0x59, 0x4d, 0x42, 0x61, 0x41, 0x46, 0x45, + 0x34, 0x4c, 0x37, 0x78, 0x71, 0x6b, 0x51, 0x46, 0x75, 0x6c, 0x0a, 0x46, + 0x32, 0x6d, 0x48, 0x4d, 0x4d, 0x6f, 0x30, 0x61, 0x45, 0x50, 0x51, 0x51, + 0x61, 0x37, 0x79, 0x4d, 0x49, 0x49, 0x42, 0x57, 0x67, 0x59, 0x44, 0x56, + 0x52, 0x30, 0x67, 0x42, 0x49, 0x49, 0x42, 0x55, 0x54, 0x43, 0x43, 0x41, + 0x55, 0x30, 0x77, 0x67, 0x67, 0x46, 0x4a, 0x42, 0x67, 0x73, 0x72, 0x42, + 0x67, 0x45, 0x45, 0x41, 0x59, 0x47, 0x31, 0x4e, 0x77, 0x45, 0x42, 0x41, + 0x54, 0x43, 0x43, 0x0a, 0x41, 0x54, 0x67, 0x77, 0x4c, 0x67, 0x59, 0x49, + 0x4b, 0x77, 0x59, 0x42, 0x42, 0x51, 0x55, 0x48, 0x41, 0x67, 0x45, 0x57, + 0x49, 0x6d, 0x68, 0x30, 0x64, 0x48, 0x41, 0x36, 0x4c, 0x79, 0x39, 0x33, + 0x64, 0x33, 0x63, 0x75, 0x63, 0x33, 0x52, 0x68, 0x63, 0x6e, 0x52, 0x7a, + 0x63, 0x32, 0x77, 0x75, 0x59, 0x32, 0x39, 0x74, 0x4c, 0x33, 0x42, 0x76, + 0x62, 0x47, 0x6c, 0x6a, 0x65, 0x53, 0x35, 0x77, 0x0a, 0x5a, 0x47, 0x59, + 0x77, 0x4e, 0x41, 0x59, 0x49, 0x4b, 0x77, 0x59, 0x42, 0x42, 0x51, 0x55, + 0x48, 0x41, 0x67, 0x45, 0x57, 0x4b, 0x47, 0x68, 0x30, 0x64, 0x48, 0x41, + 0x36, 0x4c, 0x79, 0x39, 0x33, 0x64, 0x33, 0x63, 0x75, 0x63, 0x33, 0x52, + 0x68, 0x63, 0x6e, 0x52, 0x7a, 0x63, 0x32, 0x77, 0x75, 0x59, 0x32, 0x39, + 0x74, 0x4c, 0x32, 0x6c, 0x75, 0x64, 0x47, 0x56, 0x79, 0x62, 0x57, 0x56, + 0x6b, 0x0a, 0x61, 0x57, 0x46, 0x30, 0x5a, 0x53, 0x35, 0x77, 0x5a, 0x47, + 0x59, 0x77, 0x67, 0x63, 0x38, 0x47, 0x43, 0x43, 0x73, 0x47, 0x41, 0x51, + 0x55, 0x46, 0x42, 0x77, 0x49, 0x43, 0x4d, 0x49, 0x48, 0x43, 0x4d, 0x43, + 0x63, 0x57, 0x49, 0x46, 0x4e, 0x30, 0x59, 0x58, 0x4a, 0x30, 0x49, 0x45, + 0x4e, 0x76, 0x62, 0x57, 0x31, 0x6c, 0x63, 0x6d, 0x4e, 0x70, 0x59, 0x57, + 0x77, 0x67, 0x4b, 0x46, 0x4e, 0x30, 0x0a, 0x59, 0x58, 0x4a, 0x30, 0x51, + 0x32, 0x39, 0x74, 0x4b, 0x53, 0x42, 0x4d, 0x64, 0x47, 0x51, 0x75, 0x4d, + 0x41, 0x4d, 0x43, 0x41, 0x51, 0x45, 0x61, 0x67, 0x5a, 0x5a, 0x4d, 0x61, + 0x57, 0x31, 0x70, 0x64, 0x47, 0x56, 0x6b, 0x49, 0x45, 0x78, 0x70, 0x59, + 0x57, 0x4a, 0x70, 0x62, 0x47, 0x6c, 0x30, 0x65, 0x53, 0x77, 0x67, 0x63, + 0x6d, 0x56, 0x68, 0x5a, 0x43, 0x42, 0x30, 0x61, 0x47, 0x55, 0x67, 0x0a, + 0x63, 0x32, 0x56, 0x6a, 0x64, 0x47, 0x6c, 0x76, 0x62, 0x69, 0x41, 0x71, + 0x54, 0x47, 0x56, 0x6e, 0x59, 0x57, 0x77, 0x67, 0x54, 0x47, 0x6c, 0x74, + 0x61, 0x58, 0x52, 0x68, 0x64, 0x47, 0x6c, 0x76, 0x62, 0x6e, 0x4d, 0x71, + 0x49, 0x47, 0x39, 0x6d, 0x49, 0x48, 0x52, 0x6f, 0x5a, 0x53, 0x42, 0x54, + 0x64, 0x47, 0x46, 0x79, 0x64, 0x45, 0x4e, 0x76, 0x62, 0x53, 0x42, 0x44, + 0x5a, 0x58, 0x4a, 0x30, 0x0a, 0x61, 0x57, 0x5a, 0x70, 0x59, 0x32, 0x46, + 0x30, 0x61, 0x57, 0x39, 0x75, 0x49, 0x45, 0x46, 0x31, 0x64, 0x47, 0x68, + 0x76, 0x63, 0x6d, 0x6c, 0x30, 0x65, 0x53, 0x42, 0x51, 0x62, 0x32, 0x78, + 0x70, 0x59, 0x33, 0x6b, 0x67, 0x59, 0x58, 0x5a, 0x68, 0x61, 0x57, 0x78, + 0x68, 0x59, 0x6d, 0x78, 0x6c, 0x49, 0x47, 0x46, 0x30, 0x49, 0x47, 0x68, + 0x30, 0x64, 0x48, 0x41, 0x36, 0x4c, 0x79, 0x39, 0x33, 0x0a, 0x64, 0x33, + 0x63, 0x75, 0x63, 0x33, 0x52, 0x68, 0x63, 0x6e, 0x52, 0x7a, 0x63, 0x32, + 0x77, 0x75, 0x59, 0x32, 0x39, 0x74, 0x4c, 0x33, 0x42, 0x76, 0x62, 0x47, + 0x6c, 0x6a, 0x65, 0x53, 0x35, 0x77, 0x5a, 0x47, 0x59, 0x77, 0x45, 0x51, + 0x59, 0x4a, 0x59, 0x49, 0x5a, 0x49, 0x41, 0x59, 0x62, 0x34, 0x51, 0x67, + 0x45, 0x42, 0x42, 0x41, 0x51, 0x44, 0x41, 0x67, 0x41, 0x48, 0x4d, 0x44, + 0x67, 0x47, 0x0a, 0x43, 0x57, 0x43, 0x47, 0x53, 0x41, 0x47, 0x47, 0x2b, + 0x45, 0x49, 0x42, 0x44, 0x51, 0x51, 0x72, 0x46, 0x69, 0x6c, 0x54, 0x64, + 0x47, 0x46, 0x79, 0x64, 0x45, 0x4e, 0x76, 0x62, 0x53, 0x42, 0x47, 0x63, + 0x6d, 0x56, 0x6c, 0x49, 0x46, 0x4e, 0x54, 0x54, 0x43, 0x42, 0x44, 0x5a, + 0x58, 0x4a, 0x30, 0x61, 0x57, 0x5a, 0x70, 0x59, 0x32, 0x46, 0x30, 0x61, + 0x57, 0x39, 0x75, 0x49, 0x45, 0x46, 0x31, 0x0a, 0x64, 0x47, 0x68, 0x76, + 0x63, 0x6d, 0x6c, 0x30, 0x65, 0x54, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, + 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x73, 0x46, + 0x41, 0x41, 0x4f, 0x43, 0x41, 0x67, 0x45, 0x41, 0x6a, 0x6f, 0x2f, 0x6e, + 0x33, 0x4a, 0x52, 0x35, 0x66, 0x50, 0x47, 0x46, 0x66, 0x35, 0x39, 0x4a, + 0x62, 0x32, 0x76, 0x4b, 0x58, 0x66, 0x75, 0x4d, 0x2f, 0x67, 0x54, 0x46, + 0x0a, 0x77, 0x57, 0x4c, 0x52, 0x66, 0x55, 0x4b, 0x4b, 0x76, 0x46, 0x4f, + 0x33, 0x6c, 0x41, 0x4e, 0x6d, 0x4d, 0x44, 0x2b, 0x78, 0x35, 0x77, 0x71, + 0x6e, 0x55, 0x43, 0x42, 0x56, 0x4a, 0x58, 0x39, 0x32, 0x65, 0x68, 0x51, + 0x4e, 0x36, 0x77, 0x51, 0x4f, 0x51, 0x4f, 0x59, 0x2b, 0x32, 0x49, 0x69, + 0x72, 0x42, 0x79, 0x65, 0x44, 0x71, 0x58, 0x57, 0x6d, 0x4e, 0x33, 0x50, + 0x48, 0x2f, 0x55, 0x76, 0x53, 0x0a, 0x54, 0x61, 0x30, 0x58, 0x51, 0x4d, + 0x68, 0x47, 0x76, 0x6a, 0x74, 0x2f, 0x55, 0x66, 0x7a, 0x44, 0x74, 0x67, + 0x55, 0x78, 0x33, 0x4d, 0x32, 0x46, 0x49, 0x6b, 0x35, 0x78, 0x74, 0x2f, + 0x4a, 0x78, 0x58, 0x72, 0x41, 0x61, 0x78, 0x72, 0x71, 0x54, 0x69, 0x33, + 0x69, 0x53, 0x53, 0x6f, 0x58, 0x34, 0x65, 0x41, 0x2b, 0x44, 0x2f, 0x69, + 0x2b, 0x74, 0x4c, 0x50, 0x66, 0x6b, 0x70, 0x4c, 0x73, 0x74, 0x0a, 0x30, + 0x4f, 0x63, 0x4e, 0x4f, 0x72, 0x67, 0x2b, 0x7a, 0x76, 0x5a, 0x34, 0x39, + 0x71, 0x35, 0x48, 0x4a, 0x4d, 0x71, 0x6a, 0x4e, 0x54, 0x62, 0x4f, 0x78, + 0x38, 0x61, 0x48, 0x6d, 0x4e, 0x72, 0x73, 0x2b, 0x2b, 0x6d, 0x79, 0x7a, + 0x69, 0x65, 0x62, 0x69, 0x4d, 0x4d, 0x45, 0x6f, 0x66, 0x59, 0x4c, 0x57, + 0x57, 0x69, 0x76, 0x79, 0x64, 0x73, 0x51, 0x44, 0x30, 0x33, 0x32, 0x5a, + 0x47, 0x4e, 0x63, 0x0a, 0x70, 0x52, 0x4a, 0x76, 0x6b, 0x72, 0x4b, 0x54, + 0x6c, 0x4d, 0x65, 0x49, 0x46, 0x77, 0x36, 0x54, 0x74, 0x6e, 0x35, 0x69, + 0x69, 0x35, 0x42, 0x2f, 0x71, 0x30, 0x36, 0x66, 0x2f, 0x4f, 0x4e, 0x31, + 0x46, 0x45, 0x38, 0x71, 0x4d, 0x74, 0x39, 0x62, 0x44, 0x65, 0x44, 0x31, + 0x65, 0x35, 0x4d, 0x4e, 0x71, 0x36, 0x48, 0x50, 0x68, 0x2b, 0x47, 0x6c, + 0x42, 0x45, 0x58, 0x6f, 0x50, 0x42, 0x4b, 0x6c, 0x0a, 0x43, 0x63, 0x57, + 0x77, 0x30, 0x62, 0x64, 0x54, 0x38, 0x32, 0x41, 0x55, 0x75, 0x6f, 0x56, + 0x70, 0x61, 0x69, 0x46, 0x38, 0x48, 0x33, 0x56, 0x68, 0x46, 0x79, 0x41, + 0x58, 0x65, 0x32, 0x77, 0x37, 0x51, 0x53, 0x6c, 0x63, 0x34, 0x61, 0x78, + 0x61, 0x30, 0x63, 0x32, 0x4d, 0x6d, 0x2b, 0x74, 0x67, 0x48, 0x52, 0x6e, + 0x73, 0x39, 0x2b, 0x57, 0x77, 0x32, 0x76, 0x6c, 0x35, 0x47, 0x4b, 0x56, + 0x46, 0x0a, 0x50, 0x30, 0x6c, 0x44, 0x56, 0x39, 0x4c, 0x64, 0x4a, 0x4e, + 0x55, 0x73, 0x6f, 0x2f, 0x32, 0x52, 0x6a, 0x53, 0x65, 0x31, 0x35, 0x65, + 0x73, 0x55, 0x42, 0x70, 0x70, 0x4d, 0x65, 0x79, 0x47, 0x37, 0x4f, 0x71, + 0x30, 0x77, 0x42, 0x68, 0x6a, 0x41, 0x32, 0x4d, 0x46, 0x72, 0x4c, 0x48, + 0x39, 0x5a, 0x58, 0x46, 0x32, 0x52, 0x73, 0x58, 0x41, 0x69, 0x56, 0x2b, + 0x75, 0x4b, 0x61, 0x30, 0x68, 0x4b, 0x0a, 0x31, 0x51, 0x38, 0x70, 0x37, + 0x4d, 0x5a, 0x41, 0x77, 0x43, 0x2b, 0x49, 0x54, 0x47, 0x67, 0x42, 0x46, + 0x33, 0x66, 0x30, 0x4a, 0x42, 0x6c, 0x50, 0x76, 0x66, 0x72, 0x68, 0x73, + 0x69, 0x41, 0x68, 0x53, 0x39, 0x30, 0x61, 0x32, 0x43, 0x6c, 0x39, 0x71, + 0x72, 0x6a, 0x65, 0x56, 0x4f, 0x77, 0x68, 0x56, 0x59, 0x42, 0x73, 0x48, + 0x76, 0x55, 0x77, 0x79, 0x4b, 0x4d, 0x51, 0x35, 0x62, 0x4c, 0x6d, 0x0a, + 0x4b, 0x68, 0x51, 0x78, 0x77, 0x34, 0x55, 0x74, 0x6a, 0x4a, 0x69, 0x78, + 0x68, 0x6c, 0x70, 0x50, 0x69, 0x56, 0x6b, 0x74, 0x75, 0x63, 0x66, 0x33, + 0x48, 0x4d, 0x69, 0x4b, 0x66, 0x38, 0x43, 0x64, 0x42, 0x55, 0x72, 0x6d, + 0x51, 0x6b, 0x39, 0x69, 0x6f, 0x32, 0x30, 0x70, 0x70, 0x42, 0x2b, 0x46, + 0x71, 0x39, 0x76, 0x6c, 0x67, 0x63, 0x69, 0x74, 0x4b, 0x6a, 0x31, 0x4d, + 0x58, 0x56, 0x75, 0x45, 0x0a, 0x4a, 0x6e, 0x48, 0x45, 0x68, 0x56, 0x35, + 0x78, 0x4a, 0x4d, 0x71, 0x6c, 0x47, 0x32, 0x7a, 0x59, 0x59, 0x64, 0x4d, + 0x61, 0x34, 0x46, 0x54, 0x62, 0x7a, 0x72, 0x71, 0x70, 0x4d, 0x72, 0x55, + 0x69, 0x39, 0x6e, 0x4e, 0x42, 0x43, 0x56, 0x32, 0x34, 0x46, 0x31, 0x30, + 0x4f, 0x44, 0x35, 0x6d, 0x51, 0x31, 0x6b, 0x66, 0x61, 0x62, 0x77, 0x6f, + 0x36, 0x59, 0x69, 0x67, 0x55, 0x5a, 0x34, 0x4c, 0x5a, 0x0a, 0x38, 0x64, + 0x43, 0x41, 0x57, 0x5a, 0x76, 0x4c, 0x4d, 0x64, 0x69, 0x62, 0x44, 0x34, + 0x78, 0x33, 0x54, 0x72, 0x56, 0x6f, 0x69, 0x76, 0x4a, 0x73, 0x39, 0x69, + 0x51, 0x4f, 0x4c, 0x57, 0x78, 0x77, 0x78, 0x58, 0x50, 0x52, 0x33, 0x68, + 0x54, 0x51, 0x63, 0x59, 0x2b, 0x32, 0x30, 0x33, 0x73, 0x43, 0x39, 0x75, + 0x4f, 0x34, 0x31, 0x41, 0x6c, 0x75, 0x61, 0x35, 0x35, 0x31, 0x68, 0x44, + 0x6e, 0x6d, 0x0a, 0x66, 0x79, 0x57, 0x6c, 0x38, 0x6b, 0x67, 0x41, 0x77, + 0x4b, 0x51, 0x42, 0x32, 0x6a, 0x38, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, + 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x0a, 0x23, + 0x20, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x3a, 0x20, 0x43, 0x4e, 0x3d, + 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x43, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x47, 0x32, 0x20, + 0x4f, 0x3d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x4c, + 0x74, 0x64, 0x2e, 0x0a, 0x23, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x3a, 0x20, 0x43, 0x4e, 0x3d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, + 0x6f, 0x6d, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x74, 0x79, 0x20, 0x47, 0x32, 0x20, 0x4f, 0x3d, 0x53, 0x74, 0x61, 0x72, + 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x4c, 0x74, 0x64, 0x2e, 0x0a, 0x23, 0x20, + 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x53, 0x74, 0x61, 0x72, + 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x74, 0x79, 0x20, 0x47, 0x32, 0x22, 0x0a, 0x23, 0x20, 0x53, + 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x35, 0x39, 0x0a, 0x23, 0x20, + 0x4d, 0x44, 0x35, 0x20, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, + 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x37, 0x38, 0x3a, 0x34, 0x62, 0x3a, 0x66, + 0x62, 0x3a, 0x39, 0x65, 0x3a, 0x36, 0x34, 0x3a, 0x38, 0x32, 0x3a, 0x30, + 0x61, 0x3a, 0x64, 0x33, 0x3a, 0x62, 0x38, 0x3a, 0x34, 0x63, 0x3a, 0x36, + 0x32, 0x3a, 0x66, 0x33, 0x3a, 0x36, 0x34, 0x3a, 0x66, 0x32, 0x3a, 0x39, + 0x30, 0x3a, 0x36, 0x34, 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x31, 0x20, + 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, + 0x20, 0x33, 0x31, 0x3a, 0x66, 0x31, 0x3a, 0x66, 0x64, 0x3a, 0x36, 0x38, + 0x3a, 0x32, 0x32, 0x3a, 0x36, 0x33, 0x3a, 0x32, 0x30, 0x3a, 0x65, 0x65, + 0x3a, 0x63, 0x36, 0x3a, 0x33, 0x62, 0x3a, 0x33, 0x66, 0x3a, 0x39, 0x64, + 0x3a, 0x65, 0x61, 0x3a, 0x34, 0x61, 0x3a, 0x33, 0x65, 0x3a, 0x35, 0x33, + 0x3a, 0x37, 0x63, 0x3a, 0x37, 0x63, 0x3a, 0x33, 0x39, 0x3a, 0x31, 0x37, + 0x0a, 0x23, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x46, 0x69, + 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x63, + 0x37, 0x3a, 0x62, 0x61, 0x3a, 0x36, 0x35, 0x3a, 0x36, 0x37, 0x3a, 0x64, + 0x65, 0x3a, 0x39, 0x33, 0x3a, 0x61, 0x37, 0x3a, 0x39, 0x38, 0x3a, 0x61, + 0x65, 0x3a, 0x31, 0x66, 0x3a, 0x61, 0x61, 0x3a, 0x37, 0x39, 0x3a, 0x31, + 0x65, 0x3a, 0x37, 0x31, 0x3a, 0x32, 0x64, 0x3a, 0x33, 0x37, 0x3a, 0x38, + 0x66, 0x3a, 0x61, 0x65, 0x3a, 0x31, 0x66, 0x3a, 0x39, 0x33, 0x3a, 0x63, + 0x34, 0x3a, 0x33, 0x39, 0x3a, 0x37, 0x66, 0x3a, 0x65, 0x61, 0x3a, 0x34, + 0x34, 0x3a, 0x31, 0x62, 0x3a, 0x62, 0x37, 0x3a, 0x63, 0x62, 0x3a, 0x65, + 0x36, 0x3a, 0x66, 0x64, 0x3a, 0x35, 0x39, 0x3a, 0x39, 0x35, 0x0a, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, + 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x46, 0x59, 0x7a, 0x43, 0x43, 0x41, + 0x30, 0x75, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x42, 0x4f, + 0x7a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, + 0x77, 0x30, 0x42, 0x41, 0x51, 0x73, 0x46, 0x41, 0x44, 0x42, 0x54, 0x4d, + 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, + 0x77, 0x4a, 0x4a, 0x54, 0x44, 0x45, 0x57, 0x0a, 0x4d, 0x42, 0x51, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4e, 0x55, 0x33, 0x52, 0x68, + 0x63, 0x6e, 0x52, 0x44, 0x62, 0x32, 0x30, 0x67, 0x54, 0x48, 0x52, 0x6b, + 0x4c, 0x6a, 0x45, 0x73, 0x4d, 0x43, 0x6f, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x41, 0x78, 0x4d, 0x6a, 0x55, 0x33, 0x52, 0x68, 0x63, 0x6e, 0x52, 0x44, + 0x62, 0x32, 0x30, 0x67, 0x51, 0x32, 0x56, 0x79, 0x64, 0x47, 0x6c, 0x6d, + 0x0a, 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, 0x6c, 0x76, 0x62, 0x69, 0x42, + 0x42, 0x64, 0x58, 0x52, 0x6f, 0x62, 0x33, 0x4a, 0x70, 0x64, 0x48, 0x6b, + 0x67, 0x52, 0x7a, 0x49, 0x77, 0x48, 0x68, 0x63, 0x4e, 0x4d, 0x54, 0x41, + 0x77, 0x4d, 0x54, 0x41, 0x78, 0x4d, 0x44, 0x45, 0x77, 0x4d, 0x44, 0x41, + 0x78, 0x57, 0x68, 0x63, 0x4e, 0x4d, 0x7a, 0x6b, 0x78, 0x4d, 0x6a, 0x4d, + 0x78, 0x4d, 0x6a, 0x4d, 0x31, 0x0a, 0x4f, 0x54, 0x41, 0x78, 0x57, 0x6a, + 0x42, 0x54, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, + 0x51, 0x47, 0x45, 0x77, 0x4a, 0x4a, 0x54, 0x44, 0x45, 0x57, 0x4d, 0x42, + 0x51, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4e, 0x55, 0x33, + 0x52, 0x68, 0x63, 0x6e, 0x52, 0x44, 0x62, 0x32, 0x30, 0x67, 0x54, 0x48, + 0x52, 0x6b, 0x4c, 0x6a, 0x45, 0x73, 0x4d, 0x43, 0x6f, 0x47, 0x0a, 0x41, + 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x6a, 0x55, 0x33, 0x52, 0x68, 0x63, + 0x6e, 0x52, 0x44, 0x62, 0x32, 0x30, 0x67, 0x51, 0x32, 0x56, 0x79, 0x64, + 0x47, 0x6c, 0x6d, 0x61, 0x57, 0x4e, 0x68, 0x64, 0x47, 0x6c, 0x76, 0x62, + 0x69, 0x42, 0x42, 0x64, 0x58, 0x52, 0x6f, 0x62, 0x33, 0x4a, 0x70, 0x64, + 0x48, 0x6b, 0x67, 0x52, 0x7a, 0x49, 0x77, 0x67, 0x67, 0x49, 0x69, 0x4d, + 0x41, 0x30, 0x47, 0x0a, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, + 0x44, 0x51, 0x45, 0x42, 0x41, 0x51, 0x55, 0x41, 0x41, 0x34, 0x49, 0x43, + 0x44, 0x77, 0x41, 0x77, 0x67, 0x67, 0x49, 0x4b, 0x41, 0x6f, 0x49, 0x43, + 0x41, 0x51, 0x43, 0x32, 0x69, 0x54, 0x5a, 0x62, 0x42, 0x37, 0x63, 0x67, + 0x4e, 0x72, 0x32, 0x43, 0x75, 0x2b, 0x45, 0x57, 0x49, 0x41, 0x4f, 0x56, + 0x65, 0x71, 0x38, 0x4f, 0x6f, 0x31, 0x58, 0x4a, 0x0a, 0x4a, 0x5a, 0x6c, + 0x4b, 0x78, 0x64, 0x42, 0x57, 0x51, 0x59, 0x65, 0x51, 0x54, 0x53, 0x46, + 0x67, 0x70, 0x42, 0x53, 0x48, 0x4f, 0x38, 0x33, 0x39, 0x73, 0x6a, 0x36, + 0x30, 0x5a, 0x77, 0x4e, 0x71, 0x37, 0x65, 0x45, 0x50, 0x53, 0x38, 0x43, + 0x52, 0x68, 0x58, 0x42, 0x46, 0x34, 0x45, 0x4b, 0x65, 0x33, 0x69, 0x6b, + 0x6a, 0x31, 0x41, 0x45, 0x4e, 0x6f, 0x42, 0x42, 0x35, 0x75, 0x4e, 0x73, + 0x44, 0x0a, 0x76, 0x66, 0x4f, 0x70, 0x4c, 0x39, 0x48, 0x47, 0x34, 0x41, + 0x2f, 0x4c, 0x6e, 0x6f, 0x6f, 0x55, 0x43, 0x72, 0x69, 0x39, 0x39, 0x6c, + 0x5a, 0x69, 0x38, 0x63, 0x56, 0x79, 0x74, 0x6a, 0x49, 0x6c, 0x32, 0x62, + 0x4c, 0x7a, 0x76, 0x57, 0x58, 0x46, 0x44, 0x53, 0x78, 0x75, 0x31, 0x5a, + 0x4a, 0x76, 0x47, 0x49, 0x73, 0x41, 0x51, 0x52, 0x53, 0x43, 0x62, 0x30, + 0x41, 0x67, 0x4a, 0x6e, 0x6f, 0x6f, 0x0a, 0x44, 0x2f, 0x55, 0x65, 0x66, + 0x79, 0x66, 0x33, 0x6c, 0x4c, 0x45, 0x33, 0x50, 0x62, 0x66, 0x48, 0x6b, + 0x66, 0x66, 0x69, 0x41, 0x65, 0x7a, 0x39, 0x6c, 0x49, 0x6e, 0x68, 0x7a, + 0x47, 0x37, 0x54, 0x4e, 0x74, 0x59, 0x4b, 0x47, 0x58, 0x6d, 0x75, 0x31, + 0x7a, 0x53, 0x43, 0x5a, 0x66, 0x39, 0x38, 0x51, 0x72, 0x75, 0x32, 0x33, + 0x51, 0x75, 0x6d, 0x4e, 0x4b, 0x39, 0x4c, 0x59, 0x50, 0x35, 0x2f, 0x0a, + 0x51, 0x30, 0x6b, 0x47, 0x69, 0x34, 0x78, 0x44, 0x75, 0x46, 0x62, 0x79, + 0x32, 0x58, 0x38, 0x68, 0x51, 0x78, 0x66, 0x71, 0x70, 0x30, 0x69, 0x56, + 0x41, 0x58, 0x56, 0x31, 0x36, 0x69, 0x75, 0x6c, 0x51, 0x35, 0x58, 0x71, + 0x46, 0x59, 0x53, 0x64, 0x43, 0x49, 0x30, 0x6d, 0x62, 0x6c, 0x57, 0x62, + 0x71, 0x39, 0x7a, 0x53, 0x4f, 0x64, 0x49, 0x78, 0x48, 0x57, 0x44, 0x69, + 0x72, 0x4d, 0x78, 0x57, 0x0a, 0x52, 0x53, 0x54, 0x31, 0x48, 0x46, 0x53, + 0x72, 0x37, 0x6f, 0x62, 0x64, 0x6c, 0x6a, 0x4b, 0x46, 0x2b, 0x45, 0x78, + 0x50, 0x36, 0x4a, 0x56, 0x32, 0x74, 0x67, 0x58, 0x64, 0x4e, 0x69, 0x4e, + 0x6e, 0x76, 0x50, 0x38, 0x56, 0x34, 0x73, 0x6f, 0x37, 0x35, 0x71, 0x62, + 0x73, 0x4f, 0x2b, 0x77, 0x6d, 0x45, 0x54, 0x52, 0x49, 0x6a, 0x66, 0x61, + 0x41, 0x4b, 0x78, 0x6f, 0x6a, 0x41, 0x75, 0x75, 0x4b, 0x0a, 0x48, 0x44, + 0x70, 0x32, 0x4b, 0x6e, 0x74, 0x57, 0x46, 0x68, 0x78, 0x79, 0x4b, 0x72, + 0x4f, 0x71, 0x34, 0x32, 0x43, 0x6c, 0x41, 0x4a, 0x38, 0x45, 0x6d, 0x2b, + 0x4a, 0x76, 0x48, 0x68, 0x52, 0x59, 0x57, 0x36, 0x56, 0x73, 0x69, 0x31, + 0x67, 0x38, 0x77, 0x37, 0x70, 0x4f, 0x4f, 0x6c, 0x7a, 0x33, 0x34, 0x5a, + 0x59, 0x72, 0x50, 0x75, 0x38, 0x48, 0x76, 0x4b, 0x54, 0x6c, 0x58, 0x63, + 0x78, 0x4e, 0x0a, 0x6e, 0x77, 0x33, 0x68, 0x33, 0x4b, 0x71, 0x37, 0x34, + 0x57, 0x34, 0x61, 0x37, 0x49, 0x2f, 0x68, 0x74, 0x6b, 0x78, 0x4e, 0x65, + 0x58, 0x4a, 0x64, 0x46, 0x7a, 0x55, 0x4c, 0x48, 0x64, 0x66, 0x42, 0x52, + 0x39, 0x71, 0x57, 0x4a, 0x4f, 0x44, 0x51, 0x63, 0x71, 0x68, 0x61, 0x58, + 0x32, 0x59, 0x74, 0x45, 0x4e, 0x77, 0x76, 0x4b, 0x68, 0x4f, 0x75, 0x4a, + 0x76, 0x34, 0x4b, 0x48, 0x42, 0x6e, 0x4d, 0x0a, 0x30, 0x44, 0x34, 0x4c, + 0x6e, 0x4d, 0x67, 0x4a, 0x4c, 0x76, 0x6c, 0x62, 0x6c, 0x6e, 0x70, 0x48, + 0x6e, 0x4f, 0x6c, 0x36, 0x38, 0x77, 0x56, 0x51, 0x64, 0x4a, 0x56, 0x7a, + 0x6e, 0x6a, 0x41, 0x4a, 0x38, 0x35, 0x65, 0x43, 0x58, 0x75, 0x61, 0x50, + 0x4f, 0x51, 0x67, 0x65, 0x57, 0x65, 0x55, 0x31, 0x46, 0x45, 0x49, 0x54, + 0x2f, 0x77, 0x43, 0x63, 0x39, 0x37, 0x36, 0x71, 0x55, 0x4d, 0x2f, 0x69, + 0x0a, 0x55, 0x55, 0x6a, 0x58, 0x75, 0x47, 0x2b, 0x76, 0x2b, 0x45, 0x35, + 0x2b, 0x4d, 0x35, 0x69, 0x53, 0x46, 0x47, 0x49, 0x36, 0x64, 0x57, 0x50, + 0x50, 0x65, 0x2f, 0x72, 0x65, 0x67, 0x6a, 0x75, 0x70, 0x75, 0x7a, 0x6e, + 0x69, 0x78, 0x4c, 0x30, 0x73, 0x41, 0x41, 0x37, 0x49, 0x46, 0x36, 0x77, + 0x54, 0x37, 0x30, 0x30, 0x6c, 0x6a, 0x74, 0x69, 0x7a, 0x6b, 0x43, 0x2b, + 0x70, 0x32, 0x69, 0x6c, 0x39, 0x0a, 0x48, 0x61, 0x39, 0x30, 0x4f, 0x72, + 0x49, 0x6e, 0x77, 0x4d, 0x45, 0x65, 0x50, 0x6e, 0x57, 0x6a, 0x46, 0x71, + 0x6d, 0x76, 0x65, 0x69, 0x4a, 0x64, 0x6e, 0x78, 0x4d, 0x61, 0x7a, 0x36, + 0x65, 0x67, 0x36, 0x2b, 0x4f, 0x47, 0x43, 0x74, 0x50, 0x39, 0x35, 0x70, + 0x61, 0x56, 0x31, 0x79, 0x50, 0x49, 0x4e, 0x39, 0x33, 0x45, 0x66, 0x4b, + 0x6f, 0x32, 0x72, 0x4a, 0x67, 0x61, 0x45, 0x72, 0x48, 0x67, 0x0a, 0x54, + 0x75, 0x69, 0x78, 0x4f, 0x2f, 0x58, 0x57, 0x62, 0x2f, 0x45, 0x77, 0x31, + 0x77, 0x49, 0x44, 0x41, 0x51, 0x41, 0x42, 0x6f, 0x30, 0x49, 0x77, 0x51, + 0x44, 0x41, 0x50, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x52, 0x4d, 0x42, 0x41, + 0x66, 0x38, 0x45, 0x42, 0x54, 0x41, 0x44, 0x41, 0x51, 0x48, 0x2f, 0x4d, + 0x41, 0x34, 0x47, 0x41, 0x31, 0x55, 0x64, 0x44, 0x77, 0x45, 0x42, 0x2f, + 0x77, 0x51, 0x45, 0x0a, 0x41, 0x77, 0x49, 0x42, 0x42, 0x6a, 0x41, 0x64, + 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x34, 0x45, 0x46, 0x67, 0x51, 0x55, + 0x53, 0x38, 0x57, 0x30, 0x51, 0x47, 0x75, 0x74, 0x48, 0x4c, 0x4f, 0x6c, + 0x48, 0x47, 0x56, 0x75, 0x52, 0x6a, 0x61, 0x4a, 0x68, 0x77, 0x55, 0x4d, + 0x44, 0x72, 0x59, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, + 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x4c, 0x0a, 0x42, 0x51, 0x41, + 0x44, 0x67, 0x67, 0x49, 0x42, 0x41, 0x48, 0x4e, 0x58, 0x50, 0x79, 0x7a, + 0x56, 0x6c, 0x54, 0x4a, 0x2b, 0x4e, 0x39, 0x75, 0x57, 0x6b, 0x75, 0x73, + 0x5a, 0x58, 0x6e, 0x35, 0x54, 0x35, 0x30, 0x48, 0x73, 0x45, 0x62, 0x5a, + 0x48, 0x37, 0x37, 0x58, 0x65, 0x37, 0x58, 0x52, 0x63, 0x78, 0x66, 0x47, + 0x4f, 0x53, 0x65, 0x44, 0x38, 0x62, 0x70, 0x6b, 0x54, 0x7a, 0x5a, 0x2b, + 0x4b, 0x0a, 0x32, 0x73, 0x30, 0x36, 0x43, 0x74, 0x67, 0x36, 0x57, 0x67, + 0x6b, 0x2f, 0x58, 0x7a, 0x54, 0x51, 0x4c, 0x77, 0x50, 0x53, 0x5a, 0x68, + 0x30, 0x61, 0x76, 0x5a, 0x79, 0x51, 0x4e, 0x38, 0x67, 0x4d, 0x6a, 0x67, + 0x64, 0x61, 0x6c, 0x45, 0x56, 0x47, 0x4b, 0x75, 0x61, 0x2b, 0x65, 0x74, + 0x71, 0x68, 0x71, 0x61, 0x52, 0x70, 0x45, 0x70, 0x4b, 0x77, 0x66, 0x54, + 0x62, 0x55, 0x52, 0x49, 0x66, 0x58, 0x0a, 0x55, 0x66, 0x45, 0x70, 0x59, + 0x39, 0x5a, 0x31, 0x7a, 0x52, 0x62, 0x6b, 0x4a, 0x34, 0x6b, 0x64, 0x2b, + 0x4d, 0x49, 0x79, 0x53, 0x50, 0x33, 0x62, 0x6d, 0x64, 0x43, 0x50, 0x58, + 0x31, 0x52, 0x30, 0x7a, 0x4b, 0x78, 0x6e, 0x4e, 0x42, 0x46, 0x69, 0x32, + 0x51, 0x77, 0x4b, 0x4e, 0x34, 0x66, 0x52, 0x6f, 0x78, 0x64, 0x49, 0x6a, + 0x74, 0x49, 0x58, 0x48, 0x66, 0x62, 0x58, 0x2f, 0x64, 0x74, 0x6c, 0x0a, + 0x36, 0x2f, 0x32, 0x6f, 0x31, 0x50, 0x58, 0x57, 0x54, 0x36, 0x52, 0x62, + 0x64, 0x65, 0x6a, 0x46, 0x30, 0x6d, 0x43, 0x79, 0x32, 0x77, 0x6c, 0x2b, + 0x4a, 0x59, 0x74, 0x37, 0x75, 0x6c, 0x4b, 0x53, 0x6e, 0x6a, 0x37, 0x6f, + 0x78, 0x58, 0x65, 0x68, 0x50, 0x4f, 0x42, 0x4b, 0x63, 0x32, 0x74, 0x68, + 0x7a, 0x34, 0x62, 0x63, 0x51, 0x2f, 0x2f, 0x2f, 0x49, 0x66, 0x34, 0x6a, + 0x58, 0x53, 0x52, 0x4b, 0x0a, 0x39, 0x64, 0x4e, 0x74, 0x44, 0x32, 0x49, + 0x45, 0x42, 0x56, 0x65, 0x43, 0x32, 0x6d, 0x36, 0x6b, 0x4d, 0x79, 0x56, + 0x35, 0x53, 0x79, 0x35, 0x55, 0x47, 0x59, 0x76, 0x4d, 0x4c, 0x44, 0x30, + 0x77, 0x36, 0x64, 0x45, 0x47, 0x2f, 0x2b, 0x67, 0x79, 0x52, 0x72, 0x36, + 0x31, 0x4d, 0x33, 0x5a, 0x33, 0x71, 0x41, 0x46, 0x64, 0x6c, 0x73, 0x48, + 0x42, 0x31, 0x62, 0x36, 0x75, 0x4a, 0x63, 0x44, 0x4a, 0x0a, 0x48, 0x67, + 0x6f, 0x4a, 0x49, 0x49, 0x69, 0x68, 0x44, 0x73, 0x6e, 0x7a, 0x62, 0x30, + 0x32, 0x43, 0x56, 0x41, 0x41, 0x67, 0x70, 0x39, 0x4b, 0x50, 0x35, 0x44, + 0x6c, 0x55, 0x46, 0x79, 0x36, 0x4e, 0x48, 0x72, 0x67, 0x62, 0x75, 0x78, + 0x75, 0x39, 0x6d, 0x6b, 0x34, 0x37, 0x45, 0x44, 0x54, 0x63, 0x6e, 0x49, + 0x68, 0x54, 0x37, 0x36, 0x49, 0x78, 0x57, 0x31, 0x68, 0x50, 0x6b, 0x57, + 0x4c, 0x49, 0x0a, 0x77, 0x70, 0x71, 0x61, 0x7a, 0x52, 0x56, 0x64, 0x4f, + 0x4b, 0x6e, 0x57, 0x76, 0x76, 0x67, 0x54, 0x74, 0x5a, 0x38, 0x53, 0x61, + 0x66, 0x4a, 0x51, 0x59, 0x71, 0x7a, 0x37, 0x46, 0x7a, 0x66, 0x30, 0x37, + 0x72, 0x68, 0x31, 0x5a, 0x32, 0x41, 0x51, 0x2b, 0x34, 0x4e, 0x51, 0x2b, + 0x55, 0x53, 0x31, 0x64, 0x5a, 0x78, 0x41, 0x46, 0x37, 0x4c, 0x2b, 0x2f, + 0x58, 0x6c, 0x64, 0x62, 0x6c, 0x68, 0x59, 0x0a, 0x58, 0x7a, 0x44, 0x38, + 0x41, 0x4b, 0x36, 0x76, 0x4d, 0x38, 0x45, 0x4f, 0x54, 0x6d, 0x79, 0x36, + 0x70, 0x36, 0x61, 0x68, 0x66, 0x7a, 0x4c, 0x62, 0x4f, 0x4f, 0x43, 0x78, + 0x63, 0x68, 0x63, 0x4b, 0x4b, 0x35, 0x48, 0x73, 0x61, 0x6d, 0x4d, 0x6d, + 0x37, 0x59, 0x6e, 0x55, 0x65, 0x4d, 0x78, 0x30, 0x48, 0x67, 0x58, 0x34, + 0x61, 0x2f, 0x36, 0x4d, 0x61, 0x6e, 0x59, 0x35, 0x4b, 0x61, 0x35, 0x6c, + 0x0a, 0x49, 0x78, 0x4b, 0x56, 0x43, 0x43, 0x49, 0x63, 0x6c, 0x38, 0x35, + 0x62, 0x42, 0x75, 0x34, 0x4d, 0x34, 0x72, 0x75, 0x38, 0x48, 0x30, 0x53, + 0x54, 0x39, 0x74, 0x67, 0x34, 0x52, 0x51, 0x55, 0x68, 0x37, 0x65, 0x53, + 0x74, 0x71, 0x78, 0x4b, 0x32, 0x41, 0x36, 0x52, 0x43, 0x4c, 0x69, 0x33, + 0x45, 0x43, 0x54, 0x6f, 0x44, 0x5a, 0x32, 0x6d, 0x45, 0x6d, 0x75, 0x46, + 0x5a, 0x6b, 0x49, 0x6f, 0x6f, 0x0a, 0x68, 0x64, 0x56, 0x64, 0x64, 0x4c, + 0x48, 0x52, 0x44, 0x69, 0x42, 0x59, 0x6d, 0x78, 0x4f, 0x6c, 0x73, 0x47, + 0x4f, 0x6d, 0x37, 0x58, 0x74, 0x48, 0x2f, 0x55, 0x56, 0x56, 0x4d, 0x4b, + 0x54, 0x75, 0x6d, 0x74, 0x54, 0x6d, 0x34, 0x6f, 0x66, 0x76, 0x6d, 0x4d, + 0x6b, 0x79, 0x67, 0x68, 0x45, 0x70, 0x49, 0x72, 0x77, 0x41, 0x43, 0x6a, + 0x46, 0x65, 0x4c, 0x51, 0x2f, 0x41, 0x6a, 0x75, 0x6c, 0x72, 0x0a, 0x73, + 0x6f, 0x38, 0x75, 0x42, 0x74, 0x6a, 0x52, 0x6b, 0x63, 0x66, 0x47, 0x45, + 0x76, 0x52, 0x4d, 0x2f, 0x54, 0x41, 0x58, 0x77, 0x38, 0x48, 0x61, 0x4f, + 0x46, 0x76, 0x6a, 0x71, 0x65, 0x72, 0x6d, 0x6f, 0x62, 0x70, 0x35, 0x37, + 0x33, 0x50, 0x59, 0x74, 0x6c, 0x4e, 0x58, 0x4c, 0x66, 0x62, 0x51, 0x34, + 0x64, 0x64, 0x49, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, + 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a}; +unsigned int grpc_google_root_certs_size = 134862; diff --git a/src/core/security/google_root_certs.h b/src/core/security/google_root_certs.h new file mode 100644 index 0000000000..4bcfaddcdb --- /dev/null +++ b/src/core/security/google_root_certs.h @@ -0,0 +1,40 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_SECURITY_GOOGLE_ROOT_CERTS_H__ +#define __GRPC_INTERNAL_SECURITY_GOOGLE_ROOT_CERTS_H__ + +extern unsigned char grpc_google_root_certs[]; +extern unsigned int grpc_google_root_certs_size; + +#endif /* __GRPC_INTERNAL_SECURITY_GOOGLE_ROOT_CERTS_H__ */ diff --git a/src/core/security/secure_transport_setup.c b/src/core/security/secure_transport_setup.c new file mode 100644 index 0000000000..bc2e469af6 --- /dev/null +++ b/src/core/security/secure_transport_setup.c @@ -0,0 +1,289 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/security/secure_transport_setup.h" + +#include <string.h> + +#include "src/core/endpoint/secure_endpoint.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/slice_buffer.h> + +#define GRPC_INITIAL_HANDSHAKE_BUFFER_SIZE 256 + +typedef struct { + grpc_security_context *ctx; + tsi_handshaker *handshaker; + unsigned char *handshake_buffer; + size_t handshake_buffer_size; + grpc_endpoint *endpoint; + gpr_slice_buffer left_overs; + grpc_secure_transport_setup_done_cb cb; + void *user_data; +} grpc_secure_transport_setup; + +static void on_handshake_data_received_from_peer(void *setup, gpr_slice *slices, + size_t nslices, + grpc_endpoint_cb_status error); + +static void on_handshake_data_sent_to_peer(void *setup, + grpc_endpoint_cb_status error); + +static void secure_transport_setup_done(grpc_secure_transport_setup *s, + int is_success) { + if (is_success) { + s->cb(s->user_data, GRPC_SECURITY_OK, s->endpoint); + } else { + if (s->endpoint != NULL) { + grpc_endpoint_shutdown(s->endpoint); + grpc_endpoint_destroy(s->endpoint); + } + s->cb(s->user_data, GRPC_SECURITY_ERROR, NULL); + } + if (s->handshaker != NULL) tsi_handshaker_destroy(s->handshaker); + if (s->handshake_buffer != NULL) gpr_free(s->handshake_buffer); + gpr_slice_buffer_destroy(&s->left_overs); + grpc_security_context_unref(s->ctx); + gpr_free(s); +} + +static void on_peer_checked(void *user_data, grpc_security_status status) { + grpc_secure_transport_setup *s = user_data; + tsi_frame_protector *protector; + tsi_result result; + if (status != GRPC_SECURITY_OK) { + gpr_log(GPR_ERROR, "Error checking peer."); + secure_transport_setup_done(s, 0); + return; + } + result = + tsi_handshaker_create_frame_protector(s->handshaker, NULL, &protector); + if (result != TSI_OK) { + gpr_log(GPR_ERROR, "Frame protector creation failed with error %s.", + tsi_result_to_string(result)); + secure_transport_setup_done(s, 0); + return; + } + s->endpoint = grpc_secure_endpoint_create( + protector, s->endpoint, s->left_overs.slices, s->left_overs.count); + secure_transport_setup_done(s, 1); + return; +} + +static void check_peer(grpc_secure_transport_setup *s) { + grpc_security_status peer_status; + tsi_peer peer; + tsi_result result = tsi_handshaker_extract_peer(s->handshaker, &peer); + if (result != TSI_OK) { + gpr_log(GPR_ERROR, "Peer extraction failed with error %s", + tsi_result_to_string(result)); + secure_transport_setup_done(s, 0); + return; + } + peer_status = + grpc_security_context_check_peer(s->ctx, &peer, on_peer_checked, s); + tsi_peer_destruct(&peer); + if (peer_status == GRPC_SECURITY_ERROR) { + gpr_log(GPR_ERROR, "Peer check failed."); + secure_transport_setup_done(s, 0); + return; + } else if (peer_status == GRPC_SECURITY_OK) { + on_peer_checked(s, peer_status); + } +} + +static void send_handshake_bytes_to_peer(grpc_secure_transport_setup *s) { + size_t offset = 0; + tsi_result result = TSI_OK; + gpr_slice to_send; + grpc_endpoint_write_status write_status; + + do { + uint32_t to_send_size = s->handshake_buffer_size - offset; + result = tsi_handshaker_get_bytes_to_send_to_peer( + s->handshaker, s->handshake_buffer + offset, &to_send_size); + offset += to_send_size; + if (result == TSI_INCOMPLETE_DATA) { + s->handshake_buffer_size *= 2; + s->handshake_buffer = + gpr_realloc(s->handshake_buffer, s->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)); + secure_transport_setup_done(s, 0); + return; + } + + to_send = + gpr_slice_from_copied_buffer((const char *)s->handshake_buffer, offset); + /* TODO(klempner,jboeuf): This should probably use the client setup + deadline */ + write_status = + grpc_endpoint_write(s->endpoint, &to_send, 1, + on_handshake_data_sent_to_peer, s, gpr_inf_future); + if (write_status == GRPC_ENDPOINT_WRITE_ERROR) { + gpr_log(GPR_ERROR, "Could not send handshake data to peer."); + secure_transport_setup_done(s, 0); + } else if (write_status == GRPC_ENDPOINT_WRITE_DONE) { + on_handshake_data_sent_to_peer(s, GRPC_ENDPOINT_CB_OK); + } +} + +static void cleanup_slices(gpr_slice *slices, size_t num_slices) { + size_t i; + for (i = 0; i < num_slices; i++) { + gpr_slice_unref(slices[i]); + } +} + +static void on_handshake_data_received_from_peer( + void *setup, gpr_slice *slices, size_t nslices, + grpc_endpoint_cb_status error) { + grpc_secure_transport_setup *s = setup; + uint32_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 (error != GRPC_ENDPOINT_CB_OK) { + gpr_log(GPR_ERROR, "Read failed."); + cleanup_slices(slices, nslices); + secure_transport_setup_done(s, 0); + return; + } + + for (i = 0; i < nslices; i++) { + consumed_slice_size = GPR_SLICE_LENGTH(slices[i]); + result = tsi_handshaker_process_bytes_from_peer( + s->handshaker, GPR_SLICE_START_PTR(slices[i]), &consumed_slice_size); + if (!tsi_handshaker_is_in_progress(s->handshaker)) break; + } + + if (tsi_handshaker_is_in_progress(s->handshaker)) { + /* We may need more data. */ + if (result == TSI_INCOMPLETE_DATA) { + /* TODO(klempner,jboeuf): This should probably use the client setup + deadline */ + grpc_endpoint_notify_on_read(s->endpoint, + on_handshake_data_received_from_peer, setup, + gpr_inf_future); + cleanup_slices(slices, nslices); + return; + } else { + send_handshake_bytes_to_peer(s); + cleanup_slices(slices, nslices); + return; + } + } + + if (result != TSI_OK) { + gpr_log(GPR_ERROR, "Handshake failed with error %s", + tsi_result_to_string(result)); + cleanup_slices(slices, nslices); + secure_transport_setup_done(s, 0); + return; + } + + /* Handshake is done and successful this point. */ + has_left_overs_in_current_slice = + (consumed_slice_size < GPR_SLICE_LENGTH(slices[i])); + num_left_overs = (has_left_overs_in_current_slice ? 1 : 0) + nslices - i - 1; + if (num_left_overs == 0) { + cleanup_slices(slices, nslices); + check_peer(s); + return; + } + cleanup_slices(slices, nslices - num_left_overs); + + /* Put the leftovers in our buffer (ownership transfered). */ + if (has_left_overs_in_current_slice) { + gpr_slice_buffer_add(&s->left_overs, + gpr_slice_split_tail(&slices[i], consumed_slice_size)); + gpr_slice_unref(slices[i]); /* split_tail above increments refcount. */ + } + gpr_slice_buffer_addn(&s->left_overs, &slices[i + 1], + num_left_overs - has_left_overs_in_current_slice); + check_peer(s); +} + +/* If setup is NULL, the setup is done. */ +static void on_handshake_data_sent_to_peer(void *setup, + grpc_endpoint_cb_status error) { + grpc_secure_transport_setup *s = setup; + + /* Make sure that write is OK. */ + if (error != GRPC_ENDPOINT_CB_OK) { + gpr_log(GPR_ERROR, "Write failed with error %d.", error); + if (setup != NULL) secure_transport_setup_done(s, 0); + return; + } + + /* We may be done. */ + if (tsi_handshaker_is_in_progress(s->handshaker)) { + /* TODO(klempner,jboeuf): This should probably use the client setup + deadline */ + grpc_endpoint_notify_on_read(s->endpoint, + on_handshake_data_received_from_peer, setup, + gpr_inf_future); + } else { + check_peer(s); + } +} + +void grpc_setup_secure_transport(grpc_security_context *ctx, + grpc_endpoint *nonsecure_endpoint, + grpc_secure_transport_setup_done_cb cb, + void *user_data) { + grpc_security_status result = GRPC_SECURITY_OK; + grpc_secure_transport_setup *s = + gpr_malloc(sizeof(grpc_secure_transport_setup)); + memset(s, 0, sizeof(grpc_secure_transport_setup)); + result = grpc_security_context_create_handshaker(ctx, &s->handshaker); + if (result != GRPC_SECURITY_OK) { + secure_transport_setup_done(s, 0); + return; + } + s->ctx = grpc_security_context_ref(ctx); + s->handshake_buffer_size = GRPC_INITIAL_HANDSHAKE_BUFFER_SIZE; + s->handshake_buffer = gpr_malloc(s->handshake_buffer_size); + s->endpoint = nonsecure_endpoint; + s->user_data = user_data; + s->cb = cb; + gpr_slice_buffer_init(&s->left_overs); + send_handshake_bytes_to_peer(s); +} diff --git a/src/core/security/secure_transport_setup.h b/src/core/security/secure_transport_setup.h new file mode 100644 index 0000000000..1a20fa9a80 --- /dev/null +++ b/src/core/security/secure_transport_setup.h @@ -0,0 +1,53 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_SECURITY_SECURE_TRANSPORT_SETUP_H__ +#define __GRPC_INTERNAL_SECURITY_SECURE_TRANSPORT_SETUP_H__ + +#include "src/core/endpoint/endpoint.h" +#include "src/core/security/security_context.h" + +/* --- Secure transport setup --- */ + +/* Ownership of the secure_endpoint is transfered. */ +typedef void (*grpc_secure_transport_setup_done_cb)( + void *user_data, grpc_security_status status, + grpc_endpoint *secure_endpoint); + +/* Calls the callback upon completion. */ +void grpc_setup_secure_transport(grpc_security_context *ctx, + grpc_endpoint *nonsecure_endpoint, + grpc_secure_transport_setup_done_cb cb, + void *user_data); + +#endif /* __GRPC_INTERNAL_SECURITY_SECURE_TRANSPORT_SETUP_H__ */ diff --git a/src/core/security/security_context.c b/src/core/security/security_context.c new file mode 100644 index 0000000000..beda64cba2 --- /dev/null +++ b/src/core/security/security_context.c @@ -0,0 +1,497 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/security/security_context.h" + +#include <string.h> + +#include "src/core/endpoint/secure_endpoint.h" +#include "src/core/security/credentials.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/slice_buffer.h> +#include <grpc/support/string.h> + +#include "src/core/tsi/fake_transport_security.h" +#include "src/core/tsi/ssl_transport_security.h" + +/* -- Constants. -- */ + +#define GRPC_ALPN_PROTOCOL_STRING "h2-15" +/* Defines the cipher suites that we accept. All these cipher suites are + compliant with TLS 1.2 and use an RSA public key. We prefer GCM over CBC + and ECDHE-RSA over just RSA. */ +#define GRPC_SSL_CIPHER_SUITES \ + "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:AES128-GCM-SHA256:" \ + "AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:AES128-" \ + "SHA256:AES256-SHA256" + +/* -- Common methods. -- */ + +grpc_security_status grpc_security_context_create_handshaker( + grpc_security_context *ctx, tsi_handshaker **handshaker) { + if (ctx == NULL || handshaker == NULL) return GRPC_SECURITY_ERROR; + return ctx->vtable->create_handshaker(ctx, handshaker); +} + +grpc_security_status grpc_security_context_check_peer( + grpc_security_context *ctx, const tsi_peer *peer, + grpc_security_check_peer_cb cb, void *user_data) { + if (ctx == NULL) return GRPC_SECURITY_ERROR; + return ctx->vtable->check_peer(ctx, peer, cb, user_data); +} + +void grpc_security_context_unref(grpc_security_context *ctx) { + if (ctx == NULL) return; + if (gpr_unref(&ctx->refcount)) ctx->vtable->destroy(ctx); +} + +grpc_security_context *grpc_security_context_ref(grpc_security_context *ctx) { + if (ctx == NULL) return NULL; + gpr_ref(&ctx->refcount); + return ctx; +} + +static void context_pointer_arg_destroy(void *p) { + grpc_security_context_unref(p); +} + +static void *context_pointer_arg_copy(void *p) { + return grpc_security_context_ref(p); +} + +grpc_arg grpc_security_context_to_arg(grpc_security_context *ctx) { + grpc_arg result; + result.type = GRPC_ARG_POINTER; + result.key = GRPC_SECURITY_CONTEXT_ARG; + result.value.pointer.destroy = context_pointer_arg_destroy; + result.value.pointer.copy = context_pointer_arg_copy; + result.value.pointer.p = ctx; + return result; +} + +grpc_security_context *grpc_security_context_from_arg( + const grpc_arg *arg) { + if (strcmp(arg->key, GRPC_SECURITY_CONTEXT_ARG)) return NULL; + if (arg->type != GRPC_ARG_POINTER) { + gpr_log(GPR_ERROR, "Invalid type %d for arg %s", arg->type, + GRPC_SECURITY_CONTEXT_ARG); + return NULL; + } + return arg->value.pointer.p; +} + +grpc_security_context *grpc_find_security_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_security_context *ctx = grpc_security_context_from_arg(&args->args[i]); + if (ctx != NULL) return ctx; + } + return NULL; +} + +static int check_request_metadata_only_creds(grpc_credentials *creds) { + if (creds != NULL && !grpc_credentials_has_request_metadata_only(creds)) { + gpr_log(GPR_ERROR, + "Incompatible credentials for channel security context: needs to " + "only set request metadata."); + return 0; + } + return 1; +} + +/* -- Fake implementation. -- */ + +static void fake_channel_destroy(grpc_security_context *ctx) { + grpc_channel_security_context *c = (grpc_channel_security_context *)ctx; + grpc_credentials_unref(c->request_metadata_only_creds); + gpr_free(ctx); +} + +static void fake_server_destroy(grpc_security_context *ctx) { + gpr_free(ctx); +} + +static grpc_security_status fake_channel_create_handshaker( + grpc_security_context *ctx, tsi_handshaker **handshaker) { + *handshaker = tsi_create_fake_handshaker(1); + return GRPC_SECURITY_OK; +} + +static grpc_security_status fake_server_create_handshaker( + grpc_security_context *ctx, tsi_handshaker **handshaker) { + *handshaker = tsi_create_fake_handshaker(0); + return GRPC_SECURITY_OK; +} + +static grpc_security_status fake_check_peer(grpc_security_context *ctx, + const tsi_peer *peer, + grpc_security_check_peer_cb cb, + void *user_data) { + const char *prop_name; + if (peer->property_count != 1) { + gpr_log(GPR_ERROR, "Fake peers should only have 1 property."); + return GRPC_SECURITY_ERROR; + } + 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); + return GRPC_SECURITY_ERROR; + } + if (peer->properties[0].type != TSI_PEER_PROPERTY_TYPE_STRING) { + gpr_log(GPR_ERROR, "Invalid type of cert type property."); + return GRPC_SECURITY_ERROR; + } + if (strncmp(peer->properties[0].value.string.data, TSI_FAKE_CERTIFICATE_TYPE, + peer->properties[0].value.string.length)) { + gpr_log(GPR_ERROR, "Invalid value for cert type property."); + return GRPC_SECURITY_ERROR; + } + return GRPC_SECURITY_OK; +} + +static grpc_security_context_vtable fake_channel_vtable = { + fake_channel_destroy, fake_channel_create_handshaker, fake_check_peer}; + +static grpc_security_context_vtable fake_server_vtable = { + fake_server_destroy, fake_server_create_handshaker, fake_check_peer}; + +grpc_channel_security_context *grpc_fake_channel_security_context_create( + grpc_credentials *request_metadata_only_creds) { + grpc_channel_security_context *c = + gpr_malloc(sizeof(grpc_channel_security_context)); + gpr_ref_init(&c->base.refcount, 1); + c->base.is_client_side = 1; + c->base.vtable = &fake_channel_vtable; + GPR_ASSERT(check_request_metadata_only_creds(request_metadata_only_creds)); + c->request_metadata_only_creds = + grpc_credentials_ref(request_metadata_only_creds); + return c; +} + +grpc_security_context *grpc_fake_server_security_context_create(void) { + grpc_security_context *c = gpr_malloc(sizeof(grpc_security_context)); + gpr_ref_init(&c->refcount, 1); + c->vtable = &fake_server_vtable; + return c; +} + +/* --- Ssl implementation. --- */ + +typedef struct { + grpc_channel_security_context base; + tsi_ssl_handshaker_factory *handshaker_factory; + char *secure_peer_name; +} grpc_ssl_channel_security_context; + +typedef struct { + grpc_security_context base; + tsi_ssl_handshaker_factory *handshaker_factory; +} grpc_ssl_server_security_context; + +static void ssl_channel_destroy(grpc_security_context *ctx) { + grpc_ssl_channel_security_context *c = + (grpc_ssl_channel_security_context *)ctx; + grpc_credentials_unref(c->base.request_metadata_only_creds); + 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(ctx); +} + +static void ssl_server_destroy(grpc_security_context *ctx) { + grpc_ssl_server_security_context *c = + (grpc_ssl_server_security_context *)ctx; + if (c->handshaker_factory != NULL) { + tsi_ssl_handshaker_factory_destroy(c->handshaker_factory); + } + gpr_free(ctx); +} + +static grpc_security_status ssl_create_handshaker( + tsi_ssl_handshaker_factory *handshaker_factory, int is_client, + const char *secure_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 ? secure_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 grpc_security_status ssl_channel_create_handshaker( + grpc_security_context *ctx, tsi_handshaker **handshaker) { + grpc_ssl_channel_security_context *c = + (grpc_ssl_channel_security_context *)ctx; + return ssl_create_handshaker(c->handshaker_factory, 1, c->secure_peer_name, + handshaker); +} + +static grpc_security_status ssl_server_create_handshaker( + grpc_security_context *ctx, tsi_handshaker **handshaker) { + grpc_ssl_server_security_context *c = + (grpc_ssl_server_security_context *)ctx; + return ssl_create_handshaker(c->handshaker_factory, 0, NULL, handshaker); +} + +static grpc_security_status ssl_check_peer(const char *secure_peer_name, + const tsi_peer *peer) { + /* Check the ALPN. */ + const tsi_peer_property *p = + tsi_peer_get_property_by_name(peer, TSI_SSL_ALPN_SELECTED_PROTOCOL); + if (p == NULL || p->type != TSI_PEER_PROPERTY_TYPE_STRING) { + gpr_log(GPR_ERROR, "Invalid or missing selected ALPN property."); + return GRPC_SECURITY_ERROR; + } + if (strncmp(GRPC_ALPN_PROTOCOL_STRING, p->value.string.data, + p->value.string.length)) { + gpr_log(GPR_ERROR, "Invalid ALPN value."); + return GRPC_SECURITY_ERROR; + } + + /* Check the peer name if specified. */ + if (secure_peer_name != NULL && + !tsi_ssl_peer_matches_name(peer, secure_peer_name)) { + gpr_log(GPR_ERROR, "Peer name %s is not in peer certificate", + secure_peer_name); + return GRPC_SECURITY_ERROR; + } + return GRPC_SECURITY_OK; +} + +static grpc_security_status ssl_channel_check_peer( + grpc_security_context *ctx, const tsi_peer *peer, + grpc_security_check_peer_cb cb, void *user_data) { + grpc_ssl_channel_security_context *c = + (grpc_ssl_channel_security_context *)ctx; + return ssl_check_peer(c->secure_peer_name, peer); +} + +static grpc_security_status ssl_server_check_peer( + grpc_security_context *ctx, const tsi_peer *peer, + grpc_security_check_peer_cb cb, void *user_data) { + /* TODO(jboeuf): Find a way to expose the peer to the authorization layer. */ + return ssl_check_peer(NULL, peer); +} + +static grpc_security_context_vtable ssl_channel_vtable = { + ssl_channel_destroy, ssl_channel_create_handshaker, ssl_channel_check_peer}; + +static grpc_security_context_vtable ssl_server_vtable = { + ssl_server_destroy, ssl_server_create_handshaker, ssl_server_check_peer}; + +grpc_security_status grpc_ssl_channel_security_context_create( + grpc_credentials *request_metadata_only_creds, + const grpc_ssl_config *config, const char *secure_peer_name, + grpc_channel_security_context **ctx) { + const char *alpn_protocol_string = GRPC_ALPN_PROTOCOL_STRING; + unsigned char alpn_protocol_string_len = + (unsigned char)strlen(alpn_protocol_string); + tsi_result result = TSI_OK; + grpc_ssl_channel_security_context *c; + + if (config == NULL || secure_peer_name == NULL || + config->pem_root_certs == NULL) { + gpr_log(GPR_ERROR, "An ssl channel needs a secure name and root certs."); + return GRPC_SECURITY_ERROR; + } + if (!check_request_metadata_only_creds(request_metadata_only_creds)) { + return GRPC_SECURITY_ERROR; + } + + c = gpr_malloc(sizeof(grpc_ssl_channel_security_context)); + memset(c, 0, sizeof(grpc_ssl_channel_security_context)); + + gpr_ref_init(&c->base.base.refcount, 1); + c->base.base.vtable = &ssl_channel_vtable; + c->base.base.is_client_side = 1; + c->base.request_metadata_only_creds = + grpc_credentials_ref(request_metadata_only_creds); + if (secure_peer_name != NULL) { + c->secure_peer_name = gpr_strdup(secure_peer_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, + config->pem_root_certs, config->pem_root_certs_size, + GRPC_SSL_CIPHER_SUITES, (const unsigned char **)&alpn_protocol_string, + &alpn_protocol_string_len, 1, &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); + *ctx = NULL; + return GRPC_SECURITY_ERROR; + } + *ctx = &c->base; + return GRPC_SECURITY_OK; +} + +grpc_security_status grpc_ssl_server_security_context_create( + const grpc_ssl_config *config, grpc_security_context **ctx) { + const char *alpn_protocol_string = GRPC_ALPN_PROTOCOL_STRING; + unsigned char alpn_protocol_string_len = + (unsigned char)strlen(alpn_protocol_string); + tsi_result result = TSI_OK; + grpc_ssl_server_security_context *c; + + if (config == NULL || config->pem_private_key == NULL || + config->pem_cert_chain == NULL) { + gpr_log(GPR_ERROR, "An SSL server needs a key and a cert."); + return GRPC_SECURITY_ERROR; + } + c = gpr_malloc(sizeof(grpc_ssl_server_security_context)); + memset(c, 0, sizeof(grpc_ssl_server_security_context)); + + gpr_ref_init(&c->base.refcount, 1); + c->base.vtable = &ssl_server_vtable; + result = tsi_create_ssl_server_handshaker_factory( + (const unsigned char **)&config->pem_private_key, + (const gpr_uint32 *)&config->pem_private_key_size, + (const unsigned char **)&config->pem_cert_chain, + (const gpr_uint32 *)&config->pem_cert_chain_size, 1, + config->pem_root_certs, config->pem_root_certs_size, + GRPC_SSL_CIPHER_SUITES, (const unsigned char **)&alpn_protocol_string, + &alpn_protocol_string_len, 1, &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); + *ctx = NULL; + return GRPC_SECURITY_ERROR; + } + *ctx = &c->base; + return GRPC_SECURITY_OK; +} + + + +/* -- High level objects. -- */ + +static grpc_channel *grpc_ssl_channel_create(grpc_credentials *creds, + const grpc_ssl_config *config, + const char *target, + const grpc_channel_args *args) { + grpc_channel_security_context *ctx = NULL; + grpc_channel *channel = NULL; + grpc_security_status status = GRPC_SECURITY_OK; + size_t i = 0; + const char *secure_peer_name = target; + for (i = 0; i < args->num_args; i++) { + grpc_arg *arg = &args->args[i]; + if (!strcmp(arg->key, GRPC_SSL_TARGET_NAME_OVERRIDE_ARG) && + arg->type == GRPC_ARG_STRING) { + secure_peer_name = arg->value.string; + break; + } + } + status = grpc_ssl_channel_security_context_create(creds, config, + secure_peer_name, &ctx); + if (status != GRPC_SECURITY_OK) { + return NULL; /* TODO(ctiller): return lame channel. */ + } + channel = grpc_secure_channel_create_internal(target, args, ctx); + grpc_security_context_unref(&ctx->base); + return channel; +} + + +grpc_channel *grpc_secure_channel_create(grpc_credentials *creds, + const char *target, + const grpc_channel_args *args) { + if (grpc_credentials_has_request_metadata_only(creds)) { + gpr_log(GPR_ERROR, + "Credentials is insufficient to create a secure channel."); + return NULL; /* TODO(ctiller): return lame channel. */ + } + if (!strcmp(creds->type, GRPC_CREDENTIALS_TYPE_SSL)) { + return grpc_ssl_channel_create(NULL, grpc_ssl_credentials_get_config(creds), + target, args); + } else if (!strcmp(creds->type, + GRPC_CREDENTIALS_TYPE_FAKE_TRANSPORT_SECURITY)) { + grpc_channel_security_context *ctx = + grpc_fake_channel_security_context_create(NULL); + grpc_channel *channel = + grpc_secure_channel_create_internal(target, args, ctx); + grpc_security_context_unref(&ctx->base); + return channel; + } else if (!strcmp(creds->type, GRPC_CREDENTIALS_TYPE_COMPOSITE)) { + return NULL; /* TODO(jboeuf) Implement. */ + } else { + gpr_log(GPR_ERROR, + "Unknown credentials type %s for creating a secure channel."); + return NULL; /* TODO(ctiller): return lame channel. */ + } +} + +grpc_channel *grpc_default_secure_channel_create( + const char *target, const grpc_channel_args *args) { + return grpc_secure_channel_create(grpc_default_credentials_create(), target, + args); +} + +grpc_server *grpc_secure_server_create(grpc_server_credentials *creds, + grpc_completion_queue *cq, + const grpc_channel_args *args) { + grpc_security_status status = GRPC_SECURITY_ERROR; + grpc_security_context *ctx = NULL; + grpc_server *server = NULL; + if (creds == NULL) return NULL; /* TODO(ctiller): Return lame server. */ + if (!strcmp(creds->type, GRPC_CREDENTIALS_TYPE_SSL)) { + status = grpc_ssl_server_security_context_create( + grpc_ssl_server_credentials_get_config(creds), &ctx); + } else if (!strcmp(creds->type, + GRPC_CREDENTIALS_TYPE_FAKE_TRANSPORT_SECURITY)) { + ctx = grpc_fake_server_security_context_create(); + status = GRPC_SECURITY_OK; + } else { + gpr_log(GPR_ERROR, + "Unable to create secure server with credentials of type %s.", + creds->type); + } + if (status != GRPC_SECURITY_OK) { + return NULL; /* TODO(ctiller): Return lame server. */ + } + server = grpc_secure_server_create_internal(cq, args, ctx); + grpc_security_context_unref(ctx); + return server; +} diff --git a/src/core/security/security_context.h b/src/core/security/security_context.h new file mode 100644 index 0000000000..59c9bbdf34 --- /dev/null +++ b/src/core/security/security_context.h @@ -0,0 +1,176 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_SECURITY_SECURITY_CONTEXT_H__ +#define __GRPC_INTERNAL_SECURITY_SECURITY_CONTEXT_H__ + +#include <grpc/grpc_security.h> +#include "src/core/endpoint/endpoint.h" +#include "src/core/security/credentials.h" +#include "src/core/tsi/transport_security_interface.h" + +/* --- status enum. --- */ + +typedef enum { + GRPC_SECURITY_OK = 0, + GRPC_SECURITY_PENDING, + GRPC_SECURITY_ERROR +} grpc_security_status; + +/* --- security_context object. --- + + A security context object represents away to configure the underlying + transport security mechanism and check the resulting trusted peer. */ + +typedef struct grpc_security_context grpc_security_context; + +#define GRPC_SECURITY_CONTEXT_ARG "grpc.security_context" + +typedef void (*grpc_security_check_peer_cb)(void *user_data, + grpc_security_status status); + +typedef struct { + void (*destroy)(grpc_security_context *ctx); + grpc_security_status (*create_handshaker)(grpc_security_context *ctx, + tsi_handshaker **handshaker); + grpc_security_status (*check_peer)(grpc_security_context *ctx, + const tsi_peer *peer, + grpc_security_check_peer_cb, + void *user_data); +} grpc_security_context_vtable; + +struct grpc_security_context { + const grpc_security_context_vtable *vtable; + gpr_refcount refcount; + int is_client_side; +}; + +/* Increments the refcount. */ +grpc_security_context *grpc_security_context_ref(grpc_security_context *ctx); + +/* Decrements the refcount and destroys the object if it reaches 0. */ +void grpc_security_context_unref(grpc_security_context *ctx); + +/* Handshake creation. */ +grpc_security_status grpc_security_context_create_handshaker( + grpc_security_context *ctx, tsi_handshaker **handshaker); + +/* Check the peer. + Implementations can choose to check the peer either synchronously or + asynchronously. In the first case, a successful will return + GRPC_SECURITY_OK. In the asynchronous case, the call will return + GRPC_SECURITY_PENDING unless an error is detected early on. + + Note: + Asynchronous implementations of this interface should make a copy of the + fields of the peer they want to check as there is no guarantee on the + lifetime of the peer object beyond this call. +*/ +grpc_security_status grpc_security_context_check_peer( + grpc_security_context *ctx, const tsi_peer *peer, + grpc_security_check_peer_cb cb, void *user_data); + +/* Util to encapsulate the context in a channel arg. */ +grpc_arg grpc_security_context_to_arg(grpc_security_context *ctx); + +/* Util to get the context from a channel arg. */ +grpc_security_context *grpc_security_context_from_arg(const grpc_arg *arg); + +/* Util to find the context from channel args. */ +grpc_security_context *grpc_find_security_context_in_args( + const grpc_channel_args *args); + +/* --- channel_security_context object. --- + + A channel security context object represents away to configure the + underlying transport security mechanism on the client side. */ + +typedef struct grpc_channel_security_context grpc_channel_security_context; + +struct grpc_channel_security_context { + grpc_security_context base; /* requires is_client_side to be non 0. */ + grpc_credentials *request_metadata_only_creds; +}; + +/* --- Creation security contexts. --- */ + +/* For TESTING ONLY! + Creates a fake context that emulates real channel security. */ +grpc_channel_security_context *grpc_fake_channel_security_context_create( + grpc_credentials *request_metadata_only_creds); + +/* For TESTING ONLY! + Creates a fake context that emulates real server security. */ +grpc_security_context *grpc_fake_server_security_context_create(void); + +/* Creates an SSL channel_security_context. + - request_metadata_only_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_context_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. + - ctx is a pointer on the context 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_context_create( + grpc_credentials *request_metadata_only_creds, + const grpc_ssl_config *config, const char *secure_peer_name, + grpc_channel_security_context **ctx); + +/* Creates an SSL server_security_context. + - config is the SSL config to be used for the SSL channel establishment. + - ctx is a pointer on the context 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_context_create( + const grpc_ssl_config *config, grpc_security_context **ctx); + + +/* --- Creation of high level objects. --- */ + +/* Secure client channel creation. */ +grpc_channel *grpc_secure_channel_create_internal( + const char *target, const grpc_channel_args *args, + grpc_channel_security_context *ctx); + +/* Secure server creation. */ +grpc_server *grpc_secure_server_create_internal( + grpc_completion_queue *cq, const grpc_channel_args *args, + grpc_security_context *ctx); + +#endif /* __GRPC_INTERNAL_SECURITY_SECURITY_CONTEXT_H__ */ diff --git a/src/core/security/server_secure_chttp2.c b/src/core/security/server_secure_chttp2.c new file mode 100644 index 0000000000..bce27ec3ab --- /dev/null +++ b/src/core/security/server_secure_chttp2.c @@ -0,0 +1,141 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 "src/core/channel/http_filter.h" +#include "src/core/channel/http_server_filter.h" +#include "src/core/endpoint/resolve_address.h" +#include "src/core/endpoint/tcp_server.h" +#include "src/core/security/security_context.h" +#include "src/core/security/secure_transport_setup.h" +#include "src/core/surface/server.h" +#include "src/core/surface/surface_em.h" +#include "src/core/transport/chttp2_transport.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/useful.h> + +static grpc_transport_setup_result setup_transport(void *server, + grpc_transport *transport, + grpc_mdctx *mdctx) { + static grpc_channel_filter const *extra_filters[] = {&grpc_http_server_filter, + &grpc_http_filter}; + return grpc_server_setup_transport(server, transport, extra_filters, + GPR_ARRAY_SIZE(extra_filters), mdctx); +} + +static void on_secure_transport_setup_done(void *server, + grpc_security_status status, + grpc_endpoint *secure_endpoint) { + if (status == GRPC_SECURITY_OK) { + grpc_create_chttp2_transport( + setup_transport, server, grpc_server_get_channel_args(server), + secure_endpoint, NULL, 0, grpc_mdctx_create(), 0); + } else { + gpr_log(GPR_ERROR, "Secure transport failed with error %d", status); + } +} + +static void on_accept(void *server, grpc_endpoint *tcp) { + const grpc_channel_args *args = grpc_server_get_channel_args(server); + grpc_security_context *ctx = grpc_find_security_context_in_args(args); + GPR_ASSERT(ctx); + grpc_setup_secure_transport(ctx, tcp, on_secure_transport_setup_done, + server); +} + +/* Note: the following code is the same with server_chttp2.c */ + +/* Server callback: start listening on our ports */ +static void start(grpc_server *server, void *tcpp) { + grpc_tcp_server *tcp = tcpp; + grpc_tcp_server_start(tcp, on_accept, server); +} + +/* Server callback: destroy the tcp listener (so we don't generate further + callbacks) */ +static void destroy(grpc_server *server, void *tcpp) { + grpc_tcp_server *tcp = tcpp; + grpc_tcp_server_destroy(tcp); +} + +int grpc_server_add_secure_http2_port(grpc_server *server, const char *addr) { + grpc_resolved_addresses *resolved = NULL; + grpc_tcp_server *tcp = NULL; + size_t i; + int count = 0; + + resolved = grpc_blocking_resolve_address(addr, "https"); + if (!resolved) { + goto error; + } + + tcp = grpc_tcp_server_create(grpc_surface_em()); + if (!tcp) { + goto error; + } + + for (i = 0; i < resolved->naddrs; i++) { + if (grpc_tcp_server_add_port(tcp, + (struct sockaddr *)&resolved->addrs[i].addr, + resolved->addrs[i].len) >= 0) { + 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(server, tcp, start, destroy); + + return 1; + +/* Error path: cleanup and return */ +error: + if (resolved) { + grpc_resolved_addresses_destroy(resolved); + } + if (tcp) { + grpc_tcp_server_destroy(tcp); + } + return 0; +} diff --git a/src/core/statistics/census_init.c b/src/core/statistics/census_init.c new file mode 100644 index 0000000000..340214f8f5 --- /dev/null +++ b/src/core/statistics/census_init.c @@ -0,0 +1,37 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/statistics/census_interface.h" + +void census_init() {} +void census_shutdown() {} diff --git a/src/core/statistics/census_interface.h b/src/core/statistics/census_interface.h new file mode 100644 index 0000000000..7618387ee2 --- /dev/null +++ b/src/core/statistics/census_interface.h @@ -0,0 +1,76 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_STATISTICS_CENSUS_INTERFACE_H__ +#define __GRPC_INTERNAL_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 { + gpr_uint32 upper; + gpr_uint32 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(); + +/* Shutdown Census Library. */ +void census_shutdown(); + +/* 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(); + +/* Ends tracing. Calling this function will invalidate the input op_id. */ +void census_tracing_end_op(census_op_id op_id); + +#endif /* __GRPC_INTERNAL_STATISTICS_CENSUS_INTERFACE_H__ */ diff --git a/src/core/statistics/census_rpc_stats.c b/src/core/statistics/census_rpc_stats.c new file mode 100644 index 0000000000..28101ac734 --- /dev/null +++ b/src/core/statistics/census_rpc_stats.c @@ -0,0 +1,57 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/statistics/census_interface.h" +#include "src/core/statistics/census_rpc_stats.h" +#include <grpc/support/alloc.h> + +census_rpc_stats* census_rpc_stats_create_empty() { + 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_destroy(census_aggregated_rpc_stats* data) {} + +void census_record_rpc_client_stats(census_op_id op_id, + const census_rpc_stats* stats) {} + +void census_record_rpc_server_stats(census_op_id op_id, + const census_rpc_stats* stats) {} + +void census_get_server_stats(census_aggregated_rpc_stats* data) {} + +void census_get_client_stats(census_aggregated_rpc_stats* data) {} diff --git a/src/core/statistics/census_rpc_stats.h b/src/core/statistics/census_rpc_stats.h new file mode 100644 index 0000000000..6ab7614805 --- /dev/null +++ b/src/core/statistics/census_rpc_stats.h @@ -0,0 +1,89 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_STATISTICS_CENSUS_RPC_STATS_H__ +#define __GRPC_INTERNAL_STATISTICS_CENSUS_RPC_STATS_H__ + +#include "src/core/statistics/census_interface.h" +#include <grpc/support/port_platform.h> + +struct census_rpc_stats { + gpr_uint64 cnt; + gpr_uint64 rpc_error_cnt; + gpr_uint64 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(); + +typedef struct census_per_service_per_method_rpc_stats { + const char* service; + const char* method; + census_rpc_stats data; +} census_per_service_per_method_rpc_stats; + +typedef struct census_aggregated_rpc_stats { + int num_entries; + census_per_service_per_method_rpc_stats* stats; +} census_aggregated_rpc_stats; + +/* Deletes aggregated data. */ +void census_aggregated_rpc_stats_destroy(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); + +#endif /* __GRPC_INTERNAL_STATISTICS_CENSUS_RPC_STATS_H__ */ diff --git a/src/core/statistics/census_tracing.c b/src/core/statistics/census_tracing.c new file mode 100644 index 0000000000..d0c9032837 --- /dev/null +++ b/src/core/statistics/census_tracing.c @@ -0,0 +1,47 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/statistics/census_interface.h" + +census_op_id census_tracing_start_op() { + census_op_id empty_op_id = {0, 0}; + return empty_op_id; +} + +int census_add_method_tag(census_op_id op_id, const char* method_name) { + return 0; +} + +void census_tracing_print(census_op_id op_id, const char* annotation) {} + +void census_tracing_end_op(census_op_id op_id) {} diff --git a/src/core/statistics/hash_table.c b/src/core/statistics/hash_table.c new file mode 100644 index 0000000000..f0105ee683 --- /dev/null +++ b/src/core/statistics/hash_table.c @@ -0,0 +1,303 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/statistics/hash_table.h" + +#include <stdio.h> +#include <stddef.h> + +#include <grpc/support/log.h> +#include <grpc/support/alloc.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. */ + gpr_int32 prev_non_empty_bucket; + /* -1 if all buckets are empty. */ + gpr_int32 next_non_empty_bucket; +} bucket; + +struct unresizable_hash_table { + /* Number of entries in the table */ + size_t size; + /* Number of buckets */ + gpr_uint32 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. */ + gpr_int32 first_non_empty_bucket; + /* Index of the last non_empty bucket. -1 iff size == 0. */ + gpr_int32 last_non_empty_bucket; + /* Immutable options of this hash table, initialized at creation time. */ + census_ht_option options; +}; + +typedef struct entry_locator { + gpr_int32 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 gpr_uint64 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 gpr_int32 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) { + if (opt->key_type == CENSUS_HT_UINT64) return p->key.val == key.val; + if (opt->key_type == CENSUS_HT_POINTER) + return !opt->compare_keys((p->key).ptr, key.ptr); + return 0; +} + +static entry_locator ht_find(const census_ht* ht, census_ht_key key) { + entry_locator loc = {0, 0, 0, NULL}; + gpr_int32 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) { + gpr_int32 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; + gpr_int32 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) { + int 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/statistics/hash_table.h b/src/core/statistics/hash_table.h new file mode 100644 index 0000000000..5c9a3fa0b4 --- /dev/null +++ b/src/core/statistics/hash_table.h @@ -0,0 +1,131 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_STATISTICS_HASH_TABLE_H_ +#define __GRPC_INTERNAL_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 { + gpr_uint64 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 */ + gpr_int32 num_buckets; + /* Fucntion to calculate uint64 hash value of the key. Only takes effect if + key_type is POINTER. */ + gpr_uint64 (*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. */ +gpr_uint64 census_ht_for_all(const census_ht* ht, census_ht_itr_cb); + +#endif /* __GRPC_INTERNAL_STATISTICS_HASH_TABLE_H_ */ diff --git a/src/core/statistics/log.c b/src/core/statistics/log.c new file mode 100644 index 0000000000..43a8653de6 --- /dev/null +++ b/src/core/statistics/log.c @@ -0,0 +1,617 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/statistics/log.h" +#include <string.h> +#include "src/core/support/cpu.h" +#include <grpc/support/alloc.h> +#include <grpc/support/atm.h> +#include <grpc/support/log.h> +#include <grpc/support/port_platform.h> +#include <grpc/support/sync.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 */ + gpr_int32 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 + gpr_int32 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 { + gpr_int32 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) */ + int num_cores; + /* number of CENSUS_LOG_2_MAX_RECORD_SIZE blocks in log */ + gpr_int32 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. */ + gpr_int32 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, + gpr_int32 bytes_committed) { + gpr_atm_rel_store(&block->bytes_committed, bytes_committed); +} + +static gpr_int32 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) { + gpr_int32 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() { + 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(gpr_int32 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) { + gpr_uintptr p = (gpr_uintptr)((char*)record - g_log.buffer); + gpr_uintptr 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) { + gpr_int32 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(); + if (size_in_mb < 1 || size_in_mb > 1000) { + gpr_log(GPR_ERROR, "Invalid size for stats_log: using 1MB default"); + size_in_mb = 1; + } + g_log.num_blocks = (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); + 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); + 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() { + 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_int32 attempts_remaining = g_log.num_blocks; + /* TODO(aveitch): move this inside the do loop when current_cpu is fixed */ + gpr_int32 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() { + 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() { + 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() { + GPR_ASSERT(g_log.initialized); + return gpr_atm_acq_load(&g_log.out_of_space_count); +} diff --git a/src/core/statistics/log.h b/src/core/statistics/log.h new file mode 100644 index 0000000000..e9c745cac0 --- /dev/null +++ b/src/core/statistics/log.h @@ -0,0 +1,89 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_STATISTICS_LOG_H__ +#define __GRPC_INTERNAL_STATISTICS_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. 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(); + +/* 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(); +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(); + +/* Returns the number of times gprc_stats_log_start_write() failed due to + out-of-space. */ +int census_log_out_of_space_count(); + +#endif /* __GRPC_INTERNAL_STATISTICS_LOG_H__ */ diff --git a/src/core/statistics/window_stats.c b/src/core/statistics/window_stats.c new file mode 100644 index 0000000000..be53d818a0 --- /dev/null +++ b/src/core/statistics/window_stats.c @@ -0,0 +1,317 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/statistics/window_stats.h" +#include <math.h> +#include <stddef.h> +#include <string.h> +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/time.h> +#include <grpc/support/useful.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 { + gpr_int64 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. */ + gpr_int64 bottom; + /* The largest time storable in the current window + 1ns */ + gpr_int64 top; + /* The width of each bucket in ns. */ + gpr_int64 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. */ + gpr_int64 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 gpr_int64 max_seconds = + (GPR_INT64_MAX - GPR_NS_PER_SEC) / GPR_NS_PER_SEC; + +static gpr_int64 timespec_to_ns(const gpr_timespec ts) { + if (ts.tv_sec > max_seconds) { + return GPR_INT64_MAX - 1; + } + return (gpr_int64)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++) { + gpr_int64 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++) { + gpr_int64 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, gpr_int64 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; + gpr_int64 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; + gpr_int64 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) { + gpr_int64 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/statistics/window_stats.h b/src/core/statistics/window_stats.h new file mode 100644 index 0000000000..677f40031e --- /dev/null +++ b/src/core/statistics/window_stats.h @@ -0,0 +1,173 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_STATISTICS_WINDOW_STATS_H_ +#define __GRPC_INTERNAL_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(), &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(), 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_INTERNAL_STATISTICS_WINDOW_STATS_H_ */ diff --git a/src/core/support/alloc.c b/src/core/support/alloc.c new file mode 100644 index 0000000000..658408f334 --- /dev/null +++ b/src/core/support/alloc.c @@ -0,0 +1,67 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 <stdlib.h> +#include <grpc/support/port_platform.h> + +void *gpr_malloc(size_t size) { + void *p = malloc(size); + if (!p) { + abort(); + } + return p; +} + +void gpr_free(void *p) { free(p); } + +void *gpr_realloc(void *p, size_t size) { + p = realloc(p, size); + if (!p) { + abort(); + } + return p; +} + +void *gpr_malloc_aligned(size_t size, size_t alignment) { + size_t extra = alignment - 1 + sizeof(void *); + void *p = gpr_malloc(size + extra); + void **ret = (void **)(((gpr_uintptr)p + extra) & ~(alignment - 1)); + ret[-1] = p; + return (void *)ret; +} + +void gpr_free_aligned(void *ptr) { + free(((void **)ptr)[-1]); +} diff --git a/src/core/support/cancellable.c b/src/core/support/cancellable.c new file mode 100644 index 0000000000..5596413fba --- /dev/null +++ b/src/core/support/cancellable.c @@ -0,0 +1,156 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* Implementation for gpr_cancellable */ + +#include <grpc/support/atm.h> +#include <grpc/support/sync.h> +#include <grpc/support/time.h> + +void gpr_cancellable_init(gpr_cancellable *c) { + gpr_mu_init(&c->mu); + c->cancelled = 0; + c->waiters.next = &c->waiters; + c->waiters.prev = &c->waiters; + c->waiters.mu = NULL; + c->waiters.cv = NULL; +} + +void gpr_cancellable_destroy(gpr_cancellable *c) { gpr_mu_destroy(&c->mu); } + +int gpr_cancellable_is_cancelled(gpr_cancellable *c) { + return gpr_atm_acq_load(&c->cancelled) != 0; +} + +/* Threads in gpr_cv_cancellable_wait(cv, mu, ..., c) place themselves on a + linked list c->waiters of gpr_cancellable_list_ before waiting on their + condition variables. They check for cancellation while holding *mu. Thus, + to wake a thread from gpr_cv_cancellable_wait(), it suffices to: + - set c->cancelled + - acquire and release *mu + - gpr_cv_broadcast(cv) + + However, gpr_cancellable_cancel() may not use gpr_mu_lock(mu), since the + caller may already hold *mu---a possible deadlock. (If we knew the caller + did not hold *mu, care would still be needed, because c->mu follows *mu in + the locking order, so *mu could not be acquired while holding c->mu---which + is needed to iterate over c->waiters.) + + Therefore, gpr_cancellable_cancel() uses gpr_mu_trylock() rather than + gpr_mu_lock(), and retries until either gpr_mu_trylock() succeeds or the + thread leaves gpr_cv_cancellable_wait() for other reasons. In the first + case, gpr_cancellable_cancel() removes the entry from the waiters list; in + the second, the waiting thread removes itself from the list. + + A one-entry cache of mutexes and condition variables processed is kept to + avoid doing the same work again and again if many threads are blocked in the + same place. However, it's important to broadcast on a condition variable if + the corresponding mutex has been locked successfully, even if the condition + variable has been signalled before. */ + +void gpr_cancellable_cancel(gpr_cancellable *c) { + if (!gpr_cancellable_is_cancelled(c)) { + int failures; + int backoff = 1; + do { + struct gpr_cancellable_list_ *l; + struct gpr_cancellable_list_ *nl; + gpr_mu *omu = 0; /* one-element cache of a processed gpr_mu */ + gpr_cv *ocv = 0; /* one-element cache of a processd gpr_cv */ + gpr_mu_lock(&c->mu); + gpr_atm_rel_store(&c->cancelled, 1); + failures = 0; + for (l = c->waiters.next; l != &c->waiters; l = nl) { + nl = l->next; + if (omu != l->mu) { + omu = l->mu; + if (gpr_mu_trylock(l->mu)) { + gpr_mu_unlock(l->mu); + l->next->prev = l->prev; /* remove *l from list */ + l->prev->next = l->next; + /* allow unconditional dequeue in gpr_cv_cancellable_wait() */ + l->next = l; + l->prev = l; + ocv = 0; /* force broadcast */ + } else { + failures++; + } + } + if (ocv != l->cv) { + ocv = l->cv; + gpr_cv_broadcast(l->cv); + } + } + gpr_mu_unlock(&c->mu); + if (failures != 0) { + if (backoff < 10) { + volatile int i; + for (i = 0; i != (1 << backoff); i++) { + } + backoff++; + } else { + gpr_event ev; + gpr_event_init(&ev); + gpr_event_wait(&ev, + gpr_time_add(gpr_now(), gpr_time_from_micros(1000))); + } + } + } while (failures != 0); + } +} + +int gpr_cv_cancellable_wait(gpr_cv *cv, gpr_mu *mu, gpr_timespec abs_deadline, + gpr_cancellable *c) { + gpr_int32 timeout; + gpr_mu_lock(&c->mu); + timeout = gpr_cancellable_is_cancelled(c); + if (!timeout) { + struct gpr_cancellable_list_ le; + le.mu = mu; + le.cv = cv; + le.next = c->waiters.next; + le.prev = &c->waiters; + le.next->prev = ≤ + le.prev->next = ≤ + gpr_mu_unlock(&c->mu); + timeout = gpr_cv_wait(cv, mu, abs_deadline); + gpr_mu_lock(&c->mu); + le.next->prev = le.prev; + le.prev->next = le.next; + if (!timeout) { + timeout = gpr_cancellable_is_cancelled(c); + } + } + gpr_mu_unlock(&c->mu); + return timeout; +} diff --git a/src/core/support/cmdline.c b/src/core/support/cmdline.c new file mode 100644 index 0000000000..ff163a1f6c --- /dev/null +++ b/src/core/support/cmdline.c @@ -0,0 +1,292 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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.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; + + void (*state)(gpr_cmdline *cl, char *arg); + arg *cur_arg; +}; + +static void 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_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; +} + +static void print_usage_and_die(gpr_cmdline *cl) { + /* TODO(ctiller): make this prettier */ + arg *a; + const char *name = strrchr(cl->argv0, '/'); + if (name) { + name++; + } else { + name = cl->argv0; + } + fprintf(stderr, "Usage: %s", name); + for (a = cl->args; a; a = a->next) { + switch (a->type) { + case ARGTYPE_BOOL: + fprintf(stderr, " [--%s|--no-%s]", a->name, a->name); + break; + case ARGTYPE_STRING: + fprintf(stderr, " [--%s=string]", a->name); + break; + case ARGTYPE_INT: + fprintf(stderr, " [--%s=int]", a->name); + break; + } + } + if (cl->extra_arg) { + fprintf(stderr, " [%s...]", cl->extra_arg_name); + } + fprintf(stderr, "\n"); + exit(1); +} + +static void extra_state(gpr_cmdline *cl, char *arg) { + if (!cl->extra_arg) print_usage_and_die(cl); + cl->extra_arg(cl->extra_arg_user_data, arg); +} + +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); + print_usage_and_die(cl); + } + + return a; +} + +static void value_state(gpr_cmdline *cl, char *arg) { + long intval; + char *end; + + GPR_ASSERT(cl->cur_arg); + + switch (cl->cur_arg->type) { + case ARGTYPE_INT: + intval = strtol(arg, &end, 0); + if (*end || intval < INT_MIN || intval > INT_MAX) { + fprintf(stderr, "expected integer, got '%s' for %s\n", arg, + cl->cur_arg->name); + print_usage_and_die(cl); + } + *(int *)cl->cur_arg->value = intval; + break; + case ARGTYPE_BOOL: + if (0 == strcmp(arg, "1") || 0 == strcmp(arg, "true")) { + *(int *)cl->cur_arg->value = 1; + } else if (0 == strcmp(arg, "0") || 0 == strcmp(arg, "false")) { + *(int *)cl->cur_arg->value = 0; + } else { + fprintf(stderr, "expected boolean, got '%s' for %s\n", arg, + cl->cur_arg->name); + print_usage_and_die(cl); + } + break; + case ARGTYPE_STRING: + *(char **)cl->cur_arg->value = arg; + break; + } + + cl->state = normal_state; +} + +static void normal_state(gpr_cmdline *cl, char *arg) { + char *eq = NULL; + char *tmp = NULL; + char *arg_name = NULL; + + if (0 == strcmp(arg, "-help") || 0 == strcmp(arg, "--help") || + 0 == strcmp(arg, "-h")) { + print_usage_and_die(cl); + } + + cl->cur_arg = NULL; + + if (arg[0] == '-') { + if (arg[1] == '-') { + if (arg[2] == 0) { + /* handle '--' to move to just extra args */ + cl->state = extra_state; + return; + } + arg += 2; + } else { + arg += 1; + } + /* first byte of arg is now past the leading '-' or '--' */ + if (arg[0] == 'n' && arg[1] == 'o' && arg[2] == '-') { + /* arg is of the form '--no-foo' - it's a flag disable */ + arg += 3; + cl->cur_arg = find_arg(cl, arg); + if (cl->cur_arg->type != ARGTYPE_BOOL) { + fprintf(stderr, "%s is not a flag argument\n", arg); + print_usage_and_die(cl); + } + *(int *)cl->cur_arg->value = 0; + return; /* early out */ + } + eq = strchr(arg, '='); + if (eq != NULL) { + /* copy the string into a temp buffer and extract the name */ + tmp = arg_name = gpr_malloc(eq - arg + 1); + memcpy(arg_name, arg, eq - arg); + arg_name[eq - arg] = 0; + } else { + arg_name = arg; + } + cl->cur_arg = find_arg(cl, arg_name); + if (eq != NULL) { + /* arg was of the type --foo=value, parse the value */ + 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 { + extra_state(cl, arg); + } + + gpr_free(tmp); +} + +void 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++) { + cl->state(cl, argv[i]); + } +} diff --git a/src/core/support/cpu.h b/src/core/support/cpu.h new file mode 100644 index 0000000000..6ac0db35e5 --- /dev/null +++ b/src/core/support/cpu.h @@ -0,0 +1,49 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_SUPPORT_CPU_H__ +#define __GRPC_INTERNAL_SUPPORT_CPU_H__ + +/* Interface providing CPU information for currently running system */ + +/* Return the number of CPU cores on the current system. Will return 0 if + if information is not available. */ +int gpr_cpu_num_cores(); + +/* Return the CPU on which the current thread is executing; N.B. This should + be considered advisory only - it is possible that the thread is switched + to a different CPU at any time. Returns a value in range + [0, gpr_cpu_num_cores() - 1] */ +int gpr_cpu_current_cpu(); + +#endif /* __GRPC_INTERNAL_SUPPORT_CPU_H__ */ diff --git a/src/core/support/cpu_posix.c b/src/core/support/cpu_posix.c new file mode 100644 index 0000000000..82d58de2b4 --- /dev/null +++ b/src/core/support/cpu_posix.c @@ -0,0 +1,71 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/support/cpu.h" + +#ifdef __linux__ +#include <errno.h> +#include <unistd.h> +#define _GNU_SOURCE +#define __USE_GNU +#define __USE_MISC +#include <sched.h> +#undef _GNU_SOURCE +#undef __USE_GNU +#undef __USE_MISC +#include <string.h> + +#include <grpc/support/log.h> + +int gpr_cpu_num_cores() { + static int ncpus = 0; + if (ncpus == 0) { + ncpus = sysconf(_SC_NPROCESSORS_ONLN); + if (ncpus < 1) { + gpr_log(GPR_ERROR, "Cannot determine number of CPUs: assuming 1"); + ncpus = 1; + } + } + return ncpus; +} + +int gpr_cpu_current_cpu() { + int cpu = sched_getcpu(); + if (cpu < 0) { + gpr_log(GPR_ERROR, "Error determining current CPU: %s\n", strerror(errno)); + return 0; + } + return cpu; +} + +#endif /* __linux__ */ diff --git a/src/core/support/histogram.c b/src/core/support/histogram.c new file mode 100644 index 0000000000..a3ecd3e152 --- /dev/null +++ b/src/core/support/histogram.c @@ -0,0 +1,226 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/port_platform.h> +#include <grpc/support/log.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 */ + gpr_uint32 *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, 0, h->max_possible)); + GPR_ASSERT(bucket >= 0); + 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(gpr_uint32) * h->num_buckets); + memset(h->buckets, 0, sizeof(gpr_uint32) * 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, gpr_histogram *src) { + int i; + if ((dst->num_buckets != src->num_buckets) || + (dst->multiplier != src->multiplier)) { + /* Fail because these histograms don't match */ + return 0; + } + dst->sum += src->sum; + dst->sum_of_squares += src->sum_of_squares; + dst->count += src->count; + if (src->min_seen < dst->min_seen) { + dst->min_seen = src->min_seen; + } + if (src->max_seen > dst->max_seen) { + dst->max_seen = src->max_seen; + } + for (i = 0; i < dst->num_buckets; i++) { + dst->buckets[i] += src->buckets[i]; + } + return 1; +} + +static double threshold_for_count_below(gpr_histogram *h, double count_below) { + double count_so_far; + double lower_bound; + double upper_bound; + int lower_idx; + int upper_idx; + + GPR_ASSERT(h->count >= 1); + + 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, lower_idx) + bucket_start(h, upper_idx)) / 2.0; + } else { + /* treat values as uniform throughout the bucket, and find where this value + should lie */ + lower_bound = bucket_start(h, lower_idx); + upper_bound = bucket_start(h, 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); + 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; +} diff --git a/src/core/support/host_port.c b/src/core/support/host_port.c new file mode 100644 index 0000000000..02500551fc --- /dev/null +++ b/src/core/support/host_port.c @@ -0,0 +1,49 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/log.h> +#include <grpc/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); + } +} diff --git a/src/core/support/log.c b/src/core/support/log.c new file mode 100644 index 0000000000..79321f7ffe --- /dev/null +++ b/src/core/support/log.c @@ -0,0 +1,48 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 <stdio.h> + +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"; + } + return "UNKNOWN"; +} diff --git a/src/core/support/log_android.c b/src/core/support/log_android.c new file mode 100644 index 0000000000..9e2b03471f --- /dev/null +++ b/src/core/support/log_android.c @@ -0,0 +1,86 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 <grpc/support/log.h> +#include <grpc/support/time.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <android/log.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 *final_slash; + const char *display_file; + char *prefix = NULL; + char *suffix = NULL; + char *output = NULL; + va_list args; + va_start(args, format); + + final_slash = strrchr(file, '/'); + if (final_slash == NULL) + display_file = file; + else + display_file = final_slash + 1; + + asprintf(&prefix, "%s:%d] ", display_file, line); + vasprintf(&suffix, format, args); + asprintf(&output, "%s%s", prefix, suffix); + va_end(args); + + __android_log_write(severity_to_log_priority(severity), "GRPC", output); + + /* allocated by asprintf => use free, not gpr_free */ + free(prefix); + free(suffix); + free(output); +} + +#endif /* GPR_ANDROID */ diff --git a/src/core/support/log_linux.c b/src/core/support/log_linux.c new file mode 100644 index 0000000000..e39e2cc166 --- /dev/null +++ b/src/core/support/log_linux.c @@ -0,0 +1,85 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#define _POSIX_SOURCE +#define _GNU_SOURCE +#include <grpc/support/port_platform.h> + +#ifdef GPR_LINUX + +#include <grpc/support/log.h> +#include <grpc/support/time.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <time.h> +#include <linux/unistd.h> +#include <sys/syscall.h> +#include <unistd.h> + +static long gettid() { return syscall(__NR_gettid); } + +void gpr_log(const char *file, int line, gpr_log_severity severity, + const char *format, ...) { + char *final_slash; + const char *display_file; + char time_buffer[64]; + gpr_timespec now = gpr_now(); + struct tm tm; + va_list args; + va_start(args, format); + + final_slash = strrchr(file, '/'); + if (final_slash == NULL) + display_file = file; + else + display_file = final_slash + 1; + + if (!localtime_r(&now.tv_sec, &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"); + } + + flockfile(stderr); + fprintf(stderr, "%s%s.%09d %7ld %s:%d] ", gpr_log_severity_string(severity), + time_buffer, (int)(now.tv_nsec), gettid(), display_file, line); + vfprintf(stderr, format, args); + fputc('\n', stderr); + funlockfile(stderr); + + va_end(args); +} + +#endif diff --git a/src/core/support/log_posix.c b/src/core/support/log_posix.c new file mode 100644 index 0000000000..68882f7e89 --- /dev/null +++ b/src/core/support/log_posix.c @@ -0,0 +1,83 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#define _POSIX_SOURCE +#define _GNU_SOURCE +#include <grpc/support/port_platform.h> + +#if defined(GPR_POSIX_LOG) + +#include <grpc/support/log.h> +#include <grpc/support/time.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <time.h> +#include <pthread.h> + +static long gettid() { return pthread_self(); } + +void gpr_log(const char *file, int line, gpr_log_severity severity, + const char *format, ...) { + char *final_slash; + const char *display_file; + char time_buffer[64]; + gpr_timespec now = gpr_now(); + struct tm tm; + va_list args; + va_start(args, format); + + final_slash = strrchr(file, '/'); + if (final_slash == NULL) + display_file = file; + else + display_file = final_slash + 1; + + if (!localtime_r(&now.tv_sec, &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"); + } + + flockfile(stderr); + fprintf(stderr, "%s%s.%09d %7ld %s:%d] ", gpr_log_severity_string(severity), + time_buffer, (int)(now.tv_nsec), gettid(), display_file, line); + vfprintf(stderr, format, args); + fputc('\n', stderr); + funlockfile(stderr); + + va_end(args); +} + +#endif /* defined(GPR_POSIX_LOG) */ diff --git a/src/core/support/log_win32.c b/src/core/support/log_win32.c new file mode 100644 index 0000000000..f5710fa179 --- /dev/null +++ b/src/core/support/log_win32.c @@ -0,0 +1,55 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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> +#include <stdio.h> +#include <stdarg.h> + +/* Simple starter implementation */ +void gpr_log(char *file, int line, gpr_log_severity severity, char *format, + ...) { + va_list args; + va_start(args, format); + + fprintf(stderr, "%s %s:%d: ", gpr_log_severity_string(severity), file, line); + vfprintf(stderr, format, args); + fputc('\n', stderr); + + va_end(args); +} + +#endif diff --git a/src/core/support/murmur_hash.c b/src/core/support/murmur_hash.c new file mode 100644 index 0000000000..5d30263e52 --- /dev/null +++ b/src/core/support/murmur_hash.c @@ -0,0 +1,94 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/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)] + +gpr_uint32 gpr_murmur_hash3(const void* key, size_t len, gpr_uint32 seed) { + const gpr_uint8* data = (const gpr_uint8*)key; + const int nblocks = len / 4; + int i; + + gpr_uint32 h1 = seed; + gpr_uint32 k1 = 0; + + const gpr_uint32 c1 = 0xcc9e2d51; + const gpr_uint32 c2 = 0x1b873593; + + const gpr_uint32* blocks = (const uint32_t*)(data + nblocks * 4); + const uint8_t* tail = (const uint8_t*)(data + nblocks * 4); + + /* body */ + for (i = -nblocks; i; i++) { + gpr_uint32 k1 = GETBLOCK32(blocks, i); + + k1 *= c1; + k1 = ROTL32(k1, 15); + k1 *= c2; + + h1 ^= k1; + h1 = ROTL32(h1, 13); + h1 = h1 * 5 + 0xe6546b64; + } + + /* tail */ + switch (len & 3) { + case 3: + k1 ^= tail[2] << 16; + case 2: + k1 ^= tail[1] << 8; + case 1: + k1 ^= tail[0]; + k1 *= c1; + k1 = ROTL32(k1, 15); + k1 *= c2; + h1 ^= k1; + }; + + /* finalization */ + h1 ^= len; + FMIX32(h1); + return h1; +} diff --git a/src/core/support/murmur_hash.h b/src/core/support/murmur_hash.h new file mode 100644 index 0000000000..5643717cd2 --- /dev/null +++ b/src/core/support/murmur_hash.h @@ -0,0 +1,44 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_SUPPORT_MURMUR_HASH_H__ +#define __GRPC_INTERNAL_SUPPORT_MURMUR_HASH_H__ + +#include <grpc/support/port_platform.h> + +#include <stddef.h> + +/* compute the hash of key (length len) */ +gpr_uint32 gpr_murmur_hash3(const void *key, size_t len, gpr_uint32 seed); + +#endif /* __GRPC_INTERNAL_SUPPORT_MURMUR_HASH_H__ */ diff --git a/src/core/support/slice.c b/src/core/support/slice.c new file mode 100644 index 0000000000..fcdeb478fb --- /dev/null +++ b/src/core/support/slice.c @@ -0,0 +1,325 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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() { + 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_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 = (gpr_uint8 *)(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 = length; + } + return slice; +} + +gpr_slice gpr_slice_sub_no_ref(gpr_slice source, size_t begin, size_t end) { + gpr_slice subset; + + if (source.refcount) { + /* Enforce preconditions */ + GPR_ASSERT(source.data.refcounted.length >= begin); + GPR_ASSERT(source.data.refcounted.length >= end); + GPR_ASSERT(end >= begin); + + /* 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 { + subset.refcount = NULL; + subset.data.inlined.length = 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 = 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 = source->data.inlined.length - split; + memcpy(tail.data.inlined.bytes, source->data.inlined.bytes + split, + tail.data.inlined.length); + source->data.inlined.length = 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 = 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 = split; + memcpy(head.data.inlined.bytes, source->data.inlined.bytes, split); + 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 = 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 = 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 = 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/support/slice_buffer.c b/src/core/support/slice_buffer.c new file mode 100644 index 0000000000..2ade049c89 --- /dev/null +++ b/src/core/support/slice_buffer.c @@ -0,0 +1,155 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_buffer.h> + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +/* initial allocation size (# of slices) */ +#define INITIAL_CAPACITY 4 +/* grow a buffer; requires INITIAL_CAPACITY > 1 */ +#define GROW(x) (3 * (x) / 2) + +void gpr_slice_buffer_init(gpr_slice_buffer *sb) { + sb->count = 0; + sb->length = 0; + sb->capacity = INITIAL_CAPACITY; + sb->slices = gpr_malloc(sizeof(gpr_slice) * INITIAL_CAPACITY); +} + +void gpr_slice_buffer_destroy(gpr_slice_buffer *sb) { + gpr_slice_buffer_reset_and_unref(sb); + gpr_free(sb->slices); +} + +gpr_uint8 *gpr_slice_buffer_tiny_add(gpr_slice_buffer *sb, int n) { + gpr_slice *back; + gpr_uint8 *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 += n; + return out; + +add_new: + if (sb->count == sb->capacity) { + sb->capacity = GROW(sb->capacity); + GPR_ASSERT(sb->capacity > sb->count); + sb->slices = gpr_realloc(sb->slices, sb->capacity * sizeof(gpr_slice)); + } + back = &sb->slices[sb->count]; + sb->count++; + back->refcount = NULL; + back->data.inlined.length = 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; + if (out == sb->capacity) { + sb->capacity = GROW(sb->capacity); + GPR_ASSERT(sb->capacity > sb->count); + sb->slices = gpr_realloc(sb->slices, sb->capacity * sizeof(gpr_slice)); + } + 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 += 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; + if (n == sb->capacity) { + sb->capacity = GROW(sb->capacity); + GPR_ASSERT(sb->capacity > sb->count); + sb->slices = + gpr_realloc(sb->slices, sb->capacity * sizeof(gpr_slice)); + } + back = &sb->slices[n]; + sb->count = n + 1; + back->refcount = NULL; + back->data.inlined.length = 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_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; +} diff --git a/src/core/support/string.c b/src/core/support/string.c new file mode 100644 index 0000000000..b1f0795846 --- /dev/null +++ b/src/core/support/string.c @@ -0,0 +1,124 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/string.h> + +#include <ctype.h> +#include <stddef.h> +#include <string.h> + +#include <grpc/support/alloc.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; +} hexout; + +static hexout hexout_create() { + hexout r = {0, 0, NULL}; + return r; +} + +static void hexout_append(hexout *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; +} + +char *gpr_hexdump(const char *buf, size_t len, gpr_uint32 flags) { + static const char hex[16] = "0123456789abcdef"; + hexout out = hexout_create(); + + const gpr_uint8 *const beg = (const gpr_uint8 *)buf; + const gpr_uint8 *const end = beg + len; + const gpr_uint8 *cur; + + for (cur = beg; cur != end; ++cur) { + if (cur != beg) hexout_append(&out, ' '); + hexout_append(&out, hex[*cur >> 4]); + hexout_append(&out, hex[*cur & 0xf]); + } + + if (flags & GPR_HEXDUMP_PLAINTEXT) { + cur = beg; + if (len) hexout_append(&out, ' '); + hexout_append(&out, '\''); + for (cur = beg; cur != end; ++cur) { + hexout_append(&out, isprint(*cur) ? *cur : '.'); + } + hexout_append(&out, '\''); + } + + hexout_append(&out, 0); + + return out.data; +} + +int gpr_parse_bytes_to_uint32(const char *buf, size_t len, gpr_uint32 *result) { + gpr_uint32 out = 0; + gpr_uint32 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 + (buf[i] - '0'); + if (new < out) return 0; /* overflow */ + out = new; + } + + *result = out; + return 1; +} diff --git a/src/core/support/string_posix.c b/src/core/support/string_posix.c new file mode 100644 index 0000000000..d1da37923f --- /dev/null +++ b/src/core/support/string_posix.c @@ -0,0 +1,86 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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. */ + +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200112L +#endif + +#include <stdio.h> +#include <stdarg.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 (!(0 <= ret && ret < ~(size_t)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 (ret == strp_buflen - 1) { + return ret; + } + + /* This should never happen. */ + gpr_free(*strp); + *strp = NULL; + return -1; +} diff --git a/src/core/support/sync.c b/src/core/support/sync.c new file mode 100644 index 0000000000..40e5465e5d --- /dev/null +++ b/src/core/support/sync.c @@ -0,0 +1,135 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/log.h> +#include <grpc/support/sync.h> +#include <grpc/support/atm.h> + +/* Number of mutexes to allocate for events, to avoid lock contention. + Should be a prime. */ +enum { event_sync_partitions = 31 }; + +/* Event 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[((gpr_uintptr)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_ASSERT(value != NULL); + gpr_atm_rel_store(&ev->state, (gpr_atm)value); + gpr_cv_broadcast(&s->cv); + gpr_mu_unlock(&s->mu); +} + +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_event_cancellable_wait(gpr_event *ev, gpr_timespec abs_deadline, + gpr_cancellable *c) { + 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_cancellable_wait(&s->cv, &s->mu, abs_deadline, c)); + 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_refn(gpr_refcount *r, int n) { + gpr_atm_no_barrier_fetch_add(&r->count, n); +} + +int gpr_unref(gpr_refcount *r) { + return gpr_atm_full_fetch_add(&r->count, -1) == 1; +} + +void gpr_stats_init(gpr_stats_counter *c, gpr_intptr n) { + gpr_atm_rel_store(&c->value, n); +} + +void gpr_stats_inc(gpr_stats_counter *c, gpr_intptr inc) { + gpr_atm_no_barrier_fetch_add(&c->value, inc); +} + +gpr_intptr 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/support/sync_posix.c b/src/core/support/sync_posix.c new file mode 100644 index 0000000000..257a7fbf4e --- /dev/null +++ b/src/core/support/sync_posix.c @@ -0,0 +1,82 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 gpr synchroization support code. */ + +#include <errno.h> +#include <grpc/support/log.h> +#include <grpc/support/sync.h> +#include <grpc/support/time.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_ASSERT(pthread_mutex_lock(mu) == 0); } + +void gpr_mu_unlock(gpr_mu *mu) { GPR_ASSERT(pthread_mutex_unlock(mu) == 0); } + +int gpr_mu_trylock(gpr_mu *mu) { + int err = pthread_mutex_trylock(mu); + GPR_ASSERT(err == 0 || err == EBUSY); + 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) == 0) { + err = pthread_cond_wait(cv, mu); + } else { + err = pthread_cond_timedwait(cv, mu, &abs_deadline); + } + 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); +} diff --git a/src/core/support/sync_win32.c b/src/core/support/sync_win32.c new file mode 100644 index 0000000000..63dd4eb708 --- /dev/null +++ b/src/core/support/sync_win32.c @@ -0,0 +1,120 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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. */ + +#define _WIN32_WINNT 0x0600 +#include <windows.h> +#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; + if (gpr_time_cmp(abs_deadline, gpr_inf_future) == 0) { + SleepConditionVariableCS(cv, &mu->cs, INFINITE); + } else { + gpr_timespec now = gpr_now(); + gpr_int64 now_ms = now.tv_sec * 1000 + now.tv_nsec / 1000000; + gpr_int64 deadline_ms = + abs_deadline.tv_sec * 1000 + abs_deadline.tv_nsec / 1000000; + if (now_ms >= deadline_ms) { + timeout = 1; + } else { + timeout = + (SleepConditionVariableCS(cv, &mu->cs, deadline_ms - now_ms) == 0 && + GetLastError() == ERROR_TIMEOUT); + } + } + 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 int 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); +} diff --git a/src/core/support/thd_internal.h b/src/core/support/thd_internal.h new file mode 100644 index 0000000000..519177a555 --- /dev/null +++ b/src/core/support/thd_internal.h @@ -0,0 +1,39 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_SUPPORT_THD_INTERNAL_H__ +#define __GRPC_INTERNAL_SUPPORT_THD_INTERNAL_H__ + +/* Internal interfaces between modules within the gpr support library. */ + +#endif /* __GRPC_INTERNAL_SUPPORT_THD_INTERNAL_H__ */ diff --git a/src/core/support/thd_posix.c b/src/core/support/thd_posix.c new file mode 100644 index 0000000000..c86eea415d --- /dev/null +++ b/src/core/support/thd_posix.c @@ -0,0 +1,78 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 <pthread.h> +#include <stdlib.h> +#include <string.h> +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/thd.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; + gpr_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; + struct thd_arg *a = gpr_malloc(sizeof(*a)); + a->body = thd_body; + a->arg = arg; + + GPR_ASSERT(pthread_attr_init(&attr) == 0); + GPR_ASSERT(pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) == 0); + thread_started = (pthread_create(t, &attr, &thread_body, a) == 0); + GPR_ASSERT(pthread_attr_destroy(&attr) == 0); + if (!thread_started) { + gpr_free(a); + } + return thread_started; +} + +gpr_thd_options gpr_thd_options_default(void) { + gpr_thd_options options; + memset(&options, 0, sizeof(options)); + return options; +} diff --git a/src/core/support/thd_win32.c b/src/core/support/thd_win32.c new file mode 100644 index 0000000000..8440479520 --- /dev/null +++ b/src/core/support/thd_win32.c @@ -0,0 +1,80 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_WIN32 + +#include <windows.h> +#include <string.h> +#include <grpc/support/alloc.h> +#include <grpc/support/thd.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 DWORD thread_body(void *v) { + struct thd_arg a = *(struct thd_arg *)v; + gpr_free(v); + (*a.body)(a.arg); + 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_arg *a = gpr_malloc(sizeof(*a)); + a->body = thd_body; + a->arg = arg; + *t = 0; + handle = CreateThread(NULL, 64 * 1024, &thread_body, a, 0, NULL); + if (handle == NULL) { + gpr_free(a); + } else { + CloseHandle(handle); /* threads are "detached" */ + } + return handle != NULL; +} + +gpr_thd_options gpr_thd_options_default(void) { + gpr_thd_options options; + memset(&options, 0, sizeof(options)); + return options; +} + +#endif /* GPR_WIN32 */ diff --git a/src/core/support/time.c b/src/core/support/time.c new file mode 100644 index 0000000000..1d8765f8cc --- /dev/null +++ b/src/core/support/time.c @@ -0,0 +1,243 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/time.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <grpc/support/log.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); + if (cmp == 0) { + cmp = (a.tv_nsec > b.tv_nsec) - (a.tv_nsec < b.tv_nsec); + } + return cmp; +} + +/* There's no standard TIME_T_MIN and TIME_T_MAX, so we construct them. The + following assumes that signed types are two's-complement and that bytes are + 8 bits. */ + +/* The top bit of integral type t. */ +#define TOP_BIT_OF_TYPE(t) (((gpr_uintmax)1) << ((8 * sizeof(t)) - 1)) + +/* Return whether integral type t is signed. */ +#define TYPE_IS_SIGNED(t) (((t)1) > (t) ~(t)0) + +/* The minimum and maximum value of integral type t. */ +#define TYPE_MIN(t) ((t)(TYPE_IS_SIGNED(t) ? TOP_BIT_OF_TYPE(t) : 0)) +#define TYPE_MAX(t) \ + ((t)(TYPE_IS_SIGNED(t) ? (TOP_BIT_OF_TYPE(t) - 1) \ + : ((TOP_BIT_OF_TYPE(t) - 1) << 1) + 1)) + +const gpr_timespec gpr_time_0 = {0, 0}; +const gpr_timespec gpr_inf_future = {TYPE_MAX(time_t), 0}; +const gpr_timespec gpr_inf_past = {TYPE_MIN(time_t), 0}; + +/* 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(long ns) { + gpr_timespec result; + if (ns == LONG_MAX) { + result = gpr_inf_future; + } else if (ns == LONG_MIN) { + result = gpr_inf_past; + } else if (ns >= 0) { + result.tv_sec = ns / 1000000000; + result.tv_nsec = ns - result.tv_sec * 1000000000; + } else { + /* Calculation carefully formulated to avoid any possible under/overflow. */ + result.tv_sec = (-(999999999 - (ns + 1000000000)) / 1000000000) - 1; + result.tv_nsec = ns - result.tv_sec * 1000000000; + } + return result; +} + +gpr_timespec gpr_time_from_micros(long us) { + gpr_timespec result; + if (us == LONG_MAX) { + result = gpr_inf_future; + } else if (us == LONG_MIN) { + result = gpr_inf_past; + } else if (us >= 0) { + result.tv_sec = us / 1000000; + result.tv_nsec = (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 = (us - result.tv_sec * 1000000) * 1000; + } + return result; +} + +gpr_timespec gpr_time_from_millis(long ms) { + gpr_timespec result; + if (ms == LONG_MAX) { + result = gpr_inf_future; + } else if (ms == LONG_MIN) { + result = gpr_inf_past; + } else if (ms >= 0) { + result.tv_sec = ms / 1000; + result.tv_nsec = (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 = (ms - result.tv_sec * 1000) * 1000000; + } + return result; +} + +gpr_timespec gpr_time_from_seconds(long s) { + gpr_timespec result; + if (s == LONG_MAX) { + result = gpr_inf_future; + } else if (s == LONG_MIN) { + result = gpr_inf_past; + } else { + result.tv_sec = s; + result.tv_nsec = 0; + } + return result; +} + +gpr_timespec gpr_time_from_minutes(long m) { + gpr_timespec result; + if (m >= LONG_MAX / 60) { + result = gpr_inf_future; + } else if (m <= LONG_MIN / 60) { + result = gpr_inf_past; + } else { + result.tv_sec = m * 60; + result.tv_nsec = 0; + } + return result; +} + +gpr_timespec gpr_time_from_hours(long h) { + gpr_timespec result; + if (h >= LONG_MAX / 3600) { + result = gpr_inf_future; + } else if (h <= LONG_MIN / 3600) { + result = gpr_inf_past; + } 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; + int inc = 0; + sum.tv_nsec = a.tv_nsec + b.tv_nsec; + if (sum.tv_nsec >= 1000000000) { + sum.tv_nsec -= 1000000000; + inc++; + } + if (a.tv_sec == TYPE_MAX(time_t) || a.tv_sec == TYPE_MIN(time_t)) { + sum = a; + } else if (b.tv_sec == TYPE_MAX(time_t) || + (b.tv_sec >= 0 && a.tv_sec >= TYPE_MAX(time_t) - b.tv_sec)) { + sum = gpr_inf_future; + } else if (b.tv_sec == TYPE_MIN(time_t) || + (b.tv_sec <= 0 && a.tv_sec <= TYPE_MIN(time_t) - b.tv_sec)) { + sum = gpr_inf_past; + } else { + sum.tv_sec = a.tv_sec + b.tv_sec; + if (inc != 0 && sum.tv_sec == TYPE_MAX(time_t) - 1) { + sum = gpr_inf_future; + } else { + sum.tv_sec += inc; + } + } + return sum; +} + +gpr_timespec gpr_time_sub(gpr_timespec a, gpr_timespec b) { + gpr_timespec diff; + int dec = 0; + diff.tv_nsec = a.tv_nsec - b.tv_nsec; + if (diff.tv_nsec < 0) { + diff.tv_nsec += 1000000000; + dec++; + } + if (a.tv_sec == TYPE_MAX(time_t) || a.tv_sec == TYPE_MIN(time_t)) { + diff = a; + } else if (b.tv_sec == TYPE_MIN(time_t) || + (b.tv_sec <= 0 && a.tv_sec >= TYPE_MAX(time_t) + b.tv_sec)) { + diff = gpr_inf_future; + } else if (b.tv_sec == TYPE_MAX(time_t) || + (b.tv_sec >= 0 && a.tv_sec <= TYPE_MIN(time_t) + b.tv_sec)) { + diff = gpr_inf_past; + } else { + diff.tv_sec = a.tv_sec - b.tv_sec; + if (dec != 0 && diff.tv_sec == TYPE_MIN(time_t) + 1) { + diff = gpr_inf_past; + } else { + diff.tv_sec -= dec; + } + } + return diff; +} + +int gpr_time_similar(gpr_timespec a, gpr_timespec b, gpr_timespec threshold) { + int cmp_ab; + + 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; + } +} + +struct timeval gpr_timeval_from_timespec(gpr_timespec t) { + /* TODO(klempner): Consider whether this should round up, since it is likely + to be used for delays */ + struct timeval tv; + tv.tv_sec = t.tv_sec; + tv.tv_usec = t.tv_nsec / 1000; + return tv; +} + +gpr_timespec gpr_timespec_from_timeval(struct timeval t) { + gpr_timespec ts; + ts.tv_sec = t.tv_sec; + ts.tv_nsec = t.tv_usec * 1000; + return ts; +} diff --git a/src/core/support/time_posix.c b/src/core/support/time_posix.c new file mode 100644 index 0000000000..e7b79d10b1 --- /dev/null +++ b/src/core/support/time_posix.c @@ -0,0 +1,81 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 time support. */ + +/* So we get nanosleep and clock_* */ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 199309L +#endif +#include <stdlib.h> +#include <time.h> +#include <unistd.h> +#include <grpc/support/time.h> + +#if _POSIX_TIMERS > 0 +gpr_timespec gpr_now(void) { + gpr_timespec now; + clock_gettime(CLOCK_REALTIME, &now); + return now; +} +#else +/* For some reason Apple's OSes haven't implemented clock_gettime. */ +/* TODO(klempner): Add special handling for Apple. */ +gpr_timespec gpr_now(void) { + gpr_timespec now; + struct timeval now_tv; + gettimeofday(&now_tv, NULL); + now.tv_sec = now_tv.tv_sec; + now.tv_nsec = now_tv.tv_usec / 1000; + return now; +} +#endif + +void gpr_sleep_until(gpr_timespec until) { + gpr_timespec now; + gpr_timespec delta; + + for (;;) { + /* We could simplify by using clock_nanosleep instead, but it might be + * slightly less portable. */ + now = gpr_now(); + if (gpr_time_cmp(until, now) <= 0) { + return; + } + + delta = gpr_time_sub(until, now); + if (nanosleep(&delta, NULL) == 0) { + break; + } + } +} diff --git a/src/core/support/time_win32.c b/src/core/support/time_win32.c new file mode 100644 index 0000000000..425809144c --- /dev/null +++ b/src/core/support/time_win32.c @@ -0,0 +1,52 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/time.h> +#include <windows.h> + +gpr_timespec gpr_now(void) { + gpr_timespec now_tv; + struct _timeb64 now_tb; + _ftime64(&now_tb); + now_tv.tv_sec = now_tb.time; + now_tv.tv_nsec = now_tb.millitm * 1000000; + return now_tv; +} + +#endif /* GPR_WIN32 */ diff --git a/src/core/surface/byte_buffer.c b/src/core/surface/byte_buffer.c new file mode 100644 index 0000000000..27a6c6e33d --- /dev/null +++ b/src/core/surface/byte_buffer.c @@ -0,0 +1,68 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_byte_buffer_create(gpr_slice *slices, size_t nslices) { + size_t i; + grpc_byte_buffer *bb = malloc(sizeof(grpc_byte_buffer)); + + bb->type = GRPC_BB_SLICE_BUFFER; + gpr_slice_buffer_init(&bb->data.slice_buffer); + for (i = 0; i < nslices; i++) { + gpr_slice_ref(slices[i]); + gpr_slice_buffer_add(&bb->data.slice_buffer, slices[i]); + } + + return bb; +} + +void grpc_byte_buffer_destroy(grpc_byte_buffer *bb) { + switch (bb->type) { + case GRPC_BB_SLICE_BUFFER: + gpr_slice_buffer_destroy(&bb->data.slice_buffer); + break; + } + free(bb); +} + +size_t grpc_byte_buffer_length(grpc_byte_buffer *bb) { + switch (bb->type) { + case GRPC_BB_SLICE_BUFFER: + return bb->data.slice_buffer.length; + } + gpr_log(GPR_ERROR, "should never reach here"); + abort(); +} diff --git a/src/core/surface/byte_buffer_reader.c b/src/core/surface/byte_buffer_reader.c new file mode 100644 index 0000000000..18500b83e8 --- /dev/null +++ b/src/core/surface/byte_buffer_reader.c @@ -0,0 +1,74 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 <grpc/grpc.h> +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/slice_buffer.h> +#include <grpc/byte_buffer.h> + +grpc_byte_buffer_reader *grpc_byte_buffer_reader_create( + grpc_byte_buffer *buffer) { + grpc_byte_buffer_reader *reader = malloc(sizeof(grpc_byte_buffer_reader)); + reader->buffer = buffer; + switch (buffer->type) { + case GRPC_BB_SLICE_BUFFER: + reader->current.index = 0; + } + return reader; +} + +int grpc_byte_buffer_reader_next(grpc_byte_buffer_reader *reader, + gpr_slice *slice) { + grpc_byte_buffer *buffer = reader->buffer; + gpr_slice_buffer *slice_buffer; + switch (buffer->type) { + case GRPC_BB_SLICE_BUFFER: + slice_buffer = &buffer->data.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; + } else { + return 0; + } + break; + } + return 0; +} + +void grpc_byte_buffer_reader_destroy(grpc_byte_buffer_reader *reader) { + free(reader); +} diff --git a/src/core/surface/call.c b/src/core/surface/call.c new file mode 100644 index 0000000000..63d408d2d5 --- /dev/null +++ b/src/core/surface/call.c @@ -0,0 +1,835 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/surface/call.h" +#include "src/core/channel/channel_stack.h" +#include "src/core/channel/metadata_buffer.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string.h> +#include "src/core/surface/channel.h" +#include "src/core/surface/completion_queue.h" +#include "src/core/surface/surface_em.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define INVALID_TAG ((void *)0xdeadbeef) + +/* Pending read queue + + This data structure tracks reads that need to be presented to the completion + queue but are waiting for the application to ask for them. */ + +#define INITIAL_PENDING_READ_COUNT 4 + +typedef struct { + grpc_byte_buffer *byte_buffer; + void *user_data; + void (*on_finish)(void *user_data, grpc_op_error error); +} pending_read; + +/* TODO(ctiller): inline an element or two into this struct to avoid per-call + allocations */ +typedef struct { + pending_read *data; + size_t count; + size_t capacity; +} pending_read_array; + +typedef struct { + size_t drain_pos; + pending_read_array filling; + pending_read_array draining; +} pending_read_queue; + +static void pra_init(pending_read_array *array) { + array->data = gpr_malloc(sizeof(pending_read) * INITIAL_PENDING_READ_COUNT); + array->count = 0; + array->capacity = INITIAL_PENDING_READ_COUNT; +} + +static void pra_destroy(pending_read_array *array, + size_t finish_starting_from) { + size_t i; + for (i = finish_starting_from; i < array->count; i++) { + array->data[i].on_finish(array->data[i].user_data, GRPC_OP_ERROR); + } + gpr_free(array->data); +} + +/* Append an operation to an array, expanding as needed */ +static void pra_push(pending_read_array *a, grpc_byte_buffer *buffer, + void (*on_finish)(void *user_data, grpc_op_error error), + void *user_data) { + if (a->count == a->capacity) { + a->capacity *= 2; + a->data = gpr_realloc(a->data, sizeof(pending_read) * a->capacity); + } + a->data[a->count].byte_buffer = buffer; + a->data[a->count].user_data = user_data; + a->data[a->count].on_finish = on_finish; + a->count++; +} + +static void prq_init(pending_read_queue *q) { + q->drain_pos = 0; + pra_init(&q->filling); + pra_init(&q->draining); +} + +static void prq_destroy(pending_read_queue *q) { + pra_destroy(&q->filling, 0); + pra_destroy(&q->draining, q->drain_pos); +} + +static int prq_is_empty(pending_read_queue *q) { + return (q->drain_pos == q->draining.count && q->filling.count == 0); +} + +static void prq_push(pending_read_queue *q, grpc_byte_buffer *buffer, + void (*on_finish)(void *user_data, grpc_op_error error), + void *user_data) { + pra_push(&q->filling, buffer, on_finish, user_data); +} + +/* Take the first queue element and move it to the completion queue. Do nothing + if q is empty */ +static int prq_pop_to_cq(pending_read_queue *q, void *tag, grpc_call *call, + grpc_completion_queue *cq) { + pending_read_array temp_array; + pending_read *pr; + + if (q->drain_pos == q->draining.count) { + if (q->filling.count == 0) { + return 0; + } + q->draining.count = 0; + q->drain_pos = 0; + /* swap arrays */ + temp_array = q->filling; + q->filling = q->draining; + q->draining = temp_array; + } + + pr = q->draining.data + q->drain_pos; + q->drain_pos++; + grpc_cq_end_read(cq, tag, call, pr->on_finish, pr->user_data, + pr->byte_buffer); + return 1; +} + +/* grpc_call proper */ + +/* the state of a call, based upon which functions have been called against + said call */ +typedef enum { CALL_CREATED, CALL_STARTED, CALL_FINISHED } call_state; + +struct grpc_call { + grpc_completion_queue *cq; + grpc_channel *channel; + grpc_mdctx *metadata_context; + + call_state state; + gpr_uint8 is_client; + gpr_uint8 have_write; + grpc_metadata_buffer incoming_metadata; + + /* protects variables in this section */ + gpr_mu read_mu; + gpr_uint8 reads_done; + gpr_uint8 received_finish; + gpr_uint8 received_metadata; + gpr_uint8 have_read; + gpr_uint8 have_alarm; + /* The current outstanding read message tag (only valid if have_read == 1) */ + void *read_tag; + void *metadata_tag; + void *finished_tag; + pending_read_queue prq; + + grpc_em_alarm alarm; + + /* The current outstanding send message/context/invoke/end tag (only valid if + have_write == 1) */ + void *write_tag; + + /* The final status of the call */ + grpc_status_code status_code; + grpc_mdstr *status_details; + + gpr_refcount internal_refcount; +}; + +#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 do_nothing(void *ignored, grpc_op_error also_ignored) {} + +grpc_call *grpc_call_create(grpc_channel *channel, + const void *server_transport_data) { + grpc_channel_stack *channel_stack = grpc_channel_get_channel_stack(channel); + grpc_call *call = + gpr_malloc(sizeof(grpc_call) + channel_stack->call_stack_size); + call->cq = NULL; + call->channel = channel; + grpc_channel_internal_ref(channel); + call->metadata_context = grpc_channel_get_metadata_context(channel); + call->state = CALL_CREATED; + call->is_client = (server_transport_data == NULL); + call->write_tag = INVALID_TAG; + call->read_tag = INVALID_TAG; + call->metadata_tag = INVALID_TAG; + call->finished_tag = INVALID_TAG; + call->have_read = 0; + call->have_write = 0; + call->have_alarm = 0; + call->received_metadata = 0; + call->status_code = + server_transport_data != NULL ? GRPC_STATUS_OK : GRPC_STATUS_UNKNOWN; + call->status_details = NULL; + call->received_finish = 0; + call->reads_done = 0; + grpc_metadata_buffer_init(&call->incoming_metadata); + gpr_ref_init(&call->internal_refcount, 1); + grpc_call_stack_init(channel_stack, server_transport_data, + CALL_STACK_FROM_CALL(call)); + prq_init(&call->prq); + gpr_mu_init(&call->read_mu); + return call; +} + +void grpc_call_internal_ref(grpc_call *c) { gpr_ref(&c->internal_refcount); } + +void grpc_call_internal_unref(grpc_call *c) { + if (gpr_unref(&c->internal_refcount)) { + grpc_call_stack_destroy(CALL_STACK_FROM_CALL(c)); + grpc_metadata_buffer_destroy(&c->incoming_metadata, GRPC_OP_OK); + if (c->status_details) { + grpc_mdstr_unref(c->status_details); + } + prq_destroy(&c->prq); + gpr_mu_destroy(&c->read_mu); + grpc_channel_internal_unref(c->channel); + gpr_free(c); + } +} + +void grpc_call_destroy(grpc_call *c) { + gpr_mu_lock(&c->read_mu); + if (c->have_alarm) { + void *arg_was; + grpc_em_alarm_cancel(&c->alarm, &arg_was); + c->have_alarm = 0; + } + gpr_mu_unlock(&c->read_mu); + grpc_call_internal_unref(c); +} + +grpc_call_error grpc_call_cancel(grpc_call *c) { + grpc_call_element *elem; + grpc_call_op op; + + op.type = GRPC_CANCEL_OP; + op.dir = GRPC_CALL_DOWN; + op.flags = 0; + op.done_cb = do_nothing; + op.user_data = NULL; + + elem = CALL_ELEM_FROM_CALL(c, 0); + elem->filter->call_op(elem, &op); + + return GRPC_CALL_OK; +} + +void grpc_call_execute_op(grpc_call *call, grpc_call_op *op) { + grpc_call_element *elem; + GPR_ASSERT(op->dir == GRPC_CALL_DOWN); + elem = CALL_ELEM_FROM_CALL(call, 0); + elem->filter->call_op(elem, op); +} + +grpc_call_error grpc_call_add_metadata(grpc_call *call, grpc_metadata *metadata, + gpr_uint32 flags) { + grpc_call_element *elem; + grpc_call_op op; + + if (call->state >= CALL_STARTED) { + return GRPC_CALL_ERROR_ALREADY_INVOKED; + } + + op.type = GRPC_SEND_METADATA; + op.dir = GRPC_CALL_DOWN; + op.flags = flags; + op.done_cb = do_nothing; + op.user_data = NULL; + op.data.metadata = grpc_mdelem_from_string_and_buffer( + call->metadata_context, metadata->key, (gpr_uint8 *)metadata->value, + metadata->value_length); + + elem = CALL_ELEM_FROM_CALL(call, 0); + elem->filter->call_op(elem, &op); + + return GRPC_CALL_OK; +} + +static void done_invoke(void *user_data, grpc_op_error error) { + grpc_call *call = user_data; + void *tag = call->write_tag; + + GPR_ASSERT(call->have_write); + call->have_write = 0; + call->write_tag = INVALID_TAG; + grpc_cq_end_invoke_accepted(call->cq, tag, call, NULL, NULL, error); +} + +static void finish_call(grpc_call *call) { + grpc_status status; + status.code = call->status_code; + status.details = call->status_details + ? (char *)grpc_mdstr_as_c_string(call->status_details) + : NULL; + grpc_cq_end_finished(call->cq, call->finished_tag, call, NULL, NULL, status); +} + +grpc_call_error grpc_call_start_invoke(grpc_call *call, + grpc_completion_queue *cq, + void *invoke_accepted_tag, + void *metadata_read_tag, + void *finished_tag, gpr_uint32 flags) { + grpc_call_element *elem; + grpc_call_op op; + + /* validate preconditions */ + if (!call->is_client) { + gpr_log(GPR_ERROR, "can only call %s on clients", __FUNCTION__); + return GRPC_CALL_ERROR_NOT_ON_SERVER; + } + + if (call->state >= CALL_STARTED || call->cq) { + gpr_log(GPR_ERROR, "call is already invoked"); + return GRPC_CALL_ERROR_ALREADY_INVOKED; + } + + if (call->have_write) { + gpr_log(GPR_ERROR, "can only have one pending write operation at a time"); + return GRPC_CALL_ERROR_TOO_MANY_OPERATIONS; + } + + if (call->have_read) { + gpr_log(GPR_ERROR, "can only have one pending read operation at a time"); + return GRPC_CALL_ERROR_TOO_MANY_OPERATIONS; + } + + if (flags & GRPC_WRITE_NO_COMPRESS) { + return GRPC_CALL_ERROR_INVALID_FLAGS; + } + + /* inform the completion queue of an incoming operation */ + grpc_cq_begin_op(cq, call, GRPC_FINISHED); + grpc_cq_begin_op(cq, call, GRPC_CLIENT_METADATA_READ); + grpc_cq_begin_op(cq, call, GRPC_INVOKE_ACCEPTED); + + gpr_mu_lock(&call->read_mu); + + /* update state */ + call->cq = cq; + call->state = CALL_STARTED; + call->finished_tag = finished_tag; + + if (call->received_finish) { + /* handle early cancellation */ + grpc_cq_end_invoke_accepted(call->cq, invoke_accepted_tag, call, NULL, NULL, + GRPC_OP_ERROR); + grpc_cq_end_client_metadata_read(call->cq, metadata_read_tag, call, NULL, + NULL, 0, NULL); + finish_call(call); + + /* early out.. unlock & return */ + gpr_mu_unlock(&call->read_mu); + return GRPC_CALL_OK; + } + + call->write_tag = invoke_accepted_tag; + call->metadata_tag = metadata_read_tag; + + call->have_write = 1; + + gpr_mu_unlock(&call->read_mu); + + /* call down the filter stack */ + op.type = GRPC_SEND_START; + op.dir = GRPC_CALL_DOWN; + op.flags = flags; + op.done_cb = done_invoke; + op.user_data = call; + + elem = CALL_ELEM_FROM_CALL(call, 0); + elem->filter->call_op(elem, &op); + + return GRPC_CALL_OK; +} + +grpc_call_error grpc_call_accept(grpc_call *call, grpc_completion_queue *cq, + void *finished_tag, gpr_uint32 flags) { + grpc_call_element *elem; + grpc_call_op op; + + /* validate preconditions */ + if (call->is_client) { + gpr_log(GPR_ERROR, "can only call %s on servers", __FUNCTION__); + return GRPC_CALL_ERROR_NOT_ON_CLIENT; + } + + if (call->state >= CALL_STARTED) { + gpr_log(GPR_ERROR, "call is already invoked"); + return GRPC_CALL_ERROR_ALREADY_INVOKED; + } + + if (flags & GRPC_WRITE_NO_COMPRESS) { + return GRPC_CALL_ERROR_INVALID_FLAGS; + } + + /* inform the completion queue of an incoming operation (corresponding to + finished_tag) */ + grpc_cq_begin_op(cq, call, GRPC_FINISHED); + + /* update state */ + gpr_mu_lock(&call->read_mu); + call->state = CALL_STARTED; + call->cq = cq; + call->finished_tag = finished_tag; + if (prq_is_empty(&call->prq) && call->received_finish) { + finish_call(call); + + /* early out.. unlock & return */ + gpr_mu_unlock(&call->read_mu); + return GRPC_CALL_OK; + } + gpr_mu_unlock(&call->read_mu); + + /* call down */ + op.type = GRPC_SEND_START; + op.dir = GRPC_CALL_DOWN; + op.flags = flags; + op.done_cb = do_nothing; + op.user_data = NULL; + + elem = CALL_ELEM_FROM_CALL(call, 0); + elem->filter->call_op(elem, &op); + + return GRPC_CALL_OK; +} + +static void done_writes_done(void *user_data, grpc_op_error error) { + grpc_call *call = user_data; + void *tag = call->write_tag; + + GPR_ASSERT(call->have_write); + call->have_write = 0; + call->write_tag = INVALID_TAG; + grpc_cq_end_finish_accepted(call->cq, tag, call, NULL, NULL, error); +} + +static void done_write(void *user_data, grpc_op_error error) { + grpc_call *call = user_data; + void *tag = call->write_tag; + + GPR_ASSERT(call->have_write); + call->have_write = 0; + call->write_tag = INVALID_TAG; + grpc_cq_end_write_accepted(call->cq, tag, call, NULL, NULL, error); +} + +void grpc_call_client_initial_metadata_complete( + grpc_call_element *surface_element) { + grpc_call *call = grpc_call_from_top_element(surface_element); + size_t count; + grpc_metadata *elements; + + gpr_mu_lock(&call->read_mu); + count = grpc_metadata_buffer_count(&call->incoming_metadata); + elements = grpc_metadata_buffer_extract_elements(&call->incoming_metadata); + + GPR_ASSERT(!call->received_metadata); + grpc_cq_end_client_metadata_read(call->cq, call->metadata_tag, call, + grpc_metadata_buffer_cleanup_elements, + elements, count, elements); + call->received_metadata = 1; + call->metadata_tag = INVALID_TAG; + gpr_mu_unlock(&call->read_mu); +} + +static void request_more_data(grpc_call *call) { + grpc_call_element *elem; + grpc_call_op op; + + /* call down */ + op.type = GRPC_REQUEST_DATA; + op.dir = GRPC_CALL_DOWN; + op.flags = 0; + op.done_cb = do_nothing; + op.user_data = NULL; + + elem = CALL_ELEM_FROM_CALL(call, 0); + elem->filter->call_op(elem, &op); +} + +grpc_call_error grpc_call_start_read(grpc_call *call, void *tag) { + gpr_uint8 request_more = 0; + + switch (call->state) { + case CALL_CREATED: + return GRPC_CALL_ERROR_NOT_INVOKED; + case CALL_STARTED: + break; + case CALL_FINISHED: + return GRPC_CALL_ERROR_ALREADY_FINISHED; + } + + gpr_mu_lock(&call->read_mu); + + if (call->have_read) { + gpr_mu_unlock(&call->read_mu); + return GRPC_CALL_ERROR_TOO_MANY_OPERATIONS; + } + + grpc_cq_begin_op(call->cq, call, GRPC_READ); + + if (!prq_pop_to_cq(&call->prq, tag, call, call->cq)) { + if (call->reads_done) { + grpc_cq_end_read(call->cq, tag, call, do_nothing, NULL, NULL); + } else { + call->read_tag = tag; + call->have_read = 1; + request_more = 1; + } + } else if (prq_is_empty(&call->prq) && call->received_finish) { + finish_call(call); + } + + gpr_mu_unlock(&call->read_mu); + + if (request_more) { + request_more_data(call); + } + + return GRPC_CALL_OK; +} + +grpc_call_error grpc_call_start_write(grpc_call *call, + grpc_byte_buffer *byte_buffer, void *tag, + gpr_uint32 flags) { + grpc_call_element *elem; + grpc_call_op op; + + switch (call->state) { + case CALL_CREATED: + return GRPC_CALL_ERROR_NOT_INVOKED; + case CALL_STARTED: + break; + case CALL_FINISHED: + return GRPC_CALL_ERROR_ALREADY_FINISHED; + } + + if (call->have_write) { + return GRPC_CALL_ERROR_TOO_MANY_OPERATIONS; + } + + grpc_cq_begin_op(call->cq, call, GRPC_WRITE_ACCEPTED); + + /* for now we do no buffering, so a NULL byte_buffer can have no impact + on our behavior -- succeed immediately */ + /* TODO(ctiller): if flags & GRPC_WRITE_BUFFER_HINT == 0, this indicates a + flush, and that flush should be propogated down from here */ + if (byte_buffer == NULL) { + grpc_cq_end_write_accepted(call->cq, tag, call, NULL, NULL, GRPC_OP_OK); + return GRPC_CALL_OK; + } + + call->write_tag = tag; + call->have_write = 1; + + op.type = GRPC_SEND_MESSAGE; + op.dir = GRPC_CALL_DOWN; + op.flags = flags; + op.done_cb = done_write; + op.user_data = call; + op.data.message = byte_buffer; + + elem = CALL_ELEM_FROM_CALL(call, 0); + elem->filter->call_op(elem, &op); + + return GRPC_CALL_OK; +} + +grpc_call_error grpc_call_writes_done(grpc_call *call, void *tag) { + grpc_call_element *elem; + grpc_call_op op; + + if (!call->is_client) { + return GRPC_CALL_ERROR_NOT_ON_SERVER; + } + + switch (call->state) { + case CALL_CREATED: + return GRPC_CALL_ERROR_NOT_INVOKED; + case CALL_FINISHED: + return GRPC_CALL_ERROR_ALREADY_FINISHED; + case CALL_STARTED: + break; + } + + if (call->have_write) { + return GRPC_CALL_ERROR_TOO_MANY_OPERATIONS; + } + + grpc_cq_begin_op(call->cq, call, GRPC_FINISH_ACCEPTED); + + call->write_tag = tag; + call->have_write = 1; + + op.type = GRPC_SEND_FINISH; + op.dir = GRPC_CALL_DOWN; + op.flags = 0; + op.done_cb = done_writes_done; + op.user_data = call; + + elem = CALL_ELEM_FROM_CALL(call, 0); + elem->filter->call_op(elem, &op); + + return GRPC_CALL_OK; +} + +grpc_call_error grpc_call_start_write_status(grpc_call *call, + grpc_status status, void *tag) { + grpc_call_element *elem; + grpc_call_op op; + + if (call->is_client) { + return GRPC_CALL_ERROR_NOT_ON_CLIENT; + } + + switch (call->state) { + case CALL_CREATED: + return GRPC_CALL_ERROR_NOT_INVOKED; + case CALL_FINISHED: + return GRPC_CALL_ERROR_ALREADY_FINISHED; + case CALL_STARTED: + break; + } + + if (call->have_write) { + return GRPC_CALL_ERROR_TOO_MANY_OPERATIONS; + } + + elem = CALL_ELEM_FROM_CALL(call, 0); + + if (status.details && status.details[0]) { + grpc_mdelem *md = grpc_mdelem_from_strings(call->metadata_context, + "grpc-message", status.details); + + op.type = GRPC_SEND_METADATA; + op.dir = GRPC_CALL_DOWN; + op.flags = 0; + op.done_cb = do_nothing; + op.user_data = NULL; + op.data.metadata = md; + elem->filter->call_op(elem, &op); + } + + /* always send status */ + { + grpc_mdelem *md; + char buffer[32]; + sprintf(buffer, "%d", status.code); + md = + grpc_mdelem_from_strings(call->metadata_context, "grpc-status", buffer); + + op.type = GRPC_SEND_METADATA; + op.dir = GRPC_CALL_DOWN; + op.flags = 0; + op.done_cb = do_nothing; + op.user_data = NULL; + op.data.metadata = md; + elem->filter->call_op(elem, &op); + } + + grpc_cq_begin_op(call->cq, call, GRPC_FINISH_ACCEPTED); + + call->state = CALL_FINISHED; + call->write_tag = tag; + call->have_write = 1; + + op.type = GRPC_SEND_FINISH; + op.dir = GRPC_CALL_DOWN; + op.flags = 0; + op.done_cb = done_writes_done; + op.user_data = call; + + elem->filter->call_op(elem, &op); + + return GRPC_CALL_OK; +} + +/* 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 gpr_uint32 decode_status(grpc_mdelem *md) { + gpr_uint32 status; + void *user_data = grpc_mdelem_get_user_data(md, destroy_status); + if (user_data) { + status = ((gpr_uint32)(gpr_intptr)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 *)(gpr_intptr)(status + STATUS_OFFSET)); + } + return status; +} + +void grpc_call_recv_metadata(grpc_call_element *elem, grpc_call_op *op) { + grpc_call *call = CALL_FROM_TOP_ELEM(elem); + grpc_mdelem *md = op->data.metadata; + grpc_mdstr *key = md->key; + if (key == grpc_channel_get_status_string(call->channel)) { + call->status_code = decode_status(md); + grpc_mdelem_unref(md); + op->done_cb(op->user_data, GRPC_OP_OK); + } else if (key == grpc_channel_get_message_string(call->channel)) { + if (call->status_details) { + grpc_mdstr_unref(call->status_details); + } + call->status_details = grpc_mdstr_ref(md->value); + grpc_mdelem_unref(md); + op->done_cb(op->user_data, GRPC_OP_OK); + } else { + grpc_metadata_buffer_queue(&call->incoming_metadata, op); + } +} + +void grpc_call_recv_finish(grpc_call_element *elem, int is_full_close) { + grpc_call *call = CALL_FROM_TOP_ELEM(elem); + + gpr_mu_lock(&call->read_mu); + + if (call->have_read) { + grpc_cq_end_read(call->cq, call->read_tag, call, do_nothing, NULL, NULL); + call->read_tag = INVALID_TAG; + call->have_read = 0; + } + if (call->is_client && !call->received_metadata && call->cq) { + size_t count; + grpc_metadata *elements; + + call->received_metadata = 1; + + count = grpc_metadata_buffer_count(&call->incoming_metadata); + elements = grpc_metadata_buffer_extract_elements(&call->incoming_metadata); + grpc_cq_end_client_metadata_read(call->cq, call->metadata_tag, call, + grpc_metadata_buffer_cleanup_elements, + elements, count, elements); + } + if (is_full_close) { + if (call->have_alarm) { + void *arg_was; + grpc_em_alarm_cancel(&call->alarm, &arg_was); + call->have_alarm = 0; + } + call->received_finish = 1; + if (prq_is_empty(&call->prq) && call->cq != NULL) { + finish_call(call); + } + } else { + call->reads_done = 1; + } + gpr_mu_unlock(&call->read_mu); +} + +void grpc_call_recv_message(grpc_call_element *elem, grpc_byte_buffer *message, + void (*on_finish)(void *user_data, + grpc_op_error error), + void *user_data) { + grpc_call *call = CALL_FROM_TOP_ELEM(elem); + + gpr_mu_lock(&call->read_mu); + if (call->have_read) { + grpc_cq_end_read(call->cq, call->read_tag, call, on_finish, user_data, + message); + call->read_tag = INVALID_TAG; + call->have_read = 0; + } else { + prq_push(&call->prq, message, on_finish, user_data); + } + gpr_mu_unlock(&call->read_mu); +} + +grpc_call *grpc_call_from_top_element(grpc_call_element *elem) { + return CALL_FROM_TOP_ELEM(elem); +} + +grpc_metadata_buffer *grpc_call_get_metadata_buffer(grpc_call *call) { + return &call->incoming_metadata; +} + +static void call_alarm(void *arg, grpc_em_cb_status status) { + grpc_call *call = arg; + if (status == GRPC_CALLBACK_SUCCESS) { + grpc_call_cancel(call); + } + grpc_call_internal_unref(call); +} + +void grpc_call_set_deadline(grpc_call_element *elem, gpr_timespec deadline) { + grpc_call *call = CALL_FROM_TOP_ELEM(elem); + + if (call->have_alarm) { + gpr_log(GPR_ERROR, "Attempt to set deadline alarm twice"); + } + grpc_call_internal_ref(call); + call->have_alarm = 1; + grpc_em_alarm_init(&call->alarm, grpc_surface_em(), call_alarm, call); + grpc_em_alarm_add(&call->alarm, deadline); +} diff --git a/src/core/surface/call.h b/src/core/surface/call.h new file mode 100644 index 0000000000..2c785a59fc --- /dev/null +++ b/src/core/surface/call.h @@ -0,0 +1,73 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_SURFACE_CALL_H__ +#define __GRPC_INTERNAL_SURFACE_CALL_H__ + +#include "src/core/channel/channel_stack.h" +#include "src/core/channel/metadata_buffer.h" +#include <grpc/grpc.h> + +grpc_call *grpc_call_create(grpc_channel *channel, + const void *server_transport_data); + +void grpc_call_internal_ref(grpc_call *call); +void grpc_call_internal_unref(grpc_call *call); + +/* Helpers for grpc_client, grpc_server filters to publish received data to + the completion queue/surface layer */ +void grpc_call_recv_metadata(grpc_call_element *surface_element, + grpc_call_op *op); +void grpc_call_recv_message( + grpc_call_element *surface_element, grpc_byte_buffer *message, + void (*on_finish)(void *user_data, grpc_op_error error), void *user_data); +void grpc_call_recv_finish(grpc_call_element *surface_element, + int is_full_close); + +void grpc_call_execute_op(grpc_call *call, grpc_call_op *op); + +/* Called when it's known that the initial batch of metadata is complete on the + client side (must not be called on the server) */ +void grpc_call_client_initial_metadata_complete( + grpc_call_element *surface_element); + +void grpc_call_set_deadline(grpc_call_element *surface_element, + gpr_timespec deadline); + +/* Given the top call_element, get the call object. */ +grpc_call *grpc_call_from_top_element(grpc_call_element *surface_element); + +/* Get the metadata buffer. */ +grpc_metadata_buffer *grpc_call_get_metadata_buffer(grpc_call *call); + +#endif /* __GRPC_INTERNAL_SURFACE_CALL_H__ */ diff --git a/src/core/surface/channel.c b/src/core/surface/channel.c new file mode 100644 index 0000000000..ff994257f4 --- /dev/null +++ b/src/core/surface/channel.c @@ -0,0 +1,152 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/surface/channel.h" + +#include <stdlib.h> +#include <string.h> + +#include "src/core/surface/call.h" +#include "src/core/surface/client.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +struct grpc_channel { + int is_client; + gpr_refcount refs; + grpc_mdctx *metadata_context; + grpc_mdstr *grpc_status_string; + grpc_mdstr *grpc_message_string; +}; + +#define CHANNEL_STACK_FROM_CHANNEL(c) ((grpc_channel_stack *)((c)+1)) + +grpc_channel *grpc_channel_create_from_filters( + const grpc_channel_filter **filters, size_t num_filters, + const grpc_channel_args *args, grpc_mdctx *mdctx, int is_client) { + size_t size = + sizeof(grpc_channel) + grpc_channel_stack_size(filters, num_filters); + grpc_channel *channel = gpr_malloc(size); + channel->is_client = is_client; + /* decremented by grpc_channel_destroy */ + gpr_ref_init(&channel->refs, 1); + channel->metadata_context = mdctx; + channel->grpc_status_string = grpc_mdstr_from_string(mdctx, "grpc-status"); + channel->grpc_message_string = grpc_mdstr_from_string(mdctx, "grpc-message"); + grpc_channel_stack_init(filters, num_filters, args, channel->metadata_context, + CHANNEL_STACK_FROM_CHANNEL(channel)); + return channel; +} + +static void do_nothing(void *ignored, grpc_op_error error) {} + +grpc_call *grpc_channel_create_call(grpc_channel *channel, const char *method, + const char *host, + gpr_timespec absolute_deadline) { + grpc_call *call; + grpc_metadata md; + + if (!channel->is_client) { + gpr_log(GPR_ERROR, "Cannot create a call on the server."); + return NULL; + } + + call = grpc_call_create(channel, NULL); + +#define ADDMD(k, v) \ + do { \ + md.key = (k); \ + md.value = (char *)(v); \ + md.value_length = strlen((v)); \ + grpc_call_add_metadata(call, &md, 0); \ + } while (0) + ADDMD(":method", "POST"); + ADDMD(":scheme", "grpc"); + ADDMD(":path", method); + ADDMD(":authority", host); + ADDMD("content-type", "application/grpc"); + if (0 != gpr_time_cmp(absolute_deadline, gpr_inf_future)) { + grpc_call_op op; + op.type = GRPC_SEND_DEADLINE; + op.dir = GRPC_CALL_DOWN; + op.flags = 0; + op.data.deadline = absolute_deadline; + op.done_cb = do_nothing; + op.user_data = NULL; + grpc_call_execute_op(call, &op); + } + + return call; +} + +void grpc_channel_internal_ref(grpc_channel *channel) { + gpr_ref(&channel->refs); +} + +void grpc_channel_internal_unref(grpc_channel *channel) { + if (gpr_unref(&channel->refs)) { + grpc_channel_stack_destroy(CHANNEL_STACK_FROM_CHANNEL(channel)); + grpc_mdstr_unref(channel->grpc_status_string); + grpc_mdstr_unref(channel->grpc_message_string); + grpc_mdctx_orphan(channel->metadata_context); + gpr_free(channel); + } +} + +void grpc_channel_destroy(grpc_channel *channel) { + grpc_channel_op op; + grpc_channel_element *elem; + + op.type = GRPC_CHANNEL_SHUTDOWN; + op.dir = GRPC_CALL_DOWN; + elem = grpc_channel_stack_element(CHANNEL_STACK_FROM_CHANNEL(channel), 0); + elem->filter->channel_op(elem, &op); + + grpc_channel_internal_unref(channel); +} + +grpc_channel_stack *grpc_channel_get_channel_stack(grpc_channel *channel) { + return CHANNEL_STACK_FROM_CHANNEL(channel); +} + +grpc_mdctx *grpc_channel_get_metadata_context(grpc_channel *channel) { + return channel->metadata_context; +} + +grpc_mdstr *grpc_channel_get_status_string(grpc_channel *channel) { + return channel->grpc_status_string; +} + +grpc_mdstr *grpc_channel_get_message_string(grpc_channel *channel) { + return channel->grpc_message_string; +} diff --git a/src/core/surface/channel.h b/src/core/surface/channel.h new file mode 100644 index 0000000000..11d4939916 --- /dev/null +++ b/src/core/surface/channel.h @@ -0,0 +1,51 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_SURFACE_CHANNEL_H__ +#define __GRPC_INTERNAL_SURFACE_CHANNEL_H__ + +#include "src/core/channel/channel_stack.h" + +grpc_channel *grpc_channel_create_from_filters( + const grpc_channel_filter **filters, size_t count, + const grpc_channel_args *args, grpc_mdctx *mdctx, int is_client); + +grpc_channel_stack *grpc_channel_get_channel_stack(grpc_channel *channel); +grpc_mdctx *grpc_channel_get_metadata_context(grpc_channel *channel); +grpc_mdstr *grpc_channel_get_status_string(grpc_channel *channel); +grpc_mdstr *grpc_channel_get_message_string(grpc_channel *channel); + +void grpc_channel_internal_ref(grpc_channel *channel); +void grpc_channel_internal_unref(grpc_channel *channel); + +#endif /* __GRPC_INTERNAL_SURFACE_CHANNEL_H__ */ diff --git a/src/core/surface/channel_create.c b/src/core/surface/channel_create.c new file mode 100644 index 0000000000..ec1c8477fa --- /dev/null +++ b/src/core/surface/channel_create.c @@ -0,0 +1,213 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 "src/core/channel/census_filter.h" +#include "src/core/channel/channel_args.h" +#include "src/core/channel/client_channel.h" +#include "src/core/channel/client_setup.h" +#include "src/core/channel/connected_channel.h" +#include "src/core/channel/http_client_filter.h" +#include "src/core/channel/http_filter.h" +#include "src/core/endpoint/resolve_address.h" +#include "src/core/endpoint/tcp.h" +#include "src/core/endpoint/tcp_client.h" +#include "src/core/surface/channel.h" +#include "src/core/surface/client.h" +#include "src/core/surface/surface_em.h" +#include "src/core/transport/chttp2_transport.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string.h> +#include <grpc/support/sync.h> +#include <grpc/support/useful.h> + +typedef struct setup setup; + +/* A single setup request (started via initiate) */ +typedef struct { + grpc_client_setup_request *cs_request; + setup *setup; + /* Resolved addresses, or null if resolution not yet completed */ + grpc_resolved_addresses *resolved; + /* which address in resolved should we pick for the next connection attempt */ + size_t resolved_index; +} request; + +/* Global setup logic (may be running many simultaneous setup requests, but + with only one 'active' */ +struct setup { + const char *target; + grpc_transport_setup_callback setup_callback; + void *setup_user_data; + grpc_em *em; +}; + +static int maybe_try_next_resolved(request *r); + +static void done(request *r, int was_successful) { + grpc_client_setup_request_finish(r->cs_request, was_successful); + if (r->resolved) { + grpc_resolved_addresses_destroy(r->resolved); + } + gpr_free(r); +} + +/* connection callback: tcp is either valid, or null on error */ +static void on_connect(void *rp, grpc_endpoint *tcp) { + request *r = rp; + + if (!grpc_client_setup_request_should_continue(r->cs_request)) { + if (tcp) { + grpc_endpoint_shutdown(tcp); + grpc_endpoint_destroy(tcp); + } + done(r, 0); + return; + } + + if (!tcp) { + if (!maybe_try_next_resolved(r)) { + done(r, 0); + return; + } else { + return; + } + } else { + grpc_create_chttp2_transport( + r->setup->setup_callback, r->setup->setup_user_data, + grpc_client_setup_get_channel_args(r->cs_request), tcp, NULL, 0, + grpc_client_setup_get_mdctx(r->cs_request), 1); + done(r, 1); + return; + } +} + +/* attempt to connect to the next available resolved address */ +static int maybe_try_next_resolved(request *r) { + grpc_resolved_address *addr; + if (!r->resolved) return 0; + if (r->resolved_index == r->resolved->naddrs) return 0; + addr = &r->resolved->addrs[r->resolved_index++]; + grpc_tcp_client_connect(on_connect, r, r->setup->em, + (struct sockaddr *)&addr->addr, addr->len, + grpc_client_setup_request_deadline(r->cs_request)); + return 1; +} + +/* callback for when our target address has been resolved */ +static void on_resolved(void *rp, grpc_resolved_addresses *resolved) { + request *r = rp; + + /* if we're not still the active request, abort */ + if (!grpc_client_setup_request_should_continue(r->cs_request)) { + if (resolved) { + grpc_resolved_addresses_destroy(resolved); + } + done(r, 0); + return; + } + + if (!resolved) { + done(r, 0); + return; + } else { + r->resolved = resolved; + r->resolved_index = 0; + if (!maybe_try_next_resolved(r)) { + done(r, 0); + } + } +} + +static void initiate_setup(void *sp, grpc_client_setup_request *cs_request) { + request *r = gpr_malloc(sizeof(request)); + r->setup = sp; + r->cs_request = cs_request; + r->resolved = NULL; + r->resolved_index = 0; + /* TODO(klempner): Make grpc_resolve_address respect deadline */ + grpc_resolve_address(r->setup->target, "http", on_resolved, r); +} + +static void done_setup(void *sp) { + setup *s = sp; + gpr_free((void *)s->target); + gpr_free(s); +} + +static grpc_transport_setup_result complete_setup(void *channel_stack, + grpc_transport *transport, + grpc_mdctx *mdctx) { + static grpc_channel_filter const *extra_filters[] = {&grpc_http_client_filter, + &grpc_http_filter}; + return grpc_client_channel_transport_setup_complete( + channel_stack, transport, extra_filters, GPR_ARRAY_SIZE(extra_filters), + mdctx); +} + +/* Create a client channel: + Asynchronously: - resolve target + - connect to it (trying alternatives as presented) + - perform handshakes */ +grpc_channel *grpc_channel_create(const char *target, + const grpc_channel_args *args) { + setup *s = gpr_malloc(sizeof(setup)); + grpc_mdctx *mdctx = grpc_mdctx_create(); + grpc_channel *channel = NULL; +#define MAX_FILTERS 3 + const grpc_channel_filter *filters[MAX_FILTERS]; + int n = 0; + filters[n++] = &grpc_client_surface_filter; + if (grpc_channel_args_is_census_enabled(args)) { + filters[n++] = &grpc_client_census_filter; + } + filters[n++] = &grpc_client_channel_filter; + GPR_ASSERT(n <= MAX_FILTERS); + channel = grpc_channel_create_from_filters(filters, n, args, mdctx, 1); + + s->target = gpr_strdup(target); + s->em = grpc_surface_em(); + s->setup_callback = complete_setup; + s->setup_user_data = grpc_channel_get_channel_stack(channel); + + grpc_client_setup_create_and_attach(grpc_channel_get_channel_stack(channel), + args, mdctx, initiate_setup, done_setup, + s, s->em); + + return channel; +} diff --git a/src/core/surface/client.c b/src/core/surface/client.c new file mode 100644 index 0000000000..26abffa817 --- /dev/null +++ b/src/core/surface/client.c @@ -0,0 +1,115 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/surface/client.h" + +#include "src/core/surface/call.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string.h> + +typedef struct { void *unused; } call_data; + +typedef struct { void *unused; } channel_data; + +static void call_op(grpc_call_element *elem, grpc_call_op *op) { + GRPC_CALL_LOG_OP(GPR_INFO, elem, op); + + switch (op->type) { + case GRPC_SEND_DEADLINE: + grpc_call_set_deadline(elem, op->data.deadline); + grpc_call_next_op(elem, op); + break; + case GRPC_RECV_METADATA: + grpc_call_recv_metadata(elem, op); + break; + case GRPC_RECV_DEADLINE: + gpr_log(GPR_ERROR, "Deadline received by client (ignored)"); + break; + case GRPC_RECV_MESSAGE: + grpc_call_recv_message(elem, op->data.message, op->done_cb, + op->user_data); + break; + case GRPC_RECV_HALF_CLOSE: + grpc_call_recv_finish(elem, 0); + break; + case GRPC_RECV_FINISH: + grpc_call_recv_finish(elem, 1); + break; + case GRPC_RECV_END_OF_INITIAL_METADATA: + grpc_call_client_initial_metadata_complete(elem); + break; + default: + GPR_ASSERT(op->dir == GRPC_CALL_DOWN); + grpc_call_next_op(elem, op); + } +} + +static void channel_op(grpc_channel_element *elem, grpc_channel_op *op) { + switch (op->type) { + case GRPC_ACCEPT_CALL: + gpr_log(GPR_ERROR, "Client cannot accept new calls"); + break; + case GRPC_TRANSPORT_CLOSED: + gpr_log(GPR_ERROR, "Transport closed"); + break; + default: + GPR_ASSERT(op->dir == GRPC_CALL_DOWN); + grpc_channel_next_op(elem, op); + } +} + +static void init_call_elem(grpc_call_element *elem, + const void *transport_server_data) {} + +static void destroy_call_elem(grpc_call_element *elem) {} + +static void init_channel_elem(grpc_channel_element *elem, + const grpc_channel_args *args, grpc_mdctx *mdctx, + int is_first, int is_last) { + GPR_ASSERT(is_first); + GPR_ASSERT(!is_last); +} + +static void destroy_channel_elem(grpc_channel_element *elem) { +} + +const grpc_channel_filter grpc_client_surface_filter = { + call_op, channel_op, + + sizeof(call_data), init_call_elem, destroy_call_elem, + + sizeof(channel_data), init_channel_elem, destroy_channel_elem, + + "client", +}; diff --git a/src/core/surface/client.h b/src/core/surface/client.h new file mode 100644 index 0000000000..eb567276e2 --- /dev/null +++ b/src/core/surface/client.h @@ -0,0 +1,41 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_SURFACE_CLIENT_H__ +#define __GRPC_INTERNAL_SURFACE_CLIENT_H__ + +#include "src/core/channel/channel_stack.h" + +extern const grpc_channel_filter grpc_client_surface_filter; + +#endif /* __GRPC_INTERNAL_SURFACE_CLIENT_H__ */ diff --git a/src/core/surface/completion_queue.c b/src/core/surface/completion_queue.c new file mode 100644 index 0000000000..a7d611579f --- /dev/null +++ b/src/core/surface/completion_queue.c @@ -0,0 +1,392 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/surface/completion_queue.h" + +#include <stdio.h> +#include <string.h> + +#include "src/core/eventmanager/em.h" +#include "src/core/surface/call.h" +#include "src/core/surface/event_string.h" +#include "src/core/surface/surface_em.h" +#include "src/core/surface/surface_trace.h" +#include <grpc/support/alloc.h> +#include <grpc/support/atm.h> +#include <grpc/support/log.h> +#include <grpc/support/string.h> + +#define NUM_TAG_BUCKETS 31 + +/* A single event: extends grpc_event to form a linked list with a destruction + function (on_finish) that is hidden from outside this module */ +typedef struct event { + grpc_event base; + grpc_event_finish_func on_finish; + void *on_finish_user_data; + struct event *queue_next; + struct event *queue_prev; + struct event *bucket_next; + struct event *bucket_prev; +} event; + +/* Completion queue structure */ +struct grpc_completion_queue { + grpc_em *em; + int allow_polling; + + /* When refs drops to zero, we are in shutdown mode, and will be destroyable + once all queued events are drained */ + gpr_refcount refs; + /* 0 initially, 1 once we've begun shutting down */ + int shutdown; + /* Head of a linked list of queued events (prev points to the last element) */ + event *queue; + /* Fixed size chained hash table of events for pluck() */ + event *buckets[NUM_TAG_BUCKETS]; + +#ifndef NDEBUG + /* Debug support: track which operations are in flight at any given time */ + gpr_atm pending_op_count[GRPC_COMPLETION_DO_NOT_USE]; +#endif +}; + +/* Default do-nothing on_finish function */ +static void null_on_finish(void *user_data, grpc_op_error error) {} + +grpc_completion_queue *grpc_completion_queue_create() { + grpc_completion_queue *cc = gpr_malloc(sizeof(grpc_completion_queue)); + memset(cc, 0, sizeof(*cc)); + /* Initial ref is dropped by grpc_completion_queue_shutdown */ + gpr_ref_init(&cc->refs, 1); + cc->em = grpc_surface_em(); + cc->allow_polling = 1; + return cc; +} + +void grpc_completion_queue_dont_poll_test_only(grpc_completion_queue *cc) { + cc->allow_polling = 0; +} + +/* Create and append an event to the queue. Returns the event so that its data + members can be filled in. + Requires cc->em->mu locked. */ +static event *add_locked(grpc_completion_queue *cc, grpc_completion_type type, + void *tag, grpc_call *call, + grpc_event_finish_func on_finish, void *user_data) { + event *ev = gpr_malloc(sizeof(event)); + gpr_intptr bucket = ((gpr_intptr)tag) % NUM_TAG_BUCKETS; + GPR_ASSERT(!cc->shutdown); + ev->base.type = type; + ev->base.tag = tag; + ev->base.call = call; + ev->on_finish = on_finish ? on_finish : null_on_finish; + ev->on_finish_user_data = user_data; + if (cc->queue == NULL) { + cc->queue = ev->queue_next = ev->queue_prev = ev; + } else { + ev->queue_next = cc->queue; + ev->queue_prev = cc->queue->queue_prev; + ev->queue_next->queue_prev = ev->queue_prev->queue_next = ev; + } + if (cc->buckets[bucket] == NULL) { + cc->buckets[bucket] = ev->bucket_next = ev->bucket_prev = ev; + } else { + ev->bucket_next = cc->buckets[bucket]; + ev->bucket_prev = cc->buckets[bucket]->bucket_prev; + ev->bucket_next->bucket_prev = ev->bucket_prev->bucket_next = ev; + } + gpr_cv_broadcast(&cc->em->cv); + return ev; +} + +void grpc_cq_begin_op(grpc_completion_queue *cc, grpc_call *call, + grpc_completion_type type) { + gpr_ref(&cc->refs); + if (call) grpc_call_internal_ref(call); +#ifndef NDEBUG + gpr_atm_no_barrier_fetch_add(&cc->pending_op_count[type], 1); +#endif +} + +/* Signal the end of an operation - if this is the last waiting-to-be-queued + event, then enter shutdown mode */ +static void end_op_locked(grpc_completion_queue *cc, + grpc_completion_type type) { +#ifndef NDEBUG + GPR_ASSERT(gpr_atm_full_fetch_add(&cc->pending_op_count[type], -1) > 0); +#endif + if (gpr_unref(&cc->refs)) { + GPR_ASSERT(!cc->shutdown); + cc->shutdown = 1; + gpr_cv_broadcast(&cc->em->cv); + } +} + +void grpc_cq_end_read(grpc_completion_queue *cc, void *tag, grpc_call *call, + grpc_event_finish_func on_finish, void *user_data, + grpc_byte_buffer *read) { + event *ev; + gpr_mu_lock(&cc->em->mu); + ev = add_locked(cc, GRPC_READ, tag, call, on_finish, user_data); + ev->base.data.read = read; + end_op_locked(cc, GRPC_READ); + gpr_mu_unlock(&cc->em->mu); +} + +void grpc_cq_end_invoke_accepted(grpc_completion_queue *cc, void *tag, + grpc_call *call, + grpc_event_finish_func on_finish, + void *user_data, grpc_op_error error) { + event *ev; + gpr_mu_lock(&cc->em->mu); + ev = add_locked(cc, GRPC_INVOKE_ACCEPTED, tag, call, on_finish, user_data); + ev->base.data.invoke_accepted = error; + end_op_locked(cc, GRPC_INVOKE_ACCEPTED); + gpr_mu_unlock(&cc->em->mu); +} + +void grpc_cq_end_write_accepted(grpc_completion_queue *cc, void *tag, + grpc_call *call, + grpc_event_finish_func on_finish, + void *user_data, grpc_op_error error) { + event *ev; + gpr_mu_lock(&cc->em->mu); + ev = add_locked(cc, GRPC_WRITE_ACCEPTED, tag, call, on_finish, user_data); + ev->base.data.write_accepted = error; + end_op_locked(cc, GRPC_WRITE_ACCEPTED); + gpr_mu_unlock(&cc->em->mu); +} + +void grpc_cq_end_finish_accepted(grpc_completion_queue *cc, void *tag, + grpc_call *call, + grpc_event_finish_func on_finish, + void *user_data, grpc_op_error error) { + event *ev; + gpr_mu_lock(&cc->em->mu); + ev = add_locked(cc, GRPC_FINISH_ACCEPTED, tag, call, on_finish, user_data); + ev->base.data.finish_accepted = error; + end_op_locked(cc, GRPC_FINISH_ACCEPTED); + gpr_mu_unlock(&cc->em->mu); +} + +void grpc_cq_end_client_metadata_read(grpc_completion_queue *cc, void *tag, + grpc_call *call, + grpc_event_finish_func on_finish, + void *user_data, size_t count, + grpc_metadata *elements) { + event *ev; + gpr_mu_lock(&cc->em->mu); + ev = add_locked(cc, GRPC_CLIENT_METADATA_READ, tag, call, on_finish, + user_data); + ev->base.data.client_metadata_read.count = count; + ev->base.data.client_metadata_read.elements = elements; + end_op_locked(cc, GRPC_CLIENT_METADATA_READ); + gpr_mu_unlock(&cc->em->mu); +} + +void grpc_cq_end_finished(grpc_completion_queue *cc, void *tag, grpc_call *call, + grpc_event_finish_func on_finish, void *user_data, + grpc_status status) { + event *ev; + gpr_mu_lock(&cc->em->mu); + ev = add_locked(cc, GRPC_FINISHED, tag, call, on_finish, user_data); + ev->base.data.finished = status; + end_op_locked(cc, GRPC_FINISHED); + gpr_mu_unlock(&cc->em->mu); +} + +void grpc_cq_end_new_rpc(grpc_completion_queue *cc, void *tag, grpc_call *call, + grpc_event_finish_func on_finish, void *user_data, + const char *method, const char *host, + gpr_timespec deadline, size_t metadata_count, + grpc_metadata *metadata_elements) { + event *ev; + gpr_mu_lock(&cc->em->mu); + ev = add_locked(cc, GRPC_SERVER_RPC_NEW, tag, call, on_finish, user_data); + ev->base.data.server_rpc_new.method = method; + ev->base.data.server_rpc_new.host = host; + ev->base.data.server_rpc_new.deadline = deadline; + ev->base.data.server_rpc_new.metadata_count = metadata_count; + ev->base.data.server_rpc_new.metadata_elements = metadata_elements; + end_op_locked(cc, GRPC_SERVER_RPC_NEW); + gpr_mu_unlock(&cc->em->mu); +} + +/* Create a GRPC_QUEUE_SHUTDOWN event without queuing it anywhere */ +static event *create_shutdown_event() { + event *ev = gpr_malloc(sizeof(event)); + ev->base.type = GRPC_QUEUE_SHUTDOWN; + ev->base.call = NULL; + ev->base.tag = NULL; + ev->on_finish = null_on_finish; + return ev; +} + +grpc_event *grpc_completion_queue_next(grpc_completion_queue *cc, + gpr_timespec deadline) { + event *ev = NULL; + + gpr_mu_lock(&cc->em->mu); + for (;;) { + if (cc->queue != NULL) { + gpr_intptr bucket; + ev = cc->queue; + bucket = ((gpr_intptr)ev->base.tag) % NUM_TAG_BUCKETS; + cc->queue = ev->queue_next; + ev->queue_next->queue_prev = ev->queue_prev; + ev->queue_prev->queue_next = ev->queue_next; + ev->bucket_next->bucket_prev = ev->bucket_prev; + ev->bucket_prev->bucket_next = ev->bucket_next; + if (ev == cc->buckets[bucket]) { + cc->buckets[bucket] = ev->bucket_next; + if (ev == cc->buckets[bucket]) { + cc->buckets[bucket] = NULL; + } + } + if (cc->queue == ev) { + cc->queue = NULL; + } + break; + } + if (cc->shutdown) { + ev = create_shutdown_event(); + break; + } + if (cc->allow_polling && grpc_em_work(cc->em, deadline)) { + continue; + } + if (gpr_cv_wait(&cc->em->cv, &cc->em->mu, deadline)) { + gpr_mu_unlock(&cc->em->mu); + return NULL; + } + } + gpr_mu_unlock(&cc->em->mu); + GRPC_SURFACE_TRACE_RETURNED_EVENT(cc, &ev->base); + return &ev->base; +} + +static event *pluck_event(grpc_completion_queue *cc, void *tag) { + gpr_intptr bucket = ((gpr_intptr)tag) % NUM_TAG_BUCKETS; + event *ev = cc->buckets[bucket]; + if (ev == NULL) return NULL; + do { + if (ev->base.tag == tag) { + ev->queue_next->queue_prev = ev->queue_prev; + ev->queue_prev->queue_next = ev->queue_next; + ev->bucket_next->bucket_prev = ev->bucket_prev; + ev->bucket_prev->bucket_next = ev->bucket_next; + if (ev == cc->buckets[bucket]) { + cc->buckets[bucket] = ev->bucket_next; + if (ev == cc->buckets[bucket]) { + cc->buckets[bucket] = NULL; + } + } + if (cc->queue == ev) { + cc->queue = ev->queue_next; + if (cc->queue == ev) { + cc->queue = NULL; + } + } + return ev; + } + ev = ev->bucket_next; + } while (ev != cc->buckets[bucket]); + return NULL; +} + +grpc_event *grpc_completion_queue_pluck(grpc_completion_queue *cc, void *tag, + gpr_timespec deadline) { + event *ev = NULL; + + gpr_mu_lock(&cc->em->mu); + for (;;) { + if ((ev = pluck_event(cc, tag))) { + break; + } + if (cc->shutdown) { + ev = create_shutdown_event(); + break; + } + if (cc->allow_polling && grpc_em_work(cc->em, deadline)) { + continue; + } + if (gpr_cv_wait(&cc->em->cv, &cc->em->mu, deadline)) { + gpr_mu_unlock(&cc->em->mu); + return NULL; + } + } + gpr_mu_unlock(&cc->em->mu); + GRPC_SURFACE_TRACE_RETURNED_EVENT(cc, &ev->base); + return &ev->base; +} + +/* 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) { + if (gpr_unref(&cc->refs)) { + gpr_mu_lock(&cc->em->mu); + GPR_ASSERT(!cc->shutdown); + cc->shutdown = 1; + gpr_cv_broadcast(&cc->em->cv); + gpr_mu_unlock(&cc->em->mu); + } +} + +void grpc_completion_queue_destroy(grpc_completion_queue *cc) { + GPR_ASSERT(cc->queue == NULL); + gpr_free(cc); +} + +void grpc_event_finish(grpc_event *base) { + event *ev = (event *)base; + ev->on_finish(ev->on_finish_user_data, GRPC_OP_OK); + if (ev->base.call) { + grpc_call_internal_unref(ev->base.call); + } + gpr_free(ev); +} + +void grpc_cq_dump_pending_ops(grpc_completion_queue *cc) { +#ifndef NDEBUG + char tmp[256]; + char *p = tmp; + int i; + + for (i = 0; i < GRPC_COMPLETION_DO_NOT_USE; i++) { + p += sprintf(p, " %d", (int)cc->pending_op_count[i]); + } + + gpr_log(GPR_INFO, "pending ops:%s", tmp); +#endif +} diff --git a/src/core/surface/completion_queue.h b/src/core/surface/completion_queue.h new file mode 100644 index 0000000000..0fe576588a --- /dev/null +++ b/src/core/surface/completion_queue.h @@ -0,0 +1,102 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_SURFACE_COMPLETION_QUEUE_H__ +#define __GRPC_INTERNAL_SURFACE_COMPLETION_QUEUE_H__ + +/* Internal API for completion channels */ + +#include <grpc/grpc.h> + +/* A finish func is executed whenever the event consumer calls + grpc_event_finish */ +typedef void (*grpc_event_finish_func)(void *user_data, grpc_op_error error); + +/* Flag that an operation is beginning: the completion channel will not finish + shutdown until a corrensponding grpc_cq_end_* call is made */ +void grpc_cq_begin_op(grpc_completion_queue *cc, grpc_call *call, + grpc_completion_type type); + +/* grpc_cq_end_* functions pair with a grpc_cq_begin_op + + grpc_cq_end_* common arguments: + cc - the completion channel to queue on + tag - the user supplied operation tag + on_finish - grpc_event_finish_func that is called during grpc_event_finish + can be NULL to not get a callback + user_data - user_data parameter to be passed to on_finish + + Other parameters match the data member of grpc_event */ + +/* Queue a GRPC_READ operation */ +void grpc_cq_end_read(grpc_completion_queue *cc, void *tag, grpc_call *call, + grpc_event_finish_func on_finish, void *user_data, + grpc_byte_buffer *read); +/* Queue a GRPC_INVOKE_ACCEPTED operation */ +void grpc_cq_end_invoke_accepted(grpc_completion_queue *cc, void *tag, + grpc_call *call, + grpc_event_finish_func on_finish, + void *user_data, grpc_op_error error); +/* Queue a GRPC_WRITE_ACCEPTED operation */ +void grpc_cq_end_write_accepted(grpc_completion_queue *cc, void *tag, + grpc_call *call, + grpc_event_finish_func on_finish, + void *user_data, grpc_op_error error); +/* Queue a GRPC_FINISH_ACCEPTED operation */ +void grpc_cq_end_finish_accepted(grpc_completion_queue *cc, void *tag, + grpc_call *call, + grpc_event_finish_func on_finish, + void *user_data, grpc_op_error error); +/* Queue a GRPC_CLIENT_METADATA_READ operation */ +void grpc_cq_end_client_metadata_read(grpc_completion_queue *cc, void *tag, + grpc_call *call, + grpc_event_finish_func on_finish, + void *user_data, size_t count, + grpc_metadata *elements); + +void grpc_cq_end_finished(grpc_completion_queue *cc, void *tag, grpc_call *call, + grpc_event_finish_func on_finish, void *user_data, + grpc_status status); + +void grpc_cq_end_new_rpc(grpc_completion_queue *cc, void *tag, grpc_call *call, + grpc_event_finish_func on_finish, void *user_data, + const char *method, const char *host, + gpr_timespec deadline, size_t metadata_count, + grpc_metadata *metadata_elements); + +/* disable polling for some tests */ +void grpc_completion_queue_dont_poll_test_only(grpc_completion_queue *cc); + +void grpc_cq_dump_pending_ops(grpc_completion_queue *cc); + +#endif /* __GRPC_INTERNAL_SURFACE_COMPLETION_QUEUE_H__ */ diff --git a/src/core/surface/event_string.c b/src/core/surface/event_string.c new file mode 100644 index 0000000000..0a6a81d18e --- /dev/null +++ b/src/core/surface/event_string.c @@ -0,0 +1,119 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/surface/event_string.h" + +#include <stdio.h> + +#include <grpc/support/string.h> +#include <grpc/byte_buffer.h> + +static size_t addhdr(char *p, grpc_event *ev) { + return sprintf(p, "tag:%p call:%p", ev->tag, (void *)ev->call); +} + +static const char *errstr(grpc_op_error err) { + switch (err) { + case GRPC_OP_OK: + return "OK"; + case GRPC_OP_ERROR: + return "ERROR"; + } + return "UNKNOWN_UNKNOWN"; +} + +static size_t adderr(char *p, grpc_op_error err) { + return sprintf(p, " err=%s", errstr(err)); +} + +char *grpc_event_string(grpc_event *ev) { + char buffer[1024]; + char *p = buffer; + + if (ev == NULL) return gpr_strdup("null"); + + switch (ev->type) { + case GRPC_QUEUE_SHUTDOWN: + p += sprintf(p, "QUEUE_SHUTDOWN"); + break; + case GRPC_READ: + p += sprintf(p, "READ: "); + p += addhdr(p, ev); + if (ev->data.read) { + p += sprintf(p, " %d bytes", + (int)grpc_byte_buffer_length(ev->data.read)); + } else { + p += sprintf(p, " end-of-stream"); + } + break; + case GRPC_INVOKE_ACCEPTED: + p += sprintf(p, "INVOKE_ACCEPTED: "); + p += addhdr(p, ev); + p += adderr(p, ev->data.invoke_accepted); + break; + case GRPC_WRITE_ACCEPTED: + p += sprintf(p, "WRITE_ACCEPTED: "); + p += addhdr(p, ev); + p += adderr(p, ev->data.write_accepted); + break; + case GRPC_FINISH_ACCEPTED: + p += sprintf(p, "FINISH_ACCEPTED: "); + p += addhdr(p, ev); + p += adderr(p, ev->data.write_accepted); + break; + case GRPC_CLIENT_METADATA_READ: + p += sprintf(p, "CLIENT_METADATA_READ: "); + p += addhdr(p, ev); + p += sprintf(p, " %d elements", (int)ev->data.client_metadata_read.count); + break; + case GRPC_FINISHED: + p += sprintf(p, "FINISHED: "); + p += addhdr(p, ev); + p += sprintf(p, " status_code=%d details='%s'", ev->data.finished.code, + ev->data.finished.details); + break; + case GRPC_SERVER_RPC_NEW: + p += sprintf(p, "SERVER_RPC_NEW: "); + p += addhdr(p, ev); + p += sprintf(p, " method='%s' host='%s' %d metadata elements", + ev->data.server_rpc_new.method, ev->data.server_rpc_new.host, + (int)ev->data.server_rpc_new.metadata_count); + break; + case GRPC_COMPLETION_DO_NOT_USE: + p += sprintf(p, "DO_NOT_USE (this is a bug)"); + p += addhdr(p, ev); + break; + } + + return gpr_strdup(buffer); +} diff --git a/src/core/surface/event_string.h b/src/core/surface/event_string.h new file mode 100644 index 0000000000..30b693e95c --- /dev/null +++ b/src/core/surface/event_string.h @@ -0,0 +1,42 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_SURFACE_EVENT_STRING_H__ +#define __GRPC_INTERNAL_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_INTERNAL_SURFACE_EVENT_STRING_H__ */ diff --git a/src/core/surface/init.c b/src/core/surface/init.c new file mode 100644 index 0000000000..92c0ac880d --- /dev/null +++ b/src/core/surface/init.c @@ -0,0 +1,46 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 "src/core/statistics/census_interface.h" +#include "src/core/surface/surface_em.h" + +void grpc_init() { + grpc_surface_em_init(); + census_init(); +} + +void grpc_shutdown() { + grpc_surface_em_shutdown(); + census_shutdown(); +} diff --git a/src/core/surface/lame_client.c b/src/core/surface/lame_client.c new file mode 100644 index 0000000000..18921c44dd --- /dev/null +++ b/src/core/surface/lame_client.c @@ -0,0 +1,94 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/surface/lame_client.h" + +#include "src/core/channel/channel_stack.h" +#include "src/core/surface/channel.h" +#include "src/core/surface/call.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string.h> + +typedef struct { void *unused; } call_data; + +typedef struct { void *unused; } channel_data; + +static void call_op(grpc_call_element *elem, grpc_call_op *op) { + GRPC_CALL_LOG_OP(GPR_INFO, elem, op); + + switch (op->type) { + case GRPC_SEND_START: + grpc_call_recv_finish(elem, 1); + break; + case GRPC_SEND_METADATA: + grpc_mdelem_unref(op->data.metadata); + break; + default: + break; + } + + op->done_cb(op->user_data, GRPC_OP_ERROR); +} + +static void channel_op(grpc_channel_element *elem, grpc_channel_op *op) {} + +static void init_call_elem(grpc_call_element *elem, + const void *transport_server_data) {} + +static void destroy_call_elem(grpc_call_element *elem) {} + +static void init_channel_elem(grpc_channel_element *elem, + const grpc_channel_args *args, grpc_mdctx *mdctx, + int is_first, int is_last) { + GPR_ASSERT(is_first); + GPR_ASSERT(is_last); +} + +static void destroy_channel_elem(grpc_channel_element *elem) {} + +static const grpc_channel_filter lame_filter = { + call_op, channel_op, + + sizeof(call_data), init_call_elem, destroy_call_elem, + + sizeof(channel_data), init_channel_elem, destroy_channel_elem, + + "lame-client", +}; + +grpc_channel *grpc_lame_client_channel_create() { + static const grpc_channel_filter *filters[] = {&lame_filter}; + return grpc_channel_create_from_filters(filters, 1, NULL, grpc_mdctx_create(), + 1); +} diff --git a/src/core/surface/lame_client.h b/src/core/surface/lame_client.h new file mode 100644 index 0000000000..74b9707202 --- /dev/null +++ b/src/core/surface/lame_client.h @@ -0,0 +1,42 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_SURFACE_LAME_CLIENT_H_ +#define __GRPC_INTERNAL_SURFACE_LAME_CLIENT_H_ + +#include <grpc/grpc.h> + +/* Create a lame client: this client fails every operation attempted on it. */ +grpc_channel *grpc_lame_client_channel_create(); + +#endif /* __GRPC_INTERNAL_SURFACE_LAME_CLIENT_H_ */ diff --git a/src/core/surface/secure_channel_create.c b/src/core/surface/secure_channel_create.c new file mode 100644 index 0000000000..f330b83521 --- /dev/null +++ b/src/core/surface/secure_channel_create.c @@ -0,0 +1,243 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 "src/core/channel/census_filter.h" +#include "src/core/channel/channel_args.h" +#include "src/core/channel/client_channel.h" +#include "src/core/channel/client_setup.h" +#include "src/core/channel/connected_channel.h" +#include "src/core/channel/http_client_filter.h" +#include "src/core/channel/http_filter.h" +#include "src/core/endpoint/resolve_address.h" +#include "src/core/endpoint/tcp.h" +#include "src/core/endpoint/tcp_client.h" +#include "src/core/security/auth.h" +#include "src/core/security/security_context.h" +#include "src/core/security/secure_transport_setup.h" +#include "src/core/surface/channel.h" +#include "src/core/surface/client.h" +#include "src/core/surface/surface_em.h" +#include "src/core/transport/chttp2_transport.h" +#include <grpc/grpc_security.h> +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string.h> +#include <grpc/support/sync.h> +#include <grpc/support/useful.h> +#include "src/core/tsi/transport_security_interface.h" + +typedef struct setup setup; + +/* A single setup request (started via initiate) */ +typedef struct { + grpc_client_setup_request *cs_request; + setup *setup; + /* Resolved addresses, or null if resolution not yet completed. */ + grpc_resolved_addresses *resolved; + /* which address in resolved should we pick for the next connection attempt */ + size_t resolved_index; +} request; + +struct setup { + grpc_channel_security_context *security_context; + const char *target; + grpc_transport_setup_callback setup_callback; + void *setup_user_data; + grpc_em *em; +}; + +static int maybe_try_next_resolved(request *r); + +static void done(request *r, int was_successful) { + grpc_client_setup_request_finish(r->cs_request, was_successful); + if (r->resolved) { + grpc_resolved_addresses_destroy(r->resolved); + } + gpr_free(r); +} + +static void on_secure_transport_setup_done(void *rp, + grpc_security_status status, + grpc_endpoint *secure_endpoint) { + request *r = rp; + if (status != GRPC_SECURITY_OK) { + gpr_log(GPR_ERROR, "Secure transport setup failed with error %d.", status); + done(r, 0); + } else { + grpc_create_chttp2_transport( + r->setup->setup_callback, r->setup->setup_user_data, + grpc_client_setup_get_channel_args(r->cs_request), secure_endpoint, + NULL, 0, grpc_client_setup_get_mdctx(r->cs_request), 1); + done(r, 1); + } +} + +/* connection callback: tcp is either valid, or null on error */ +static void on_connect(void *rp, grpc_endpoint *tcp) { + request *r = rp; + + if (!grpc_client_setup_request_should_continue(r->cs_request)) { + if (tcp) { + grpc_endpoint_shutdown(tcp); + grpc_endpoint_destroy(tcp); + } + done(r, 0); + return; + } + + if (!tcp) { + if (!maybe_try_next_resolved(r)) { + done(r, 0); + return; + } else { + return; + } + } else { + grpc_setup_secure_transport(&r->setup->security_context->base, tcp, + on_secure_transport_setup_done, r); + } +} + +/* attempt to connect to the next available resolved address */ +static int maybe_try_next_resolved(request *r) { + grpc_resolved_address *addr; + if (!r->resolved) return 0; + if (r->resolved_index == r->resolved->naddrs) return 0; + addr = &r->resolved->addrs[r->resolved_index++]; + grpc_tcp_client_connect(on_connect, r, r->setup->em, + (struct sockaddr *)&addr->addr, addr->len, + grpc_client_setup_request_deadline(r->cs_request)); + return 1; +} + +/* callback for when our target address has been resolved */ +static void on_resolved(void *rp, grpc_resolved_addresses *resolved) { + request *r = rp; + + /* if we're not still the active request, abort */ + if (!grpc_client_setup_request_should_continue(r->cs_request)) { + if (resolved) { + grpc_resolved_addresses_destroy(resolved); + } + done(r, 0); + return; + } + + if (!resolved) { + done(r, 0); + return; + } else { + r->resolved = resolved; + r->resolved_index = 0; + if (!maybe_try_next_resolved(r)) { + done(r, 0); + } + } +} + +static void initiate_setup(void *sp, grpc_client_setup_request *cs_request) { + request *r = gpr_malloc(sizeof(request)); + r->setup = sp; + r->cs_request = cs_request; + r->resolved = NULL; + r->resolved_index = 0; + /* TODO(klempner): Make grpc_resolve_address respect deadline */ + grpc_resolve_address(r->setup->target, "https", on_resolved, r); +} + +static void done_setup(void *sp) { + setup *s = sp; + gpr_free((void *)s->target); + grpc_security_context_unref(&s->security_context->base); + gpr_free(s); +} + +static grpc_transport_setup_result complete_setup(void *channel_stack, + grpc_transport *transport, + grpc_mdctx *mdctx) { + static grpc_channel_filter const *extra_filters[] = {&grpc_http_client_filter, + &grpc_http_filter}; + return grpc_client_channel_transport_setup_complete( + channel_stack, transport, extra_filters, GPR_ARRAY_SIZE(extra_filters), + mdctx); +} + +/* Create a secure client channel: + Asynchronously: - resolve target + - connect to it (trying alternatives as presented) + - perform handshakes */ +grpc_channel *grpc_secure_channel_create_internal( + const char *target, const grpc_channel_args *args, + grpc_channel_security_context *context) { + setup *s; + grpc_channel *channel; + grpc_arg context_arg; + grpc_channel_args *args_copy; + grpc_mdctx *mdctx = grpc_mdctx_create(); +#define MAX_FILTERS 4 + const grpc_channel_filter *filters[MAX_FILTERS]; + int n = 0; + if (grpc_find_security_context_in_args(args) != NULL) { + gpr_log(GPR_ERROR, "Cannot set security context in channel args."); + } + + s = gpr_malloc(sizeof(setup)); + context_arg = grpc_security_context_to_arg(&context->base); + args_copy = grpc_channel_args_copy_and_add(args, &context_arg); + filters[n++] = &grpc_client_surface_filter; + if (grpc_channel_args_is_census_enabled(args)) { + filters[n++] = &grpc_client_census_filter; + } + filters[n++] = &grpc_client_auth_filter; + filters[n++] = &grpc_client_channel_filter; + GPR_ASSERT(n <= MAX_FILTERS); + channel = grpc_channel_create_from_filters(filters, n, args_copy, mdctx, 1); + grpc_channel_args_destroy(args_copy); + + s->target = gpr_strdup(target); + s->em = grpc_surface_em(); + s->setup_callback = complete_setup; + s->setup_user_data = grpc_channel_get_channel_stack(channel); + s->security_context = + (grpc_channel_security_context *)grpc_security_context_ref( + &context->base); + grpc_client_setup_create_and_attach(grpc_channel_get_channel_stack(channel), + args, mdctx, initiate_setup, done_setup, + s, s->em); + return channel; +} diff --git a/src/core/surface/secure_server_create.c b/src/core/surface/secure_server_create.c new file mode 100644 index 0000000000..bf0f62367f --- /dev/null +++ b/src/core/surface/secure_server_create.c @@ -0,0 +1,57 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 "src/core/channel/channel_args.h" +#include "src/core/security/security_context.h" +#include "src/core/surface/completion_queue.h" +#include "src/core/surface/server.h" +#include <grpc/support/log.h> + +grpc_server *grpc_secure_server_create_internal( + grpc_completion_queue *cq, const grpc_channel_args *args, + grpc_security_context *context) { + grpc_arg context_arg; + grpc_channel_args *args_copy; + grpc_server *server; + if (grpc_find_security_context_in_args(args) != NULL) { + gpr_log(GPR_ERROR, "Cannot set security context in channel args."); + } + + context_arg = grpc_security_context_to_arg(context); + args_copy = grpc_channel_args_copy_and_add(args, &context_arg); + server = grpc_server_create_from_filters(cq, NULL, 0, args_copy); + grpc_channel_args_destroy(args_copy); + return server; +} diff --git a/src/core/surface/server.c b/src/core/surface/server.c new file mode 100644 index 0000000000..99d66ffb2d --- /dev/null +++ b/src/core/surface/server.c @@ -0,0 +1,609 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/surface/server.h" + +#include <stdlib.h> +#include <string.h> + +#include "src/core/channel/census_filter.h" +#include "src/core/channel/channel_args.h" +#include "src/core/channel/connected_channel.h" +#include "src/core/surface/call.h" +#include "src/core/surface/channel.h" +#include "src/core/surface/completion_queue.h" +#include "src/core/surface/surface_em.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string.h> +#include <grpc/support/useful.h> + +typedef enum { PENDING_START, ALL_CALLS, CALL_LIST_COUNT } call_list; + +typedef struct listener { + void *arg; + void (*start)(grpc_server *server, void *arg); + void (*destroy)(grpc_server *server, void *arg); + struct listener *next; +} listener; + +typedef struct call_data call_data; +typedef struct channel_data channel_data; + +struct channel_data { + grpc_server *server; + grpc_channel *channel; + /* linked list of all channels on a server */ + channel_data *next; + channel_data *prev; +}; + +struct grpc_server { + size_t channel_filter_count; + const grpc_channel_filter **channel_filters; + grpc_channel_args *channel_args; + grpc_completion_queue *cq; + grpc_em *em; + + gpr_mu mu; + + void **tags; + size_t ntags; + size_t tag_cap; + + gpr_uint8 shutdown; + + call_data *lists[CALL_LIST_COUNT]; + channel_data root_channel_data; + + listener *listeners; + gpr_refcount internal_refcount; +}; + +typedef struct { + call_data *next; + call_data *prev; +} call_link; + +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; + +struct call_data { + grpc_call *call; + + call_state state; + gpr_timespec deadline; + + gpr_uint8 included[CALL_LIST_COUNT]; + call_link links[CALL_LIST_COUNT]; +}; + +#define SERVER_FROM_CALL_ELEM(elem) \ + (((channel_data *)(elem)->channel_data)->server) + +static void do_nothing(void *unused, grpc_op_error ignored) {} + +static int call_list_join(grpc_server *server, call_data *call, + call_list list) { + if (call->included[list]) return 0; + call->included[list] = 1; + if (!server->lists[list]) { + server->lists[list] = call; + call->links[list].next = call->links[list].prev = call; + } else { + call->links[list].next = server->lists[list]; + call->links[list].prev = server->lists[list]->links[list].prev; + call->links[list].next->links[list].prev = + call->links[list].prev->links[list].next = call; + } + return 1; +} + +static call_data *call_list_remove_head(grpc_server *server, call_list list) { + call_data *out = server->lists[list]; + if (out) { + out->included[list] = 0; + if (out->links[list].next == out) { + server->lists[list] = NULL; + } else { + server->lists[list] = out->links[list].next; + out->links[list].next->links[list].prev = out->links[list].prev; + out->links[list].prev->links[list].next = out->links[list].next; + } + } + return out; +} + +static int call_list_remove(grpc_server *server, call_data *call, + call_list list) { + if (!call->included[list]) return 0; + call->included[list] = 0; + if (server->lists[list] == call) { + server->lists[list] = call->links[list].next; + if (server->lists[list] == call) { + server->lists[list] = NULL; + return 1; + } + } + GPR_ASSERT(server->lists[list] != call); + call->links[list].next->links[list].prev = call->links[list].prev; + call->links[list].prev->links[list].next = call->links[list].next; + return 1; +} + +static void server_ref(grpc_server *server) { + gpr_ref(&server->internal_refcount); +} + +static void server_unref(grpc_server *server) { + if (gpr_unref(&server->internal_refcount)) { + grpc_channel_args_destroy(server->channel_args); + gpr_mu_destroy(&server->mu); + gpr_free(server->channel_filters); + gpr_free(server->tags); + gpr_free(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(void *cd, grpc_em_cb_status status) { + channel_data *chand = cd; + grpc_server *server = chand->server; + /*gpr_log(GPR_INFO, "destroy channel %p", chand->channel);*/ + grpc_channel_destroy(chand->channel); + server_unref(server); +} + +static void destroy_channel(channel_data *chand) { + if (is_channel_orphaned(chand)) return; + GPR_ASSERT(chand->server != NULL); + orphan_channel(chand); + server_ref(chand->server); + grpc_em_add_callback(chand->server->em, finish_destroy_channel, chand); +} + +static void queue_new_rpc(grpc_server *server, call_data *calld, void *tag) { + grpc_call *call = calld->call; + grpc_metadata_buffer *mdbuf = grpc_call_get_metadata_buffer(call); + size_t count = grpc_metadata_buffer_count(mdbuf); + grpc_metadata *elements = grpc_metadata_buffer_extract_elements(mdbuf); + const char *host = NULL; + const char *method = NULL; + size_t i; + grpc_metadata status_md; + + for (i = 0; i < count; i++) { + if (0 == strcmp(elements[i].key, ":authority")) { + host = elements[i].value; + } else if (0 == strcmp(elements[i].key, ":path")) { + method = elements[i].value; + } + } + + status_md.key = ":status"; + status_md.value = "200"; + status_md.value_length = 3; + grpc_call_add_metadata(call, &status_md, GRPC_WRITE_BUFFER_HINT); + + grpc_call_internal_ref(call); + grpc_cq_end_new_rpc(server->cq, tag, call, + grpc_metadata_buffer_cleanup_elements, elements, method, + host, calld->deadline, count, elements); +} + +static void start_new_rpc(grpc_call_element *elem) { + channel_data *chand = elem->channel_data; + call_data *calld = elem->call_data; + grpc_server *server = chand->server; + + gpr_mu_lock(&server->mu); + if (server->ntags) { + calld->state = ACTIVATED; + queue_new_rpc(server, calld, server->tags[--server->ntags]); + } else { + calld->state = PENDING; + call_list_join(server, calld, PENDING_START); + } + gpr_mu_unlock(&server->mu); +} + +static void kill_zombie(void *elem, grpc_em_cb_status status) { + grpc_call_destroy(grpc_call_from_top_element(elem)); +} + +static void finish_rpc(grpc_call_element *elem, int is_full_close) { + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + gpr_mu_lock(&chand->server->mu); + switch (calld->state) { + case ACTIVATED: + grpc_call_recv_finish(elem, is_full_close); + break; + case PENDING: + if (!is_full_close) { + grpc_call_recv_finish(elem, is_full_close); + break; + } + call_list_remove(chand->server, calld, PENDING_START); + /* fallthrough intended */ + case NOT_STARTED: + calld->state = ZOMBIED; + grpc_em_add_callback(chand->server->em, kill_zombie, elem); + break; + case ZOMBIED: + break; + } + gpr_mu_unlock(&chand->server->mu); +} + +static void call_op(grpc_call_element *elem, grpc_call_op *op) { + GRPC_CALL_LOG_OP(GPR_INFO, elem, op); + switch (op->type) { + case GRPC_RECV_METADATA: + grpc_call_recv_metadata(elem, op); + break; + case GRPC_RECV_END_OF_INITIAL_METADATA: + start_new_rpc(elem); + break; + case GRPC_RECV_MESSAGE: + grpc_call_recv_message(elem, op->data.message, op->done_cb, + op->user_data); + break; + case GRPC_RECV_HALF_CLOSE: + finish_rpc(elem, 0); + break; + case GRPC_RECV_FINISH: + finish_rpc(elem, 1); + break; + case GRPC_RECV_DEADLINE: + grpc_call_set_deadline(elem, op->data.deadline); + ((call_data *)elem->call_data)->deadline = op->data.deadline; + break; + default: + GPR_ASSERT(op->dir == GRPC_CALL_DOWN); + grpc_call_next_op(elem, op); + break; + } +} + +static void channel_op(grpc_channel_element *elem, grpc_channel_op *op) { + channel_data *chand = elem->channel_data; + + switch (op->type) { + case GRPC_ACCEPT_CALL: + /* create a call */ + grpc_call_create(chand->channel, + op->data.accept_call.transport_server_data); + break; + case GRPC_TRANSPORT_CLOSED: + /* if the transport is closed for a server channel, we destroy the + channel */ + gpr_mu_lock(&chand->server->mu); + server_ref(chand->server); + destroy_channel(chand); + gpr_mu_unlock(&chand->server->mu); + server_unref(chand->server); + break; + default: + GPR_ASSERT(op->dir == GRPC_CALL_DOWN); + grpc_channel_next_op(elem, op); + break; + } +} + +static void finish_shutdown_channel(void *cd, grpc_em_cb_status status) { + channel_data *chand = cd; + grpc_channel_op op; + op.type = GRPC_CHANNEL_SHUTDOWN; + op.dir = GRPC_CALL_DOWN; + channel_op(grpc_channel_stack_element( + grpc_channel_get_channel_stack(chand->channel), 0), + &op); + grpc_channel_internal_unref(chand->channel); +} + +static void shutdown_channel(channel_data *chand) { + grpc_channel_internal_ref(chand->channel); + grpc_em_add_callback(chand->server->em, finish_shutdown_channel, chand); +} + +static void init_call_elem(grpc_call_element *elem, + const void *server_transport_data) { + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + memset(calld, 0, sizeof(call_data)); + calld->deadline = gpr_inf_future; + calld->call = grpc_call_from_top_element(elem); + + gpr_mu_lock(&chand->server->mu); + call_list_join(chand->server, calld, ALL_CALLS); + gpr_mu_unlock(&chand->server->mu); + + server_ref(chand->server); +} + +static void destroy_call_elem(grpc_call_element *elem) { + channel_data *chand = elem->channel_data; + int i; + + gpr_mu_lock(&chand->server->mu); + for (i = 0; i < CALL_LIST_COUNT; i++) { + call_list_remove(chand->server, elem->call_data, i); + } + gpr_mu_unlock(&chand->server->mu); + + server_unref(chand->server); +} + +static void init_channel_elem(grpc_channel_element *elem, + const grpc_channel_args *args, + grpc_mdctx *metadata_context, int is_first, + int is_last) { + channel_data *chand = elem->channel_data; + GPR_ASSERT(is_first); + GPR_ASSERT(!is_last); + chand->server = NULL; + chand->channel = NULL; + chand->next = chand->prev = chand; +} + +static void destroy_channel_elem(grpc_channel_element *elem) { + channel_data *chand = elem->channel_data; + if (chand->server) { + gpr_mu_lock(&chand->server->mu); + chand->next->prev = chand->prev; + chand->prev->next = chand->next; + chand->next = chand->prev = chand; + gpr_mu_unlock(&chand->server->mu); + server_unref(chand->server); + } +} + +static const grpc_channel_filter server_surface_filter = { + call_op, channel_op, + + sizeof(call_data), init_call_elem, destroy_call_elem, + + sizeof(channel_data), init_channel_elem, destroy_channel_elem, + + "server", +}; + +static void early_terminate_requested_calls(grpc_completion_queue *cq, + void **tags, size_t ntags) { + size_t i; + + for (i = 0; i < ntags; i++) { + grpc_cq_end_new_rpc(cq, tags[i], NULL, do_nothing, NULL, NULL, NULL, + gpr_inf_past, 0, NULL); + } +} + +grpc_server *grpc_server_create_from_filters(grpc_completion_queue *cq, + grpc_channel_filter **filters, + size_t filter_count, + const grpc_channel_args *args) { + size_t i; + int census_enabled = grpc_channel_args_is_census_enabled(args); + + grpc_server *server = gpr_malloc(sizeof(grpc_server)); + memset(server, 0, sizeof(grpc_server)); + + gpr_mu_init(&server->mu); + + server->cq = cq; + server->em = grpc_surface_em(); + /* 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; + + /* Server filter stack is: + + server_surface_filter - for making surface API calls + grpc_server_census_filter (optional) - for stats collection and tracing + {passed in filter stack} + grpc_connected_channel_filter - for interfacing with transports */ + server->channel_filter_count = filter_count + 1 + census_enabled; + server->channel_filters = + gpr_malloc(server->channel_filter_count * sizeof(grpc_channel_filter *)); + server->channel_filters[0] = &server_surface_filter; + if (census_enabled) { + server->channel_filters[1] = &grpc_server_census_filter; + } + for (i = 0; i < filter_count; i++) { + server->channel_filters[i + 1 + census_enabled] = filters[i]; + } + + server->channel_args = grpc_channel_args_copy(args); + + return server; +} + +void grpc_server_start(grpc_server *server) { + listener *l; + + for (l = server->listeners; l; l = l->next) { + l->start(server, l->arg); + } +} + +grpc_transport_setup_result grpc_server_setup_transport( + grpc_server *s, grpc_transport *transport, + grpc_channel_filter const **extra_filters, size_t num_extra_filters, + grpc_mdctx *mdctx) { + size_t num_filters = s->channel_filter_count + num_extra_filters + 1; + grpc_channel_filter const **filters = + gpr_malloc(sizeof(grpc_channel_filter *) * num_filters); + size_t i; + grpc_channel *channel; + channel_data *chand; + + for (i = 0; i < s->channel_filter_count; i++) { + filters[i] = s->channel_filters[i]; + } + for (; i < s->channel_filter_count + num_extra_filters; i++) { + filters[i] = extra_filters[i - s->channel_filter_count]; + } + filters[i] = &grpc_connected_channel_filter; + + channel = grpc_channel_create_from_filters(filters, num_filters, + s->channel_args, mdctx, 0); + 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; + + gpr_mu_lock(&s->mu); + chand->next = &s->root_channel_data; + chand->prev = chand->next->prev; + chand->next->prev = chand->prev->next = chand; + gpr_mu_unlock(&s->mu); + + gpr_free(filters); + + return grpc_connected_channel_bind_transport( + grpc_channel_get_channel_stack(channel), transport); +} + +void grpc_server_shutdown(grpc_server *server) { + /* TODO(ctiller): send goaway, etc */ + listener *l; + void **tags; + size_t ntags; + + /* lock, and gather up some stuff to do */ + gpr_mu_lock(&server->mu); + if (server->shutdown) { + gpr_mu_unlock(&server->mu); + return; + } + + tags = server->tags; + ntags = server->ntags; + server->tags = NULL; + server->ntags = 0; + + server->shutdown = 1; + gpr_mu_unlock(&server->mu); + + /* terminate all the requested calls */ + early_terminate_requested_calls(server->cq, tags, ntags); + gpr_free(tags); + + /* Shutdown listeners */ + for (l = server->listeners; l; l = l->next) { + l->destroy(server, l->arg); + } + while (server->listeners) { + l = server->listeners; + server->listeners = l->next; + gpr_free(l); + } +} + +void grpc_server_destroy(grpc_server *server) { + channel_data *c; + gpr_mu_lock(&server->mu); + for (c = server->root_channel_data.next; c != &server->root_channel_data; + c = c->next) { + shutdown_channel(c); + } + gpr_mu_unlock(&server->mu); + + server_unref(server); +} + +void grpc_server_add_listener(grpc_server *server, void *arg, + void (*start)(grpc_server *server, void *arg), + void (*destroy)(grpc_server *server, void *arg)) { + listener *l = gpr_malloc(sizeof(listener)); + l->arg = arg; + l->start = start; + l->destroy = destroy; + l->next = server->listeners; + server->listeners = l; +} + +grpc_call_error grpc_server_request_call(grpc_server *server, void *tag_new) { + call_data *calld; + + grpc_cq_begin_op(server->cq, NULL, GRPC_SERVER_RPC_NEW); + + gpr_mu_lock(&server->mu); + + if (server->shutdown) { + gpr_mu_unlock(&server->mu); + early_terminate_requested_calls(server->cq, &tag_new, 1); + return GRPC_CALL_OK; + } + + calld = call_list_remove_head(server, PENDING_START); + if (calld) { + GPR_ASSERT(calld->state == PENDING); + calld->state = ACTIVATED; + queue_new_rpc(server, calld, tag_new); + } else { + if (server->tag_cap == server->ntags) { + server->tag_cap = GPR_MAX(3 * server->tag_cap / 2, server->tag_cap + 1); + server->tags = + gpr_realloc(server->tags, sizeof(void *) * server->tag_cap); + } + server->tags[server->ntags++] = tag_new; + } + gpr_mu_unlock(&server->mu); + + return GRPC_CALL_OK; +} + +const grpc_channel_args *grpc_server_get_channel_args(grpc_server *server) { + return server->channel_args; +} diff --git a/src/core/surface/server.h b/src/core/surface/server.h new file mode 100644 index 0000000000..f0773ab9d5 --- /dev/null +++ b/src/core/surface/server.h @@ -0,0 +1,62 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_SURFACE_SERVER_H__ +#define __GRPC_INTERNAL_SURFACE_SERVER_H__ + +#include "src/core/channel/channel_stack.h" +#include <grpc/grpc.h> +#include "src/core/transport/transport.h" + +/* Create a server */ +grpc_server *grpc_server_create_from_filters(grpc_completion_queue *cq, + grpc_channel_filter **filters, + size_t filter_count, + const grpc_channel_args *args); + +/* 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_server *server, void *listener, + void (*start)(grpc_server *server, void *arg), + void (*destroy)(grpc_server *server, void *arg)); + +/* Setup a transport - creates a channel stack, binds the transport to the + server */ +grpc_transport_setup_result grpc_server_setup_transport( + grpc_server *server, grpc_transport *transport, + grpc_channel_filter const **extra_filters, size_t num_extra_filters, + grpc_mdctx *mdctx); + +const grpc_channel_args *grpc_server_get_channel_args(grpc_server *server); + +#endif /* __GRPC_INTERNAL_SURFACE_SERVER_H__ */ diff --git a/src/core/surface/server_chttp2.c b/src/core/surface/server_chttp2.c new file mode 100644 index 0000000000..24c5757166 --- /dev/null +++ b/src/core/surface/server_chttp2.c @@ -0,0 +1,123 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 "src/core/channel/http_filter.h" +#include "src/core/channel/http_server_filter.h" +#include "src/core/endpoint/resolve_address.h" +#include "src/core/endpoint/tcp_server.h" +#include "src/core/surface/server.h" +#include "src/core/surface/surface_em.h" +#include "src/core/transport/chttp2_transport.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/useful.h> + +static grpc_transport_setup_result setup_transport(void *server, + grpc_transport *transport, + grpc_mdctx *mdctx) { + static grpc_channel_filter const *extra_filters[] = {&grpc_http_server_filter, + &grpc_http_filter}; + return grpc_server_setup_transport(server, transport, extra_filters, + GPR_ARRAY_SIZE(extra_filters), mdctx); +} + +static void new_transport(void *server, grpc_endpoint *tcp) { + grpc_create_chttp2_transport(setup_transport, server, + grpc_server_get_channel_args(server), tcp, NULL, + 0, grpc_mdctx_create(), 0); +} + +/* Server callback: start listening on our ports */ +static void start(grpc_server *server, void *tcpp) { + grpc_tcp_server *tcp = tcpp; + grpc_tcp_server_start(tcp, new_transport, server); +} + +/* Server callback: destroy the tcp listener (so we don't generate further + callbacks) */ +static void destroy(grpc_server *server, void *tcpp) { + grpc_tcp_server *tcp = tcpp; + grpc_tcp_server_destroy(tcp); +} + +int grpc_server_add_http2_port(grpc_server *server, const char *addr) { + grpc_resolved_addresses *resolved = NULL; + grpc_tcp_server *tcp = NULL; + size_t i; + int count = 0; + + resolved = grpc_blocking_resolve_address(addr, "http"); + if (!resolved) { + goto error; + } + + tcp = grpc_tcp_server_create(grpc_surface_em()); + if (!tcp) { + goto error; + } + + for (i = 0; i < resolved->naddrs; i++) { + if (grpc_tcp_server_add_port(tcp, + (struct sockaddr *)&resolved->addrs[i].addr, + resolved->addrs[i].len) >= 0) { + 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(server, tcp, start, destroy); + + return 1; + +/* Error path: cleanup and return */ +error: + if (resolved) { + grpc_resolved_addresses_destroy(resolved); + } + if (tcp) { + grpc_tcp_server_destroy(tcp); + } + return 0; +} diff --git a/src/core/surface/server_create.c b/src/core/surface/server_create.c new file mode 100644 index 0000000000..dcc6ce1ccc --- /dev/null +++ b/src/core/surface/server_create.c @@ -0,0 +1,41 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 "src/core/surface/completion_queue.h" +#include "src/core/surface/server.h" + +grpc_server *grpc_server_create(grpc_completion_queue *cq, + const grpc_channel_args *args) { + return grpc_server_create_from_filters(cq, NULL, 0, args); +} diff --git a/src/core/surface/surface_em.c b/src/core/surface/surface_em.c new file mode 100644 index 0000000000..e1785d1a44 --- /dev/null +++ b/src/core/surface/surface_em.c @@ -0,0 +1,55 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/surface/surface_em.h" +#include <grpc/support/log.h> + +static int initialized = 0; +static grpc_em em; + +grpc_em *grpc_surface_em() { + GPR_ASSERT(initialized && "call grpc_init()"); + return &em; +} + +void grpc_surface_em_init() { + GPR_ASSERT(!initialized); + initialized = 1; + grpc_em_init(&em); +} + +void grpc_surface_em_shutdown() { + GPR_ASSERT(initialized); + grpc_em_destroy(&em); + initialized = 0; +} diff --git a/src/core/surface/surface_em.h b/src/core/surface/surface_em.h new file mode 100644 index 0000000000..165f42f868 --- /dev/null +++ b/src/core/surface/surface_em.h @@ -0,0 +1,47 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_SURFACE_SURFACE_EM_H__ +#define __GRPC_INTERNAL_SURFACE_SURFACE_EM_H__ + +#include "src/core/eventmanager/em.h" + +/* Returns a global singleton event manager for + the surface apis, and is passed down to channels and + transports as needed. */ +grpc_em *grpc_surface_em(); + +void grpc_surface_em_init(); +void grpc_surface_em_shutdown(); + +#endif /* __GRPC_INTERNAL_SURFACE_SURFACE_EM_H__ */ diff --git a/src/core/surface/surface_trace.h b/src/core/surface/surface_trace.h new file mode 100644 index 0000000000..f6f9acfd9c --- /dev/null +++ b/src/core/surface/surface_trace.h @@ -0,0 +1,54 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_SURFACE_SURFACE_TRACE_H__ +#define __GRPC_INTERNAL_SURFACE_SURFACE_TRACE_H__ + +#include <grpc/support/log.h> + +/* #define GRPC_ENABLE_SURFACE_TRACE 1 */ + +#ifdef GRPC_ENABLE_SURFACE_TRACE +#define GRPC_SURFACE_TRACE_RETURNED_EVENT(cq, event) \ + do { \ + char *_ev = grpc_event_string(event); \ + gpr_log(GPR_INFO, "RETURN_EVENT[%p]: %s", cq, _ev); \ + gpr_free(_ev); \ + } while (0) +#else +#define GRPC_SURFACE_TRACE_RETURNED_EVENT(cq, event) \ + do { \ + } while (0) +#endif + +#endif /* __GRPC_INTERNAL_SURFACE_SURFACE_TRACE_H__ */ diff --git a/src/core/transport/chttp2/frame.h b/src/core/transport/chttp2/frame.h new file mode 100644 index 0000000000..7c0bbe026b --- /dev/null +++ b/src/core/transport/chttp2/frame.h @@ -0,0 +1,74 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_TRANSPORT_CHTTP2_FRAME_H__ +#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_H__ + +#include <grpc/support/port_platform.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; + +typedef struct { + gpr_uint8 end_of_stream; + gpr_uint8 need_flush_reads; + gpr_uint8 metadata_boundary; + gpr_uint8 ack_settings; + gpr_uint8 send_ping_ack; + gpr_uint8 process_ping_reply; + + gpr_uint32 window_update; +} grpc_chttp2_parse_state; + +#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_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_INTERNAL_TRANSPORT_CHTTP2_FRAME_H__ */ diff --git a/src/core/transport/chttp2/frame_data.c b/src/core/transport/chttp2/frame_data.c new file mode 100644 index 0000000000..fbd3b6cabf --- /dev/null +++ b/src/core/transport/chttp2/frame_data.c @@ -0,0 +1,164 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/transport/chttp2/frame_data.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string.h> +#include <grpc/support/useful.h> +#include "src/core/transport/transport.h" + +grpc_chttp2_parse_error grpc_chttp2_data_parser_init( + grpc_chttp2_data_parser *parser) { + parser->state = GRPC_CHTTP2_DATA_FH_0; + grpc_sopb_init(&parser->incoming_sopb); + return GRPC_CHTTP2_PARSE_OK; +} + +void grpc_chttp2_data_parser_destroy(grpc_chttp2_data_parser *parser) { + grpc_sopb_destroy(&parser->incoming_sopb); +} + +grpc_chttp2_parse_error grpc_chttp2_data_parser_begin_frame( + grpc_chttp2_data_parser *parser, gpr_uint8 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; +} + +grpc_chttp2_parse_error grpc_chttp2_data_parser_parse( + void *parser, grpc_chttp2_parse_state *state, gpr_slice slice, + int is_last) { + gpr_uint8 *const beg = GPR_SLICE_START_PTR(slice); + gpr_uint8 *const end = GPR_SLICE_END_PTR(slice); + gpr_uint8 *cur = beg; + grpc_chttp2_data_parser *p = parser; + + if (is_last && p->is_last_frame) { + state->end_of_stream = 1; + state->need_flush_reads = 1; + } + + if (cur == end) { + return GRPC_CHTTP2_PARSE_OK; + } + + switch (p->state) { + fh_0: + case GRPC_CHTTP2_DATA_FH_0: + p->frame_type = *cur; + if (++cur == end) { + p->state = GRPC_CHTTP2_DATA_FH_1; + return GRPC_CHTTP2_PARSE_OK; + } + switch (p->frame_type) { + case 0: + break; + case 1: + gpr_log(GPR_ERROR, "Compressed GRPC frames not yet supported"); + return GRPC_CHTTP2_STREAM_ERROR; + default: + gpr_log(GPR_ERROR, "Bad GRPC frame type 0x%02x", p->frame_type); + return GRPC_CHTTP2_STREAM_ERROR; + } + /* fallthrough */ + case GRPC_CHTTP2_DATA_FH_1: + p->frame_size = ((gpr_uint32)*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 |= ((gpr_uint32)*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 |= ((gpr_uint32)*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 |= ((gpr_uint32)*cur); + p->state = GRPC_CHTTP2_DATA_FRAME; + ++cur; + state->need_flush_reads = 1; + grpc_sopb_add_begin_message(&p->incoming_sopb, p->frame_size, 0); + /* fallthrough */ + case GRPC_CHTTP2_DATA_FRAME: + if (cur == end) { + return GRPC_CHTTP2_PARSE_OK; + } else if (end - cur == p->frame_size) { + state->need_flush_reads = 1; + grpc_sopb_add_slice(&p->incoming_sopb, + gpr_slice_sub(slice, cur - beg, end - beg)); + p->state = GRPC_CHTTP2_DATA_FH_0; + return GRPC_CHTTP2_PARSE_OK; + } else if (end - cur > p->frame_size) { + state->need_flush_reads = 1; + grpc_sopb_add_slice( + &p->incoming_sopb, + gpr_slice_sub(slice, cur - beg, cur + p->frame_size - beg)); + cur += p->frame_size; + goto fh_0; /* loop */ + } else { + state->need_flush_reads = 1; + grpc_sopb_add_slice(&p->incoming_sopb, + gpr_slice_sub(slice, cur - beg, end - beg)); + p->frame_size -= (end - cur); + return GRPC_CHTTP2_PARSE_OK; + } + } + + gpr_log(GPR_ERROR, "should never reach here"); + abort(); + return GRPC_CHTTP2_CONNECTION_ERROR; +} + diff --git a/src/core/transport/chttp2/frame_data.h b/src/core/transport/chttp2/frame_data.h new file mode 100644 index 0000000000..abe26dab76 --- /dev/null +++ b/src/core/transport/chttp2/frame_data.h @@ -0,0 +1,80 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_TRANSPORT_CHTTP2_FRAME_DATA_H__ +#define __GRPC_INTERNAL_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/transport/stream_op.h" +#include "src/core/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_stream_state state; + gpr_uint8 is_last_frame; + gpr_uint8 frame_type; + gpr_uint32 frame_size; + + grpc_stream_op_buffer incoming_sopb; +} grpc_chttp2_data_parser; + +/* 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_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, gpr_uint8 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( + void *parser, grpc_chttp2_parse_state *state, gpr_slice slice, int is_last); + +/* create a slice with an empty data frame and is_last set */ +gpr_slice grpc_chttp2_data_frame_create_empty_close(gpr_uint32 id); + +#endif /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_DATA_H__ */ diff --git a/src/core/transport/chttp2/frame_ping.c b/src/core/transport/chttp2/frame_ping.c new file mode 100644 index 0000000000..9556c0cae8 --- /dev/null +++ b/src/core/transport/chttp2/frame_ping.c @@ -0,0 +1,93 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/transport/chttp2/frame_ping.h" + +#include <string.h> + +#include <grpc/support/log.h> + +gpr_slice grpc_chttp2_ping_create(gpr_uint8 ack, gpr_uint8 *opaque_8bytes) { + gpr_slice slice = gpr_slice_malloc(9 + 8); + gpr_uint8 *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, gpr_uint32 length, gpr_uint8 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( + void *parser, grpc_chttp2_parse_state *state, gpr_slice slice, + int is_last) { + gpr_uint8 *const beg = GPR_SLICE_START_PTR(slice); + gpr_uint8 *const end = GPR_SLICE_END_PTR(slice); + gpr_uint8 *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) { + state->process_ping_reply = 1; + } else { + state->send_ping_ack = 1; + } + } + + return GRPC_CHTTP2_PARSE_OK; +} diff --git a/src/core/transport/chttp2/frame_ping.h b/src/core/transport/chttp2/frame_ping.h new file mode 100644 index 0000000000..a64d53644b --- /dev/null +++ b/src/core/transport/chttp2/frame_ping.h @@ -0,0 +1,53 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_TRANSPORT_CHTTP2_FRAME_PING_H__ +#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_PING_H__ + +#include <grpc/support/slice.h> +#include "src/core/transport/chttp2/frame.h" + +typedef struct { + gpr_uint8 byte; + gpr_uint8 is_ack; + gpr_uint8 opaque_8bytes[8]; +} grpc_chttp2_ping_parser; + +gpr_slice grpc_chttp2_ping_create(gpr_uint8 ack, gpr_uint8 *opaque_8bytes); + +grpc_chttp2_parse_error grpc_chttp2_ping_parser_begin_frame( + grpc_chttp2_ping_parser *parser, gpr_uint32 length, gpr_uint8 flags); +grpc_chttp2_parse_error grpc_chttp2_ping_parser_parse( + void *parser, grpc_chttp2_parse_state *state, gpr_slice slice, int is_last); + +#endif /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_PING_H__ */ diff --git a/src/core/transport/chttp2/frame_rst_stream.c b/src/core/transport/chttp2/frame_rst_stream.c new file mode 100644 index 0000000000..825e156e46 --- /dev/null +++ b/src/core/transport/chttp2/frame_rst_stream.c @@ -0,0 +1,56 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/transport/chttp2/frame_rst_stream.h" +#include "src/core/transport/chttp2/frame.h" + +gpr_slice grpc_chttp2_rst_stream_create(gpr_uint32 id, gpr_uint32 code) { + gpr_slice slice = gpr_slice_malloc(13); + gpr_uint8 *p = GPR_SLICE_START_PTR(slice); + + *p++ = 0; + *p++ = 0; + *p++ = 4; + *p++ = GRPC_CHTTP2_FRAME_RST_STREAM; + *p++ = 0; + *p++ = id >> 24; + *p++ = id >> 16; + *p++ = id >> 8; + *p++ = id; + *p++ = code >> 24; + *p++ = code >> 16; + *p++ = code >> 8; + *p++ = code; + + return slice; +} diff --git a/src/core/transport/chttp2/frame_rst_stream.h b/src/core/transport/chttp2/frame_rst_stream.h new file mode 100644 index 0000000000..78aea0f26a --- /dev/null +++ b/src/core/transport/chttp2/frame_rst_stream.h @@ -0,0 +1,41 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_TRANSPORT_CHTTP2_FRAME_RST_STREAM_H__ +#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_RST_STREAM_H__ + +#include <grpc/support/slice.h> + +gpr_slice grpc_chttp2_rst_stream_create(gpr_uint32 stream_id, gpr_uint32 code); + +#endif /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_RST_STREAM_H__ */ diff --git a/src/core/transport/chttp2/frame_settings.c b/src/core/transport/chttp2/frame_settings.c new file mode 100644 index 0000000000..488b96a728 --- /dev/null +++ b/src/core/transport/chttp2/frame_settings.c @@ -0,0 +1,227 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/transport/chttp2/frame_settings.h" + +#include <string.h> + +#include "src/core/transport/chttp2/frame.h" +#include <grpc/support/log.h> +#include <grpc/support/useful.h> + +/* 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}, + {"HEADER_TABLE_SIZE", 4096, 0, 0xffffffff, + GRPC_CHTTP2_CLAMP_INVALID_VALUE}, + {"ENABLE_PUSH", 1, 0, 1, GRPC_CHTTP2_DISCONNECT_ON_INVALID_VALUE}, + {"MAX_CONCURRENT_STREAMS", 0xffffffffu, 0, 0xffffffffu, + GRPC_CHTTP2_DISCONNECT_ON_INVALID_VALUE}, + {"INITIAL_WINDOW_SIZE", 65535, 0, 0xffffffffu, + GRPC_CHTTP2_DISCONNECT_ON_INVALID_VALUE}, + {"MAX_FRAME_SIZE", 16384, 16384, 16777215, + GRPC_CHTTP2_DISCONNECT_ON_INVALID_VALUE}, + {"MAX_HEADER_LIST_SIZE", 0xffffffffu, 0, 0xffffffffu, + GRPC_CHTTP2_CLAMP_INVALID_VALUE}, +}; + +static gpr_uint8 *fill_header(gpr_uint8 *out, gpr_uint32 length, + gpr_uint8 flags) { + *out++ = length >> 16; + *out++ = length >> 8; + *out++ = length; + *out++ = GRPC_CHTTP2_FRAME_SETTINGS; + *out++ = flags; + *out++ = 0; + *out++ = 0; + *out++ = 0; + *out++ = 0; + return out; +} + +gpr_slice grpc_chttp2_settings_create(gpr_uint32 *old, const gpr_uint32 *new, + size_t count) { + size_t i; + size_t n = 0; + gpr_slice output; + gpr_uint8 *p; + + for (i = 0; i < count; i++) { + n += (new[i] != old[i]); + } + + 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]) { + GPR_ASSERT(i); + *p++ = i >> 8; + *p++ = i; + *p++ = new[i] >> 24; + *p++ = new[i] >> 16; + *p++ = new[i] >> 8; + *p++ = new[i]; + old[i] = new[i]; + } + } + + GPR_ASSERT(p == GPR_SLICE_END_PTR(output)); + + return output; +} + +gpr_slice grpc_chttp2_settings_ack_create() { + 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, gpr_uint32 length, gpr_uint8 flags, + gpr_uint32 *settings) { + parser->target_settings = settings; + memcpy(parser->incoming_settings, settings, + GRPC_CHTTP2_NUM_SETTINGS * sizeof(gpr_uint32)); + 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( + void *p, grpc_chttp2_parse_state *state, gpr_slice slice, int is_last) { + grpc_chttp2_settings_parser *parser = p; + const gpr_uint8 *cur = GPR_SLICE_START_PTR(slice); + const gpr_uint8 *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) { + memcpy(parser->target_settings, parser->incoming_settings, + GRPC_CHTTP2_NUM_SETTINGS * sizeof(gpr_uint32)); + state->ack_settings = 1; + } + return GRPC_CHTTP2_PARSE_OK; + } + parser->id = ((gpr_uint16)*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 |= (*cur); + cur++; + /* fallthrough */ + case GRPC_CHTTP2_SPS_VAL0: + if (cur == end) { + parser->state = GRPC_CHTTP2_SPS_VAL0; + return GRPC_CHTTP2_PARSE_OK; + } + parser->value = ((gpr_uint32)*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 |= ((gpr_uint32)*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 |= ((gpr_uint32)*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: + gpr_log(GPR_ERROR, "invalid value %u passed for %s", + parser->value, sp->name); + return GRPC_CHTTP2_CONNECTION_ERROR; + } + } + parser->incoming_settings[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/transport/chttp2/frame_settings.h b/src/core/transport/chttp2/frame_settings.h new file mode 100644 index 0000000000..74e2b4fa22 --- /dev/null +++ b/src/core/transport/chttp2/frame_settings.h @@ -0,0 +1,99 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_TRANSPORT_CHTTP2_FRAME_SETTINGS_H__ +#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_SETTINGS_H__ + +#include <grpc/support/port_platform.h> +#include <grpc/support/slice.h> +#include "src/core/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; + gpr_uint32 *target_settings; + gpr_uint8 is_ack; + gpr_uint16 id; + gpr_uint32 value; + gpr_uint32 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; + gpr_uint32 default_value; + gpr_uint32 min_value; + gpr_uint32 max_value; + grpc_chttp2_invalid_value_behavior invalid_value_behavior; +} 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(gpr_uint32 *old, const gpr_uint32 *new, + size_t count); +/* Create an ack settings frame */ +gpr_slice grpc_chttp2_settings_ack_create(); + +grpc_chttp2_parse_error grpc_chttp2_settings_parser_begin_frame( + grpc_chttp2_settings_parser *parser, gpr_uint32 length, gpr_uint8 flags, + gpr_uint32 *settings); +grpc_chttp2_parse_error grpc_chttp2_settings_parser_parse( + void *parser, grpc_chttp2_parse_state *state, gpr_slice slice, int is_last); + +#endif /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_SETTINGS_H__ */ diff --git a/src/core/transport/chttp2/frame_window_update.c b/src/core/transport/chttp2/frame_window_update.c new file mode 100644 index 0000000000..f61714f52b --- /dev/null +++ b/src/core/transport/chttp2/frame_window_update.c @@ -0,0 +1,99 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/transport/chttp2/frame_window_update.h" + +#include <grpc/support/log.h> + +gpr_slice grpc_chttp2_window_update_create(gpr_uint32 id, + gpr_uint32 window_update) { + gpr_slice slice = gpr_slice_malloc(13); + gpr_uint8 *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++ = id >> 24; + *p++ = id >> 16; + *p++ = id >> 8; + *p++ = id; + *p++ = window_update >> 24; + *p++ = window_update >> 16; + *p++ = window_update >> 8; + *p++ = window_update; + + return slice; +} + +grpc_chttp2_parse_error grpc_chttp2_window_update_parser_begin_frame( + grpc_chttp2_window_update_parser *parser, gpr_uint32 length, + gpr_uint8 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( + void *parser, grpc_chttp2_parse_state *state, gpr_slice slice, + int is_last) { + gpr_uint8 *const beg = GPR_SLICE_START_PTR(slice); + gpr_uint8 *const end = GPR_SLICE_END_PTR(slice); + gpr_uint8 *cur = beg; + grpc_chttp2_window_update_parser *p = parser; + + while (p->byte != 4 && cur != end) { + p->amount |= ((gpr_uint32)*cur) << (8 * (3 - p->byte)); + cur++; + p->byte++; + } + + if (p->byte == 4) { + if (p->amount == 0 || (p->amount & 0x80000000u)) { + gpr_log(GPR_ERROR, "invalid window update bytes: %d", p->amount); + return GRPC_CHTTP2_CONNECTION_ERROR; + } + GPR_ASSERT(is_last); + state->window_update = p->amount; + } + + return GRPC_CHTTP2_PARSE_OK; +} diff --git a/src/core/transport/chttp2/frame_window_update.h b/src/core/transport/chttp2/frame_window_update.h new file mode 100644 index 0000000000..4b789fcc4a --- /dev/null +++ b/src/core/transport/chttp2/frame_window_update.h @@ -0,0 +1,55 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_TRANSPORT_CHTTP2_FRAME_WINDOW_UPDATE_H__ +#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_WINDOW_UPDATE_H__ + +#include <grpc/support/slice.h> +#include "src/core/transport/chttp2/frame.h" + +typedef struct { + gpr_uint8 byte; + gpr_uint8 is_connection_update; + gpr_uint32 amount; +} grpc_chttp2_window_update_parser; + +gpr_slice grpc_chttp2_window_update_create(gpr_uint32 id, + gpr_uint32 window_delta); + +grpc_chttp2_parse_error grpc_chttp2_window_update_parser_begin_frame( + grpc_chttp2_window_update_parser *parser, gpr_uint32 length, + gpr_uint8 flags); +grpc_chttp2_parse_error grpc_chttp2_window_update_parser_parse( + void *parser, grpc_chttp2_parse_state *state, gpr_slice slice, int is_last); + +#endif /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_WINDOW_UPDATE_H__ */ diff --git a/src/core/transport/chttp2/gen_hpack_tables.c b/src/core/transport/chttp2/gen_hpack_tables.c new file mode 100644 index 0000000000..cc94a737ca --- /dev/null +++ b/src/core/transport/chttp2/gen_hpack_tables.c @@ -0,0 +1,589 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* generates constant tables for hpack.c */ + +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> + +#include <grpc/support/log.h> + +/* + * first byte LUT generation + */ + +typedef struct { + const char *call; + /* bit prefix for the field type */ + unsigned char prefix; + /* length of the bit prefix for the field type */ + unsigned char prefix_length; + /* index value: 0 = all zeros, 2 = all ones, 1 otherwise */ + unsigned char index; +} spec; + +static const spec fields[] = { + {"INDEXED_FIELD", 0X80, 1, 1}, + {"INDEXED_FIELD_X", 0X80, 1, 2}, + {"LITHDR_INCIDX", 0X40, 2, 1}, + {"LITHDR_INCIDX_X", 0X40, 2, 2}, + {"LITHDR_INCIDX_V", 0X40, 2, 0}, + {"LITHDR_NOTIDX", 0X00, 4, 1}, + {"LITHDR_NOTIDX_X", 0X00, 4, 2}, + {"LITHDR_NOTIDX_V", 0X00, 4, 0}, + {"LITHDR_NVRIDX", 0X10, 4, 1}, + {"LITHDR_NVRIDX_X", 0X10, 4, 2}, + {"LITHDR_NVRIDX_V", 0X10, 4, 0}, + {"MAX_TBL_SIZE", 0X20, 3, 1}, + {"MAX_TBL_SIZE_X", 0X20, 3, 2}, +}; + +static const int num_fields = sizeof(fields) / sizeof(*fields); + +static unsigned char prefix_mask(unsigned char prefix_len) { + unsigned char i; + unsigned char out = 0; + for (i = 0; i < prefix_len; i++) { + out |= 1 << (7 - i); + } + return out; +} + +static unsigned char suffix_mask(unsigned char prefix_len) { + return ~prefix_mask(prefix_len); +} + +static void generate_first_byte_lut() { + int i, j, n; + const spec *chrspec; + unsigned char suffix; + + n = printf("static CALLTYPE first_byte[256] = {"); + /* for each potential first byte of a header */ + for (i = 0; i < 256; i++) { + /* find the field type that matches it */ + chrspec = NULL; + for (j = 0; j < num_fields; j++) { + if ((prefix_mask(fields[j].prefix_length) & i) == fields[j].prefix) { + suffix = suffix_mask(fields[j].prefix_length) & i; + if (suffix == suffix_mask(fields[j].prefix_length)) { + if (fields[j].index != 2) continue; + } else if (suffix == 0) { + if (fields[j].index != 0) continue; + } else { + if (fields[j].index != 1) continue; + } + GPR_ASSERT(chrspec == NULL); + chrspec = &fields[j]; + } + } + if (chrspec) { + n += printf("%s, ", chrspec->call); + } else { + n += printf("ILLEGAL, "); + } + /* make some small effort towards readable output */ + if (n > 70) { + printf("\n "); + n = 2; + } + } + printf("};\n"); +} + +/* + * Huffman decoder table generation + */ + +#define NSYMS 257 +#define MAXHUFFSTATES 1024 + +/* Constants pulled from the HPACK spec, and converted to C using the vim + command: + :%s/.* \([0-9a-f]\+\) \[ *\([0-9]\+\)\]/{0x\1, \2},/g */ +static const struct { + unsigned bits; + unsigned length; +} huffsyms[NSYMS] = { + {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}, +}; + +/* represents a set of symbols as an array of booleans indicating inclusion */ +typedef struct { char included[NSYMS]; } symset; +/* represents a lookup table indexed by a nibble */ +typedef struct { int values[16]; } nibblelut; + +/* returns a symset that includes all possible symbols */ +static symset symset_all() { + symset x; + memset(x.included, 1, sizeof(x.included)); + return x; +} + +/* returns a symset that includes no symbols */ +static symset symset_none() { + symset x; + memset(x.included, 0, sizeof(x.included)); + return x; +} + +/* returns an empty nibblelut */ +static nibblelut nibblelut_empty() { + nibblelut x; + int i; + for (i = 0; i < 16; i++) { + x.values[i] = -1; + } + return x; +} + +/* counts symbols in a symset - only used for debug builds */ +#ifndef NDEBUG +static int nsyms(symset s) { + int i; + int c = 0; + for (i = 0; i < NSYMS; i++) { + c += s.included[i] != 0; + } + return c; +} +#endif + +/* global table of discovered huffman decoding states */ +static struct { + /* the bit offset that this state starts at */ + int bitofs; + /* the set of symbols that this state started with */ + symset syms; + + /* lookup table for the next state */ + nibblelut next; + /* lookup table for what to emit */ + nibblelut emit; +} huffstates[MAXHUFFSTATES]; +static int nhuffstates = 0; + +/* given a number of decoded bits and a set of symbols that are live, + return the index into the decoder table for this state. + set isnew to 1 if this state was previously undiscovered */ +static int state_index(int bitofs, symset syms, int *isnew) { + int i; + for (i = 0; i < nhuffstates; i++) { + if (huffstates[i].bitofs != bitofs) continue; + if (0 != memcmp(huffstates[i].syms.included, syms.included, NSYMS)) + continue; + *isnew = 0; + return i; + } + GPR_ASSERT(nhuffstates != MAXHUFFSTATES); + i = nhuffstates++; + huffstates[i].bitofs = bitofs; + huffstates[i].syms = syms; + huffstates[i].next = nibblelut_empty(); + huffstates[i].emit = nibblelut_empty(); + *isnew = 1; + return i; +} + +/* recursively build a decoding table + + state - the huffman state that we are trying to fill in + nibble - the current nibble + nibbits - the number of bits in the nibble that have been filled in + bitofs - the number of bits of symbol that have been decoded + emit - the symbol to emit on this nibble (or -1 if no symbol has been + found) + syms - the set of symbols that could be matched */ +static void build_dec_tbl(int state, int nibble, int nibbits, int bitofs, + int emit, symset syms) { + int i; + int bit; + + /* If we have four bits in the nibble we're looking at, then we can fill in + a slot in the lookup tables. */ + if (nibbits == 4) { + int isnew; + /* Find the state that we are in: this may be a new state, in which case + we recurse to fill it in, or we may have already seen this state, in + which case the recursion terminates */ + int st = state_index(bitofs, syms, &isnew); + GPR_ASSERT(huffstates[state].next.values[nibble] == -1); + huffstates[state].next.values[nibble] = st; + huffstates[state].emit.values[nibble] = emit; + if (isnew) { + build_dec_tbl(st, 0, 0, bitofs, -1, syms); + } + return; + } + + assert(nsyms(syms)); + + /* A bit can be 0 or 1 */ + for (bit = 0; bit < 2; bit++) { + /* walk over active symbols and see if they have this bit set */ + symset nextsyms = symset_none(); + for (i = 0; i < NSYMS; i++) { + if (!syms.included[i]) continue; /* disregard inactive symbols */ + if (((huffsyms[i].bits >> (huffsyms[i].length - bitofs - 1)) & 1) == + bit) { + /* the bit is set, include it in the next recursive set */ + if (huffsyms[i].length == bitofs + 1) { + /* additionally, we've gotten to the end of a symbol - this is a + special recursion step: re-activate all the symbols, reset + bitofs to zero, and recurse */ + build_dec_tbl(state, (nibble << 1) | bit, nibbits + 1, 0, i, + symset_all()); + /* skip the remainder of this loop */ + goto next; + } + nextsyms.included[i] = 1; + } + } + /* recurse down for this bit */ + build_dec_tbl(state, (nibble << 1) | bit, nibbits + 1, bitofs + 1, emit, + nextsyms); + next: + ; + } +} + +static nibblelut ctbl[MAXHUFFSTATES]; +static int nctbl; + +static int ctbl_idx(nibblelut x) { + int i; + for (i = 0; i < nctbl; i++) { + if (0 == memcmp(&x, ctbl + i, sizeof(nibblelut))) return i; + } + ctbl[i] = x; + nctbl++; + return i; +} + +static void dump_ctbl(const char *name) { + int i, j; + printf("static const gpr_int16 %s[%d*16] = {\n", name, nctbl); + for (i = 0; i < nctbl; i++) { + for (j = 0; j < 16; j++) { + printf("%d,", ctbl[i].values[j]); + } + printf("\n"); + } + printf("};\n"); +} + +static void generate_huff_tables() { + int i; + build_dec_tbl(state_index(0, symset_all(), &i), 0, 0, 0, -1, symset_all()); + + nctbl = 0; + printf("static const gpr_uint8 next_tbl[%d] = {", nhuffstates); + for (i = 0; i < nhuffstates; i++) { + printf("%d,", ctbl_idx(huffstates[i].next)); + } + printf("};\n"); + dump_ctbl("next_sub_tbl"); + + nctbl = 0; + printf("static const gpr_uint16 emit_tbl[%d] = {", nhuffstates); + for (i = 0; i < nhuffstates; i++) { + printf("%d,", ctbl_idx(huffstates[i].emit)); + } + printf("};\n"); + dump_ctbl("emit_sub_tbl"); +} + +int main(void) { + generate_huff_tables(); + generate_first_byte_lut(); + + return 0; +} diff --git a/src/core/transport/chttp2/hpack_parser.c b/src/core/transport/chttp2/hpack_parser.c new file mode 100644 index 0000000000..33588a73d4 --- /dev/null +++ b/src/core/transport/chttp2/hpack_parser.c @@ -0,0 +1,1212 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/transport/chttp2/hpack_parser.h" + +#include <stddef.h> +#include <string.h> +#include <assert.h> + +#include "src/core/support/murmur_hash.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/port_platform.h> +#include <grpc/support/string.h> +#include <grpc/support/useful.h> + +/* 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 gpr_uint8 *cur, + const gpr_uint8 *end); +static int parse_error(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur, + const gpr_uint8 *end); + +static int parse_string_prefix(grpc_chttp2_hpack_parser *p, + const gpr_uint8 *cur, const gpr_uint8 *end); +static int parse_key_string(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur, + const gpr_uint8 *end); +static int parse_value_string(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur, + const gpr_uint8 *end); + +static int parse_value0(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur, + const gpr_uint8 *end); +static int parse_value1(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur, + const gpr_uint8 *end); +static int parse_value2(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur, + const gpr_uint8 *end); +static int parse_value3(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur, + const gpr_uint8 *end); +static int parse_value4(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur, + const gpr_uint8 *end); +static int parse_value5up(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur, + const gpr_uint8 *end); + +static int parse_indexed_field(grpc_chttp2_hpack_parser *p, + const gpr_uint8 *cur, const gpr_uint8 *end); +static int parse_indexed_field_x(grpc_chttp2_hpack_parser *p, + const gpr_uint8 *cur, const gpr_uint8 *end); +static int parse_lithdr_incidx(grpc_chttp2_hpack_parser *p, + const gpr_uint8 *cur, const gpr_uint8 *end); +static int parse_lithdr_incidx_x(grpc_chttp2_hpack_parser *p, + const gpr_uint8 *cur, const gpr_uint8 *end); +static int parse_lithdr_incidx_v(grpc_chttp2_hpack_parser *p, + const gpr_uint8 *cur, const gpr_uint8 *end); +static int parse_lithdr_notidx(grpc_chttp2_hpack_parser *p, + const gpr_uint8 *cur, const gpr_uint8 *end); +static int parse_lithdr_notidx_x(grpc_chttp2_hpack_parser *p, + const gpr_uint8 *cur, const gpr_uint8 *end); +static int parse_lithdr_notidx_v(grpc_chttp2_hpack_parser *p, + const gpr_uint8 *cur, const gpr_uint8 *end); +static int parse_lithdr_nvridx(grpc_chttp2_hpack_parser *p, + const gpr_uint8 *cur, const gpr_uint8 *end); +static int parse_lithdr_nvridx_x(grpc_chttp2_hpack_parser *p, + const gpr_uint8 *cur, const gpr_uint8 *end); +static int parse_lithdr_nvridx_v(grpc_chttp2_hpack_parser *p, + const gpr_uint8 *cur, const gpr_uint8 *end); +static int parse_max_tbl_size(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur, + const gpr_uint8 *end); +static int parse_max_tbl_size_x(grpc_chttp2_hpack_parser *p, + const gpr_uint8 *cur, const gpr_uint8 *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_error}; + +/* indexes the first byte to a parse state function - generated by + gen_hpack_tables.c */ +static const gpr_uint8 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, + ILLEGAL, 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 gpr_uint8 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 gpr_int16 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 gpr_uint16 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 gpr_int16 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, +}; + +/* emission helpers */ +static void on_hdr(grpc_chttp2_hpack_parser *p, grpc_mdelem *md, + int add_to_table) { + if (add_to_table) { + grpc_mdelem_ref(md); + grpc_chttp2_hptbl_add(&p->table, md); + } + p->on_header(p->on_header_user_data, md); +} + +static grpc_mdstr *take_string(grpc_chttp2_hpack_parser *p, + grpc_chttp2_hpack_parser_string *str) { + grpc_mdstr *s = grpc_mdstr_from_buffer(p->table.mdctx, (gpr_uint8 *)str->str, + str->length); + str->length = 0; + return s; +} + +/* jump to the next state */ +static int parse_next(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur, + const gpr_uint8 *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 gpr_uint8 *cur, + const gpr_uint8 *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 gpr_uint8 *cur, const gpr_uint8 *end) { + if (cur == end) { + p->state = parse_stream_weight; + return 1; + } + + return parse_begin(p, cur + 1, end); +} + +static int parse_stream_dep3(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur, + const gpr_uint8 *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 gpr_uint8 *cur, + const gpr_uint8 *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 gpr_uint8 *cur, + const gpr_uint8 *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 gpr_uint8 *cur, + const gpr_uint8 *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 gpr_uint8 *cur, const gpr_uint8 *end) { + grpc_mdelem *md = grpc_chttp2_hptbl_lookup(&p->table, p->index); + grpc_mdelem_ref(md); + on_hdr(p, md, 0); + return parse_begin(p, cur, end); +} + +/* parse an indexed field with index < 127 */ +static int parse_indexed_field(grpc_chttp2_hpack_parser *p, + const gpr_uint8 *cur, const gpr_uint8 *end) { + 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 gpr_uint8 *cur, const gpr_uint8 *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + finish_indexed_field}; + 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 gpr_uint8 *cur, const gpr_uint8 *end) { + grpc_mdelem *md = grpc_chttp2_hptbl_lookup(&p->table, p->index); + on_hdr(p, grpc_mdelem_from_metadata_strings(p->table.mdctx, + grpc_mdstr_ref(md->key), + take_string(p, &p->value)), + 1); + return 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 gpr_uint8 *cur, const gpr_uint8 *end) { + on_hdr(p, grpc_mdelem_from_metadata_strings(p->table.mdctx, + take_string(p, &p->key), + take_string(p, &p->value)), + 1); + return 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 gpr_uint8 *cur, const gpr_uint8 *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + parse_value_string, finish_lithdr_incidx}; + 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 gpr_uint8 *cur, const gpr_uint8 *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + parse_string_prefix, parse_value_string, finish_lithdr_incidx}; + 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 gpr_uint8 *cur, const gpr_uint8 *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + parse_key_string, parse_string_prefix, parse_value_string, + finish_lithdr_incidx_v}; + 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 gpr_uint8 *cur, const gpr_uint8 *end) { + grpc_mdelem *md = grpc_chttp2_hptbl_lookup(&p->table, p->index); + on_hdr(p, grpc_mdelem_from_metadata_strings(p->table.mdctx, + grpc_mdstr_ref(md->key), + take_string(p, &p->value)), + 0); + return 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 gpr_uint8 *cur, const gpr_uint8 *end) { + on_hdr(p, grpc_mdelem_from_metadata_strings(p->table.mdctx, + take_string(p, &p->key), + take_string(p, &p->value)), + 0); + return 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 gpr_uint8 *cur, const gpr_uint8 *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + parse_value_string, finish_lithdr_notidx}; + 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 gpr_uint8 *cur, const gpr_uint8 *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + parse_string_prefix, parse_value_string, finish_lithdr_notidx}; + 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 gpr_uint8 *cur, const gpr_uint8 *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + parse_key_string, parse_string_prefix, parse_value_string, + finish_lithdr_notidx_v}; + 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 gpr_uint8 *cur, const gpr_uint8 *end) { + grpc_mdelem *md = grpc_chttp2_hptbl_lookup(&p->table, p->index); + on_hdr(p, grpc_mdelem_from_metadata_strings(p->table.mdctx, + grpc_mdstr_ref(md->key), + take_string(p, &p->value)), + 0); + return 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 gpr_uint8 *cur, const gpr_uint8 *end) { + on_hdr(p, grpc_mdelem_from_metadata_strings(p->table.mdctx, + take_string(p, &p->key), + take_string(p, &p->value)), + 0); + return 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 gpr_uint8 *cur, const gpr_uint8 *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + parse_value_string, finish_lithdr_nvridx}; + 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 gpr_uint8 *cur, const gpr_uint8 *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + parse_string_prefix, parse_value_string, finish_lithdr_nvridx}; + 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 gpr_uint8 *cur, const gpr_uint8 *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + parse_key_string, parse_string_prefix, parse_value_string, + finish_lithdr_nvridx_v}; + 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 gpr_uint8 *cur, const gpr_uint8 *end) { + gpr_log(GPR_INFO, "MAX TABLE SIZE: %d", p->index); + abort(); /* not implemented */ + return 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 gpr_uint8 *cur, + const gpr_uint8 *end) { + p->index = (*cur) & 0xf; + 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 gpr_uint8 *cur, const gpr_uint8 *end) { + static const grpc_chttp2_hpack_parser_state and_then[] = { + finish_max_tbl_size}; + p->next_state = and_then; + p->index = 0xf; + 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 gpr_uint8 *cur, + const gpr_uint8 *end) { + p->state = parse_error; + return 0; +} + +/* 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 gpr_uint8 *cur, + const gpr_uint8 *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 gpr_uint8 *cur, + const gpr_uint8 *end) { + if (cur == end) { + p->state = parse_value1; + return 1; + } + + *p->parsing.value += (((gpr_uint32)*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 gpr_uint8 *cur, + const gpr_uint8 *end) { + if (cur == end) { + p->state = parse_value2; + return 1; + } + + *p->parsing.value += (((gpr_uint32)*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 gpr_uint8 *cur, + const gpr_uint8 *end) { + if (cur == end) { + p->state = parse_value3; + return 1; + } + + *p->parsing.value += (((gpr_uint32)*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 gpr_uint8 *cur, + const gpr_uint8 *end) { + gpr_uint8 c; + gpr_uint32 cur_value; + gpr_uint32 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 = ((gpr_uint32)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", + *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 gpr_uint8 *cur, + const gpr_uint8 *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 4"); + return parse_error(p, cur, end); +} + +/* parse a string prefix */ +static int parse_string_prefix(grpc_chttp2_hpack_parser *p, + const gpr_uint8 *cur, const gpr_uint8 *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_string(grpc_chttp2_hpack_parser_string *str, + const gpr_uint8 *data, size_t length) { + if (length + str->length > str->capacity) { + str->capacity = str->length + length; + str->str = gpr_realloc(str->str, str->capacity); + } + memcpy(str->str + str->length, data, length); + str->length += length; +} + +/* append a null terminator to a string */ +static void finish_str(grpc_chttp2_hpack_parser_string *str) { + gpr_uint8 terminator = 0; + append_string(str, &terminator, 1); + str->length--; /* don't actually count the null terminator */ +} + +/* decode a nibble from a huffman encoded stream */ +static void huff_nibble(grpc_chttp2_hpack_parser *p, gpr_uint8 nibble) { + gpr_int16 emit = emit_sub_tbl[16 * emit_tbl[p->huff_state] + nibble]; + gpr_int16 next = next_sub_tbl[16 * next_tbl[p->huff_state] + nibble]; + if (emit != -1) { + if (emit >= 0 && emit < 256) { + gpr_uint8 c = emit; + append_string(p->parsing.str, &c, 1); + } else { + assert(emit == 256); + } + } + p->huff_state = next; +} + +/* decode full bytes from a huffman encoded stream */ +static void add_huff_bytes(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur, + const gpr_uint8 *end) { + for (; cur != end; ++cur) { + huff_nibble(p, *cur >> 4); + huff_nibble(p, *cur & 0xf); + } +} + +/* decode some string bytes based on the current decoding mode + (huffman or not) */ +static void add_str_bytes(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur, + const gpr_uint8 *end) { + if (p->huff) { + add_huff_bytes(p, cur, end); + } else { + append_string(p->parsing.str, cur, end - cur); + } +} + +/* parse a string - tries to do large chunks at a time */ +static int parse_string(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur, + const gpr_uint8 *end) { + size_t remaining = p->strlen - p->strgot; + size_t given = end - cur; + if (remaining <= given) { + add_str_bytes(p, cur, cur + remaining); + finish_str(p->parsing.str); + return parse_next(p, cur + remaining, end); + } else { + add_str_bytes(p, cur, cur + given); + p->strgot += 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 gpr_uint8 *cur, + const gpr_uint8 *end, + grpc_chttp2_hpack_parser_string *str) { + p->strgot = 0; + str->length = 0; + p->parsing.str = str; + p->huff_state = 0; + return parse_string(p, cur, end); +} + +/* parse the key string */ +static int parse_key_string(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur, + const gpr_uint8 *end) { + return begin_parse_string(p, cur, end, &p->key); +} + +/* parse the value string */ +static int parse_value_string(grpc_chttp2_hpack_parser *p, const gpr_uint8 *cur, + const gpr_uint8 *end) { + return begin_parse_string(p, cur, end, &p->value); +} + +/* PUBLIC INTERFACE */ + +static void on_header_not_set(void *user_data, grpc_mdelem *md) { + char *keyhex = + gpr_hexdump(grpc_mdstr_as_c_string(md->key), + GPR_SLICE_LENGTH(md->key->slice), GPR_HEXDUMP_PLAINTEXT); + char *valuehex = + gpr_hexdump(grpc_mdstr_as_c_string(md->value), + GPR_SLICE_LENGTH(md->value->slice), GPR_HEXDUMP_PLAINTEXT); + gpr_log(GPR_ERROR, "on_header callback not set; key=%s value=%s", keyhex, + valuehex); + gpr_free(keyhex); + gpr_free(valuehex); + grpc_mdelem_unref(md); + abort(); +} + +void grpc_chttp2_hpack_parser_init(grpc_chttp2_hpack_parser *p, + grpc_mdctx *mdctx) { + 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; + grpc_chttp2_hptbl_init(&p->table, mdctx); +} + +void grpc_chttp2_hpack_parser_set_has_priority(grpc_chttp2_hpack_parser *p) { + GPR_ASSERT(p->state == parse_begin); + 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 gpr_uint8 *beg, const gpr_uint8 *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( + void *hpack_parser, grpc_chttp2_parse_state *state, gpr_slice slice, + int is_last) { + grpc_chttp2_hpack_parser *parser = hpack_parser; + if (!grpc_chttp2_hpack_parser_parse(parser, GPR_SLICE_START_PTR(slice), + GPR_SLICE_END_PTR(slice))) { + 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"); + return GRPC_CHTTP2_CONNECTION_ERROR; + } + state->metadata_boundary = parser->is_boundary; + state->end_of_stream = parser->is_eof; + state->need_flush_reads = parser->is_eof; + parser->on_header = on_header_not_set; + parser->on_header_user_data = NULL; + parser->is_boundary = 0xde; + parser->is_eof = 0xde; + } + return GRPC_CHTTP2_PARSE_OK; +} diff --git a/src/core/transport/chttp2/hpack_parser.h b/src/core/transport/chttp2/hpack_parser.h new file mode 100644 index 0000000000..cf68042fbd --- /dev/null +++ b/src/core/transport/chttp2/hpack_parser.h @@ -0,0 +1,108 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_TRANSPORT_CHTTP2_HPACK_PARSER_H__ +#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_HPACK_PARSER_H__ + +#include <stddef.h> + +#include <grpc/support/port_platform.h> +#include "src/core/transport/chttp2/frame.h" +#include "src/core/transport/chttp2/hpack_table.h" +#include "src/core/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 gpr_uint8 *beg, + const gpr_uint8 *end); + +typedef struct { + char *str; + gpr_uint32 length; + gpr_uint32 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; + /* the value we're currently parsing */ + union { + gpr_uint32 *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 */ + gpr_uint32 index; + /* length of source bytes for the currently parsing string */ + gpr_uint32 strlen; + /* number of source bytes read for the currently parsing string */ + gpr_uint32 strgot; + /* huffman decoding state */ + gpr_uint16 huff_state; + /* is the current string huffman encoded? */ + gpr_uint8 huff; + /* set by higher layers, used by grpc_chttp2_header_parser_parse to signal + it should append a metadata boundary at the end of frame */ + gpr_uint8 is_boundary; + gpr_uint8 is_eof; + + /* hpack table */ + grpc_chttp2_hptbl table; +}; + +void grpc_chttp2_hpack_parser_init(grpc_chttp2_hpack_parser *p, + grpc_mdctx *mdctx); +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 gpr_uint8 *beg, const gpr_uint8 *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( + void *hpack_parser, grpc_chttp2_parse_state *state, gpr_slice slice, + int is_last); + +#endif /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_HPACK_PARSER_H__ */ diff --git a/src/core/transport/chttp2/hpack_table.c b/src/core/transport/chttp2/hpack_table.c new file mode 100644 index 0000000000..8f2ebecfeb --- /dev/null +++ b/src/core/transport/chttp2/hpack_table.c @@ -0,0 +1,210 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/transport/chttp2/hpack_table.h" + +#include <assert.h> +#include <string.h> + +#include <grpc/support/log.h> +#include "src/core/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", ""}, +}; + +void grpc_chttp2_hptbl_init(grpc_chttp2_hptbl *tbl, grpc_mdctx *mdctx) { + size_t i; + + memset(tbl, 0, sizeof(*tbl)); + tbl->mdctx = mdctx; + tbl->max_bytes = GRPC_CHTTP2_INITIAL_HPACK_TABLE_SIZE; + for (i = 1; i <= GRPC_CHTTP2_LAST_STATIC_ENTRY; i++) { + tbl->static_ents[i - 1] = grpc_mdelem_from_strings( + mdctx, 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) % GRPC_CHTTP2_MAX_TABLE_COUNT]); + } +} + +grpc_mdelem *grpc_chttp2_hptbl_lookup(const grpc_chttp2_hptbl *tbl, + gpr_uint32 index) { + /* Static table comes first, just return an entry from it */ + if (index <= GRPC_CHTTP2_LAST_STATIC_ENTRY) { + return tbl->static_ents[index - 1]; + } + /* Otherwise, find the value in the list of valid entries */ + index -= (GRPC_CHTTP2_LAST_STATIC_ENTRY + 1); + if (index < tbl->num_ents) { + gpr_uint32 offset = (tbl->num_ents - 1 - index + tbl->first_ent) % + GRPC_CHTTP2_MAX_TABLE_COUNT; + 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]; + tbl->mem_used -= GPR_SLICE_LENGTH(first_ent->key->slice) + + GPR_SLICE_LENGTH(first_ent->value->slice) + + GRPC_CHTTP2_HPACK_ENTRY_OVERHEAD; + tbl->first_ent = (tbl->first_ent + 1) % GRPC_CHTTP2_MAX_TABLE_COUNT; + tbl->num_ents--; + grpc_mdelem_unref(first_ent); +} + +void grpc_chttp2_hptbl_add(grpc_chttp2_hptbl *tbl, grpc_mdelem *md) { + /* determine how many bytes of buffer this entry represents */ + gpr_uint16 elem_bytes = GPR_SLICE_LENGTH(md->key->slice) + + GPR_SLICE_LENGTH(md->value->slice) + + GRPC_CHTTP2_HPACK_ENTRY_OVERHEAD; + + /* we can't add elements bigger than the max table size */ + assert(elem_bytes <= tbl->max_bytes); + + /* evict entries to ensure no overflow */ + while (elem_bytes > tbl->max_bytes - tbl->mem_used) { + evict1(tbl); + } + + /* copy the finalized entry in */ + tbl->ents[tbl->last_ent] = md; + + /* update accounting values */ + tbl->last_ent = (tbl->last_ent + 1) % GRPC_CHTTP2_MAX_TABLE_COUNT; + tbl->num_ents++; + tbl->mem_used += elem_bytes; +} + +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}; + int 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 + 1; + 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++) { + int idx = tbl->num_ents - i + GRPC_CHTTP2_LAST_STATIC_ENTRY; + grpc_mdelem *ent = + tbl->ents[(tbl->first_ent + i) % GRPC_CHTTP2_MAX_TABLE_COUNT]; + 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/transport/chttp2/hpack_table.h b/src/core/transport/chttp2/hpack_table.h new file mode 100644 index 0000000000..a3a07ad014 --- /dev/null +++ b/src/core/transport/chttp2/hpack_table.h @@ -0,0 +1,97 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_TRANSPORT_CHTTP2_HPACK_TABLE_H__ +#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_HPACK_TABLE_H__ + +#include "src/core/transport/metadata.h" +#include <grpc/support/port_platform.h> +#include <grpc/support/slice.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 +/* 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) + +/* hpack decoder table */ +typedef struct { + grpc_mdctx *mdctx; + /* the first used entry in ents */ + gpr_uint16 first_ent; + /* the last used entry in ents */ + gpr_uint16 last_ent; + /* how many entries are in the table */ + gpr_uint16 num_ents; + /* the amount of memory used by the table, according to the hpack algorithm */ + gpr_uint16 mem_used; + /* the max memory allowed to be used by the table, according to the hpack + algorithm */ + gpr_uint16 max_bytes; + /* 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_CHTTP2_MAX_TABLE_COUNT]; + 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, grpc_mdctx *mdctx); +void grpc_chttp2_hptbl_destroy(grpc_chttp2_hptbl *tbl); + +/* lookup a table entry based on its hpack index */ +grpc_mdelem *grpc_chttp2_hptbl_lookup(const grpc_chttp2_hptbl *tbl, + gpr_uint32 index); +/* add a table entry to the index */ +void grpc_chttp2_hptbl_add(grpc_chttp2_hptbl *tbl, grpc_mdelem *md); +/* 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 { + gpr_uint16 index; + gpr_uint8 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_INTERNAL_TRANSPORT_CHTTP2_HPACK_TABLE_H__ */ diff --git a/src/core/transport/chttp2/hpack_tables.txt b/src/core/transport/chttp2/hpack_tables.txt new file mode 100644 index 0000000000..08842a0267 --- /dev/null +++ b/src/core/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/transport/chttp2/http2_errors.h b/src/core/transport/chttp2/http2_errors.h new file mode 100644 index 0000000000..d065422c6f --- /dev/null +++ b/src/core/transport/chttp2/http2_errors.h @@ -0,0 +1,56 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_TRANSPORT_CHTTP2_HTTP2_ERRORS_H__ +#define __GRPC_INTERNAL_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_INTERNAL_TRANSPORT_CHTTP2_HTTP2_ERRORS_H__ */ diff --git a/src/core/transport/chttp2/status_conversion.c b/src/core/transport/chttp2/status_conversion.c new file mode 100644 index 0000000000..7bd85e8b29 --- /dev/null +++ b/src/core/transport/chttp2/status_conversion.c @@ -0,0 +1,109 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/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/transport/chttp2/status_conversion.h b/src/core/transport/chttp2/status_conversion.h new file mode 100644 index 0000000000..ae9e7f2ca3 --- /dev/null +++ b/src/core/transport/chttp2/status_conversion.h @@ -0,0 +1,50 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_TRANSPORT_CHTTP2_STATUS_CONVERSION_H__ +#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_STATUS_CONVERSION_H__ + +#include <grpc/grpc.h> +#include "src/core/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_INTERNAL_TRANSPORT_CHTTP2_STATUS_CONVERSION_H__ */ diff --git a/src/core/transport/chttp2/stream_encoder.c b/src/core/transport/chttp2/stream_encoder.c new file mode 100644 index 0000000000..d46366d8b2 --- /dev/null +++ b/src/core/transport/chttp2/stream_encoder.c @@ -0,0 +1,553 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/transport/chttp2/stream_encoder.h" + +#include <assert.h> +#include <string.h> + +#include <grpc/support/log.h> +#include <grpc/support/useful.h> +#include "src/core/transport/chttp2/hpack_table.h" +#include "src/core/transport/chttp2/timeout_encoding.h" +#include "src/core/transport/chttp2/varint.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 + +/* what kind of frame our we encoding? */ +typedef enum { HEADER, DATA, NONE } frame_type; + +typedef struct { + frame_type cur_frame_type; + /* 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; + /* was the last frame emitted a header? (if yes, we'll need a CONTINUATION */ + gpr_uint8 last_was_header; + /* output stream id */ + gpr_uint32 stream_id; + /* number of flow controlled bytes written */ + gpr_uint32 output_size; + 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(gpr_uint8 *p, gpr_uint8 type, gpr_uint32 id, + gpr_uint32 len, gpr_uint8 flags) { + *p++ = len >> 16; + *p++ = len >> 8; + *p++ = len; + *p++ = type; + *p++ = flags; + *p++ = id >> 24; + *p++ = id >> 16; + *p++ = id >> 8; + *p++ = 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) { + gpr_uint8 type = 0xff; + switch (st->cur_frame_type) { + case HEADER: + type = st->last_was_header ? GRPC_CHTTP2_FRAME_CONTINUATION + : GRPC_CHTTP2_FRAME_HEADER; + st->last_was_header = 1; + break; + case DATA: + type = GRPC_CHTTP2_FRAME_DATA; + st->last_was_header = 0; + is_header_boundary = 0; + break; + case NONE: + return; + } + 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, + (is_last_in_stream ? GRPC_CHTTP2_DATA_FLAG_END_STREAM : 0) | + (is_header_boundary ? GRPC_CHTTP2_DATA_FLAG_END_HEADERS : 0)); + st->cur_frame_type = NONE; +} + +/* begin a new frame: reserve off header space, remember how many bytes we'd + output before beginning */ +static void begin_frame(framer_state *st, frame_type type) { + GPR_ASSERT(type != NONE); + GPR_ASSERT(st->cur_frame_type == NONE); + st->cur_frame_type = type; + 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_frame_type(framer_state *st, frame_type type, + int need_bytes) { + if (st->cur_frame_type == type && + st->output->length - st->output_length_at_start_of_frame + need_bytes <= + GRPC_CHTTP2_MAX_PAYLOAD_LENGTH) { + return; + } + finish_frame(st, type != HEADER, 0); + begin_frame(st, type); +} + +/* increment a filter count, halve all counts if one element reaches max */ +static void inc_filter(gpr_uint8 idx, gpr_uint32 *sum, gpr_uint8 *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; + ensure_frame_type(st, HEADER, 1); + 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)); + add_header_data(st, slice); + } +} + +static gpr_uint8 *add_tiny_header_data(framer_state *st, int len) { + ensure_frame_type(st, HEADER, len); + return gpr_slice_buffer_tiny_add(st->output, len); +} + +static void add_elem(grpc_chttp2_hpack_compressor *c, grpc_mdelem *elem) { + gpr_uint32 key_hash = elem->key->hash; + gpr_uint32 elem_hash = key_hash ^ elem->value->hash; + gpr_uint32 new_index = c->tail_remote_index + c->table_elems + 1; + gpr_uint32 elem_size = 32 + GPR_SLICE_LENGTH(elem->key->slice) + + GPR_SLICE_LENGTH(elem->value->slice); + int drop_ref; + + /* Reserve space for this element in the remote table: if this overflows + the current table, drop elements until it fits, matching the decompressor + algorithm */ + /* TODO(ctiller): constant */ + while (c->table_size + elem_size > 4096) { + c->tail_remote_index++; + GPR_ASSERT(c->tail_remote_index > 0); + GPR_ASSERT(c->table_size >= + c->table_elem_size[c->tail_remote_index % + GRPC_CHTTP2_HPACKC_MAX_TABLE_ELEMS]); + GPR_ASSERT(c->table_elems > 0); + c->table_size -= c->table_elem_size[c->tail_remote_index % + GRPC_CHTTP2_HPACKC_MAX_TABLE_ELEMS]; + c->table_elems--; + } + GPR_ASSERT(c->table_elems < GRPC_CHTTP2_HPACKC_MAX_TABLE_ELEMS); + c->table_elem_size[new_index % GRPC_CHTTP2_HPACKC_MAX_TABLE_ELEMS] = + elem_size; + 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; + drop_ref = 1; + } 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; + drop_ref = 1; + } 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)] = elem; + c->indices_elems[HASH_FRAGMENT_2(elem_hash)] = new_index; + drop_ref = 0; + } 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)] = elem; + c->indices_elems[HASH_FRAGMENT_3(elem_hash)] = new_index; + drop_ref = 0; + } 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)] = elem; + c->indices_elems[HASH_FRAGMENT_2(elem_hash)] = new_index; + drop_ref = 0; + } else { + /* not there: replace oldest */ + grpc_mdelem_unref(c->entries_elems[HASH_FRAGMENT_3(elem_hash)]); + c->entries_elems[HASH_FRAGMENT_3(elem_hash)] = elem; + c->indices_elems[HASH_FRAGMENT_3(elem_hash)] = new_index; + drop_ref = 0; + } + + /* 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; + } + + if (drop_ref) { + grpc_mdelem_unref(elem); + } +} + +static void emit_indexed(grpc_chttp2_hpack_compressor *c, gpr_uint32 index, + framer_state *st) { + int len = GRPC_CHTTP2_VARINT_LENGTH(index, 1); + GRPC_CHTTP2_WRITE_VARINT(index, 1, 0x80, add_tiny_header_data(st, len), len); +} + +static void emit_lithdr_incidx(grpc_chttp2_hpack_compressor *c, + gpr_uint32 key_index, grpc_mdstr *value, + framer_state *st) { + int len_pfx = GRPC_CHTTP2_VARINT_LENGTH(key_index, 2); + int len_val = GPR_SLICE_LENGTH(value->slice); + int len_val_len = GRPC_CHTTP2_VARINT_LENGTH(len_val, 1); + GRPC_CHTTP2_WRITE_VARINT(key_index, 2, 0x40, + add_tiny_header_data(st, len_pfx), len_pfx); + GRPC_CHTTP2_WRITE_VARINT(len_val, 1, 0x00, + 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, + gpr_uint32 key_index, grpc_mdstr *value, + framer_state *st) { + int len_pfx = GRPC_CHTTP2_VARINT_LENGTH(key_index, 4); + int len_val = GPR_SLICE_LENGTH(value->slice); + int len_val_len = GRPC_CHTTP2_VARINT_LENGTH(len_val, 1); + GRPC_CHTTP2_WRITE_VARINT(key_index, 4, 0x00, + add_tiny_header_data(st, len_pfx), len_pfx); + GRPC_CHTTP2_WRITE_VARINT(len_val, 1, 0x00, + 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_mdstr *key, grpc_mdstr *value, + framer_state *st) { + int len_key = GPR_SLICE_LENGTH(key->slice); + int len_val = GPR_SLICE_LENGTH(value->slice); + int len_key_len = GRPC_CHTTP2_VARINT_LENGTH(len_key, 1); + int len_val_len = GRPC_CHTTP2_VARINT_LENGTH(len_val, 1); + *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(key->slice)); + GRPC_CHTTP2_WRITE_VARINT(len_val, 1, 0x00, + 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_mdstr *key, grpc_mdstr *value, + framer_state *st) { + int len_key = GPR_SLICE_LENGTH(key->slice); + int len_val = GPR_SLICE_LENGTH(value->slice); + int len_key_len = GRPC_CHTTP2_VARINT_LENGTH(len_key, 1); + int len_val_len = GRPC_CHTTP2_VARINT_LENGTH(len_val, 1); + *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(key->slice)); + GRPC_CHTTP2_WRITE_VARINT(len_val, 1, 0x00, + add_tiny_header_data(st, len_val_len), len_val_len); + add_header_data(st, gpr_slice_ref(value->slice)); +} + +static gpr_uint32 dynidx(grpc_chttp2_hpack_compressor *c, gpr_uint32 index) { + return 1 + GRPC_CHTTP2_LAST_STATIC_ENTRY + c->tail_remote_index + + c->table_elems - index; +} + +/* encode an mdelem, taking ownership of it */ +static void hpack_enc(grpc_chttp2_hpack_compressor *c, grpc_mdelem *elem, + framer_state *st) { + gpr_uint32 key_hash = elem->key->hash; + gpr_uint32 elem_hash = key_hash ^ elem->value->hash; + size_t decoder_space_usage; + int should_add_elem; + + 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); + grpc_mdelem_unref(elem); + 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); + grpc_mdelem_unref(elem); + 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? */ + + if (c->entries_keys[HASH_FRAGMENT_2(key_hash)] == elem->key && + c->indices_keys[HASH_FRAGMENT_2(key_hash)] > c->tail_remote_index) { + /* HIT: key (first cuckoo hash) */ + if (should_add_elem) { + emit_lithdr_incidx(c, + dynidx(c, c->indices_keys[HASH_FRAGMENT_2(key_hash)]), + elem->value, st); + add_elem(c, elem); + } else { + emit_lithdr_noidx(c, + dynidx(c, c->indices_keys[HASH_FRAGMENT_2(key_hash)]), + elem->value, st); + grpc_mdelem_unref(elem); + } + return; + } + + if (c->entries_keys[HASH_FRAGMENT_3(key_hash)] == elem->key && + c->indices_keys[HASH_FRAGMENT_3(key_hash)] > c->tail_remote_index) { + /* HIT: key (first cuckoo hash) */ + if (should_add_elem) { + emit_lithdr_incidx(c, + dynidx(c, c->indices_keys[HASH_FRAGMENT_3(key_hash)]), + elem->value, st); + add_elem(c, elem); + } else { + emit_lithdr_noidx(c, + dynidx(c, c->indices_keys[HASH_FRAGMENT_3(key_hash)]), + elem->value, st); + grpc_mdelem_unref(elem); + } + return; + } + + /* no elem, key in the table... fall back to literal emission */ + + if (should_add_elem) { + emit_lithdr_incidx_v(c, elem->key, elem->value, st); + add_elem(c, elem); + } else { + emit_lithdr_noidx_v(c, elem->key, elem->value, st); + grpc_mdelem_unref(elem); + } +} + +#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[32]; + grpc_chttp2_encode_timeout(gpr_time_sub(deadline, gpr_now()), timeout_str); + hpack_enc(c, grpc_mdelem_from_metadata_strings( + c->mdctx, grpc_mdstr_ref(c->timeout_key_str), + grpc_mdstr_from_string(c->mdctx, timeout_str)), + st); +} + +gpr_slice grpc_chttp2_data_frame_create_empty_close(gpr_uint32 id) { + gpr_slice slice = gpr_slice_malloc(9); + fill_header(GPR_SLICE_START_PTR(slice), GRPC_CHTTP2_FRAME_DATA, id, 0, 1); + return slice; +} + +void grpc_chttp2_hpack_compressor_init(grpc_chttp2_hpack_compressor *c, + grpc_mdctx *ctx) { + memset(c, 0, sizeof(*c)); + c->mdctx = ctx; + c->timeout_key_str = grpc_mdstr_from_string(ctx, "grpc-timeout"); +} + +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]); + } + grpc_mdstr_unref(c->timeout_key_str); +} + +gpr_uint32 grpc_chttp2_encode_some(grpc_stream_op *ops, size_t *ops_count, + int eof, gpr_slice_buffer *output, + gpr_uint32 max_bytes, gpr_uint32 stream_id, + grpc_chttp2_hpack_compressor *compressor) { + framer_state st; + gpr_slice slice; + grpc_stream_op *op; + gpr_uint32 max_take_size; + gpr_uint32 curop = 0; + gpr_uint32 nops = *ops_count; + gpr_uint8 *p; + + GPR_ASSERT(stream_id != 0); + + st.cur_frame_type = NONE; + st.last_was_header = 0; + st.stream_id = stream_id; + st.output = output; + st.output_size = 0; + + while (curop < nops) { + GPR_ASSERT(st.output_size <= max_bytes); + op = &ops[curop]; + switch (op->type) { + case GRPC_NO_OP: + curop++; + break; + case GRPC_OP_FLOW_CTL_CB: + op->data.flow_ctl_cb.cb(op->data.flow_ctl_cb.arg, GRPC_OP_OK); + curop++; + break; + case GRPC_OP_METADATA: + hpack_enc(compressor, op->data.metadata, &st); + curop++; + break; + case GRPC_OP_DEADLINE: + deadline_enc(compressor, op->data.deadline, &st); + curop++; + break; + case GRPC_OP_METADATA_BOUNDARY: + ensure_frame_type(&st, HEADER, 0); + finish_frame(&st, 1, 0); + st.last_was_header = 0; /* force a new header frame */ + curop++; + break; + case GRPC_OP_BEGIN_MESSAGE: + /* begin op: for now we just convert the op to a slice and fall + through - this lets us reuse the slice framing code below */ + slice = gpr_slice_malloc(5); + p = GPR_SLICE_START_PTR(slice); + p[0] = 0; + p[1] = op->data.begin_message.length >> 24; + p[2] = op->data.begin_message.length >> 16; + p[3] = op->data.begin_message.length >> 8; + p[4] = op->data.begin_message.length; + op->type = GRPC_OP_SLICE; + op->data.slice = slice; + /* fallthrough */ + case GRPC_OP_SLICE: + slice = op->data.slice; + if (!GPR_SLICE_LENGTH(slice)) { + curop++; + break; + } + if (st.output_size == max_bytes) { + goto exit_loop; + } + if (st.cur_frame_type == DATA && + st.output->length - st.output_length_at_start_of_frame == + GRPC_CHTTP2_MAX_PAYLOAD_LENGTH) { + finish_frame(&st, 0, 0); + } + ensure_frame_type(&st, DATA, 1); + max_take_size = + GPR_MIN(max_bytes - st.output_size, + GRPC_CHTTP2_MAX_PAYLOAD_LENGTH + + st.output_length_at_start_of_frame - st.output->length); + if (GPR_SLICE_LENGTH(slice) > max_take_size) { + slice = gpr_slice_split_head(&op->data.slice, max_take_size); + } else { + /* consume this op immediately */ + curop++; + } + st.output_size += GPR_SLICE_LENGTH(slice); + gpr_slice_buffer_add(output, slice); + break; + } + } +exit_loop: + if (eof && st.cur_frame_type == NONE) { + begin_frame(&st, DATA); + } + finish_frame(&st, 1, eof && curop == nops); + + nops -= curop; + *ops_count = nops; + memmove(ops, ops + curop, nops * sizeof(grpc_stream_op)); + + return st.output_size; +} diff --git a/src/core/transport/chttp2/stream_encoder.h b/src/core/transport/chttp2/stream_encoder.h new file mode 100644 index 0000000000..dad64697a5 --- /dev/null +++ b/src/core/transport/chttp2/stream_encoder.h @@ -0,0 +1,86 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_TRANSPORT_CHTTP2_STREAM_ENCODER_H__ +#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_STREAM_ENCODER_H__ + +#include "src/core/transport/chttp2/frame.h" +#include "src/core/transport/metadata.h" +#include "src/core/transport/stream_op.h" +#include <grpc/support/port_platform.h> +#include <grpc/support/slice.h> +#include <grpc/support/slice_buffer.h> + +#define GRPC_CHTTP2_HPACKC_NUM_FILTERS 256 +#define GRPC_CHTTP2_HPACKC_NUM_VALUES 256 +#define GRPC_CHTTP2_HPACKC_MAX_TABLE_ELEMS (4096 / 32) + +typedef struct { + gpr_uint32 filter_elems_sum; + /* one before the lowest usable table index */ + gpr_uint32 tail_remote_index; + gpr_uint16 table_size; + gpr_uint16 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. */ + gpr_uint8 filter_elems[GRPC_CHTTP2_HPACKC_NUM_FILTERS]; + + /* metadata context */ + grpc_mdctx *mdctx; + /* the string 'grpc-timeout' */ + grpc_mdstr *timeout_key_str; + + /* 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]; + gpr_uint32 indices_keys[GRPC_CHTTP2_HPACKC_NUM_VALUES]; + gpr_uint32 indices_elems[GRPC_CHTTP2_HPACKC_NUM_VALUES]; + + gpr_uint16 table_elem_size[GRPC_CHTTP2_HPACKC_MAX_TABLE_ELEMS]; +} grpc_chttp2_hpack_compressor; + +void grpc_chttp2_hpack_compressor_init(grpc_chttp2_hpack_compressor *c, + grpc_mdctx *mdctx); +void grpc_chttp2_hpack_compressor_destroy(grpc_chttp2_hpack_compressor *c); + +gpr_uint32 grpc_chttp2_encode_some(grpc_stream_op *ops, size_t *ops_count, + int eof, gpr_slice_buffer *output, + gpr_uint32 max_bytes, gpr_uint32 stream_id, + grpc_chttp2_hpack_compressor *compressor); + +#endif /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_STREAM_ENCODER_H__ */ diff --git a/src/core/transport/chttp2/stream_map.c b/src/core/transport/chttp2/stream_map.c new file mode 100644 index 0000000000..9ac3a4750d --- /dev/null +++ b/src/core/transport/chttp2/stream_map.c @@ -0,0 +1,154 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/transport/chttp2/stream_map.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.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(gpr_uint32) * 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(gpr_uint32 *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, gpr_uint32 key, + void *value) { + size_t count = map->count; + size_t capacity = map->capacity; + gpr_uint32 *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(gpr_uint32)); + map->values = values = gpr_realloc(values, capacity * sizeof(void *)); + } + } + + keys[count] = key; + values[count] = value; + map->count = count + 1; +} + +static void **find(grpc_chttp2_stream_map *map, gpr_uint32 key) { + size_t min_idx = 0; + size_t max_idx = map->count; + size_t mid_idx; + gpr_uint32 *keys = map->keys; + void **values = map->values; + gpr_uint32 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, + gpr_uint32 key) { + void **pvalue = find(map, key); + void *out = NULL; + if (pvalue != NULL) { + out = *pvalue; + *pvalue = NULL; + map->free += (out != NULL); + } + return out; +} + +void *grpc_chttp2_stream_map_find(grpc_chttp2_stream_map *map, gpr_uint32 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, gpr_uint32 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/transport/chttp2/stream_map.h b/src/core/transport/chttp2/stream_map.h new file mode 100644 index 0000000000..caaee30676 --- /dev/null +++ b/src/core/transport/chttp2/stream_map.h @@ -0,0 +1,81 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_TRANSPORT_CHTTP2_STREAM_MAP_H__ +#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_STREAM_MAP_H__ + +#include <grpc/support/port_platform.h> + +#include <stddef.h> + +/* Data structure to map a gpr_uint32 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 { + gpr_uint32 *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, gpr_uint32 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, + gpr_uint32 key); + +/* Return an existing key, or NULL if it does not exist */ +void *grpc_chttp2_stream_map_find(grpc_chttp2_stream_map *map, gpr_uint32 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, gpr_uint32 key, + void *value), + void *user_data); + +#endif /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_STREAM_MAP_H__ */ diff --git a/src/core/transport/chttp2/timeout_encoding.c b/src/core/transport/chttp2/timeout_encoding.c new file mode 100644 index 0000000000..2706c369a6 --- /dev/null +++ b/src/core/transport/chttp2/timeout_encoding.c @@ -0,0 +1,176 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/transport/chttp2/timeout_encoding.h" + +#include <stdio.h> +#include <string.h> + +static int round_up(int x, int divisor) { + return (x / divisor + (x % divisor != 0)) * divisor; +} + +/* round an integer up to the next value with three significant figures */ +static int round_up_to_three_sig_figs(int 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) { strcpy(buffer, "1n"); } + +static void enc_seconds(char *buffer, long sec) { + if (sec % 3600 == 0) { + sprintf(buffer, "%ldH", sec / 3600); + } else if (sec % 60 == 0) { + sprintf(buffer, "%ldM", sec / 60); + } else { + sprintf(buffer, "%ldS", sec); + } +} + +static void enc_nanos(char *buffer, int x) { + x = round_up_to_three_sig_figs(x); + if (x < 100000) { + if (x % 1000 == 0) { + sprintf(buffer, "%du", x / 1000); + } else { + sprintf(buffer, "%dn", x); + } + } else if (x < 100000000) { + if (x % 1000000 == 0) { + sprintf(buffer, "%dm", x / 1000000); + } else { + sprintf(buffer, "%du", x / 1000); + } + } else if (x < 1000000000) { + sprintf(buffer, "%dm", x / 1000000); + } 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) */ + strcpy(buffer, "1S"); + } +} + +static void enc_micros(char *buffer, int x) { + x = round_up_to_three_sig_figs(x); + if (x < 100000) { + if (x % 1000 == 0) { + sprintf(buffer, "%dm", x / 1000); + } else { + sprintf(buffer, "%du", x); + } + } else if (x < 100000000) { + if (x % 1000000 == 0) { + sprintf(buffer, "%dS", x / 1000000); + } else { + sprintf(buffer, "%dm", x / 1000); + } + } else { + sprintf(buffer, "%dS", x / 1000000); + } +} + +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, + 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) { + gpr_uint32 x = 0; + const char *p = buffer; + int have_digit = 0; + /* skip whitespace */ + for (; *p == ' '; p++) + ; + /* decode numeric part */ + for (; *p >= '0' && *p <= '9'; p++) { + gpr_uint32 xp = x * 10 + *p - '0'; + have_digit = 1; + if (xp < x) { + *timeout = gpr_inf_future; + return 1; + } + x = xp; + } + if (!have_digit) return 0; + /* skip whitespace */ + for (; *p == ' '; p++) + ; + /* decode unit specifier */ + switch (*p) { + case 'n': + *timeout = gpr_time_from_nanos(x); + break; + case 'u': + *timeout = gpr_time_from_micros(x); + break; + case 'm': + *timeout = gpr_time_from_millis(x); + break; + case 'S': + *timeout = gpr_time_from_seconds(x); + break; + case 'M': + *timeout = gpr_time_from_minutes(x); + break; + case 'H': + *timeout = gpr_time_from_hours(x); + break; + default: + return 0; + } + p++; + return is_all_whitespace(p); +} diff --git a/src/core/transport/chttp2/timeout_encoding.h b/src/core/transport/chttp2/timeout_encoding.h new file mode 100644 index 0000000000..a4582566ad --- /dev/null +++ b/src/core/transport/chttp2/timeout_encoding.h @@ -0,0 +1,44 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_TRANSPORT_CHTTP2_TIMEOUT_ENCODING_H_ +#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_TIMEOUT_ENCODING_H_ + +#include <grpc/support/time.h> + +/* 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_INTERNAL_TRANSPORT_CHTTP2_TIMEOUT_ENCODING_H_ */ diff --git a/src/core/transport/chttp2/varint.c b/src/core/transport/chttp2/varint.c new file mode 100644 index 0000000000..5d551be642 --- /dev/null +++ b/src/core/transport/chttp2/varint.c @@ -0,0 +1,65 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/transport/chttp2/varint.h" + +int grpc_chttp2_hpack_varint_length(gpr_uint32 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(gpr_uint32 tail_value, + gpr_uint8* target, int tail_length) { + switch (tail_length) { + case 5: + target[4] = (gpr_uint8)((tail_value >> 28) | 0x80); + case 4: + target[3] = (gpr_uint8)((tail_value >> 21) | 0x80); + case 3: + target[2] = (gpr_uint8)((tail_value >> 14) | 0x80); + case 2: + target[1] = (gpr_uint8)((tail_value >> 7) | 0x80); + case 1: + target[0] = (gpr_uint8)((tail_value) | 0x80); + } + target[tail_length - 1] &= 0x7f; +} diff --git a/src/core/transport/chttp2/varint.h b/src/core/transport/chttp2/varint.h new file mode 100644 index 0000000000..780390238f --- /dev/null +++ b/src/core/transport/chttp2/varint.h @@ -0,0 +1,73 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_TRANSPORT_CHTTP2_VARINT_H__ +#define __GRPC_INTERNAL_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 */ +int grpc_chttp2_hpack_varint_length(gpr_uint32 tail_value); + +void grpc_chttp2_hpack_write_varint_tail(gpr_uint32 tail_value, + gpr_uint8* target, int 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) ((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) \ + ? 1 \ + : 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 { \ + gpr_uint8* tgt = target; \ + if ((length) == 1) { \ + (tgt)[0] = (prefix_or) | (n); \ + } else { \ + (tgt)[0] = (prefix_or) | 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_INTERNAL_TRANSPORT_CHTTP2_VARINT_H__ */ diff --git a/src/core/transport/chttp2_transport.c b/src/core/transport/chttp2_transport.c new file mode 100644 index 0000000000..8a6b427559 --- /dev/null +++ b/src/core/transport/chttp2_transport.c @@ -0,0 +1,1615 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/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.h> +#include <grpc/support/useful.h> +#include "src/core/transport/transport_impl.h" +#include "src/core/transport/chttp2/http2_errors.h" +#include "src/core/transport/chttp2/hpack_parser.h" +#include "src/core/transport/chttp2/frame_data.h" +#include "src/core/transport/chttp2/frame_ping.h" +#include "src/core/transport/chttp2/frame_rst_stream.h" +#include "src/core/transport/chttp2/frame_settings.h" +#include "src/core/transport/chttp2/frame_window_update.h" +#include "src/core/transport/chttp2/status_conversion.h" +#include "src/core/transport/chttp2/stream_encoder.h" +#include "src/core/transport/chttp2/stream_map.h" +#include "src/core/transport/chttp2/timeout_encoding.h" + +#define DEFAULT_WINDOW 65536 +#define MAX_WINDOW 0x7fffffffu + +#define CLIENT_CONNECT_STRING "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" +#define CLIENT_CONNECT_STRLEN 24 + +typedef struct transport transport; +typedef struct stream stream; + +/* streams are kept in various linked lists depending on what things need to + happen to them... this enum labels each list */ +typedef enum { + /* streams that have pending writes */ + WRITABLE = 0, + /* streams that want to send window updates */ + WINDOW_UPDATE, + /* streams that are waiting to start because there are too many concurrent + streams on the connection */ + WAITING_FOR_CONCURRENCY, + /* streams that want to callback the application */ + PENDING_CALLBACKS, + /* streams that *ARE* calling back to the application */ + EXECUTING_CALLBACKS, + STREAM_LIST_COUNT /* must be last */ +} stream_list_id; + +/* deframer state for the overall http2 stream of bytes */ +typedef enum { + /* prefix: one entry per http2 connection prefix byte */ + DTS_CLIENT_PREFIX_0 = 0, + DTS_CLIENT_PREFIX_1, + DTS_CLIENT_PREFIX_2, + DTS_CLIENT_PREFIX_3, + DTS_CLIENT_PREFIX_4, + DTS_CLIENT_PREFIX_5, + DTS_CLIENT_PREFIX_6, + DTS_CLIENT_PREFIX_7, + DTS_CLIENT_PREFIX_8, + DTS_CLIENT_PREFIX_9, + DTS_CLIENT_PREFIX_10, + DTS_CLIENT_PREFIX_11, + DTS_CLIENT_PREFIX_12, + DTS_CLIENT_PREFIX_13, + DTS_CLIENT_PREFIX_14, + DTS_CLIENT_PREFIX_15, + DTS_CLIENT_PREFIX_16, + DTS_CLIENT_PREFIX_17, + DTS_CLIENT_PREFIX_18, + DTS_CLIENT_PREFIX_19, + DTS_CLIENT_PREFIX_20, + DTS_CLIENT_PREFIX_21, + DTS_CLIENT_PREFIX_22, + DTS_CLIENT_PREFIX_23, + /* frame header byte 0... */ + /* must follow from the prefix states */ + DTS_FH_0, + DTS_FH_1, + DTS_FH_2, + DTS_FH_3, + DTS_FH_4, + DTS_FH_5, + DTS_FH_6, + DTS_FH_7, + /* ... frame header byte 8 */ + DTS_FH_8, + /* inside a http2 frame */ + DTS_FRAME +} deframe_transport_state; + +typedef struct { + stream *head; + stream *tail; +} stream_list; + +typedef struct { + stream *next; + stream *prev; +} stream_link; + +typedef enum { + ERROR_STATE_NONE, + ERROR_STATE_SEEN, + ERROR_STATE_NOTIFIED +} error_state; + +/* We keep several sets of connection wide parameters */ +typedef enum { + /* The settings our peer has asked for (and we have acked) */ + PEER_SETTINGS = 0, + /* The settings we'd like to have */ + LOCAL_SETTINGS, + /* The settings we've published to our peer */ + SENT_SETTINGS, + /* The settings the peer has acked */ + ACKED_SETTINGS, + NUM_SETTING_SETS +} setting_set; + +/* Outstanding ping request data */ +typedef struct { + gpr_uint8 id[8]; + void (*cb)(void *user_data); + void *user_data; +} outstanding_ping; + +struct transport { + grpc_transport base; /* must be first */ + const grpc_transport_callbacks *cb; + void *cb_user_data; + grpc_endpoint *ep; + grpc_mdctx *metadata_context; + gpr_refcount refs; + gpr_uint8 is_client; + + gpr_mu mu; + gpr_cv cv; + + /* basic state management - what are we doing at the moment? */ + gpr_uint8 reading; + gpr_uint8 writing; + gpr_uint8 calling_back; + error_state error_state; + + /* stream indexing */ + gpr_uint32 next_stream_id; + + /* settings */ + gpr_uint32 settings[NUM_SETTING_SETS][GRPC_CHTTP2_NUM_SETTINGS]; + gpr_uint8 sent_local_settings; + gpr_uint8 dirtied_local_settings; + + /* window management */ + gpr_uint32 outgoing_window; + gpr_uint32 incoming_window; + + /* deframing */ + deframe_transport_state deframe_state; + gpr_uint8 incoming_frame_type; + gpr_uint8 incoming_frame_flags; + gpr_uint8 header_eof; + gpr_uint32 expect_continuation_stream_id; + gpr_uint32 incoming_frame_size; + gpr_uint32 incoming_stream_id; + + /* hpack encoding */ + grpc_chttp2_hpack_compressor hpack_compressor; + + /* various parsers */ + 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; + } simple_parsers; + + /* state for a stream that's not yet been created */ + grpc_stream_op_buffer new_stream_sopb; + + /* active parser */ + void *parser_data; + stream *incoming_stream; + grpc_chttp2_parse_error (*parser)(void *parser_user_data, + grpc_chttp2_parse_state *state, + gpr_slice slice, int is_last); + + gpr_slice_buffer outbuf; + gpr_slice_buffer qbuf; + + stream_list lists[STREAM_LIST_COUNT]; + grpc_chttp2_stream_map stream_map; + + /* metadata object cache */ + grpc_mdstr *str_grpc_timeout; + + /* pings */ + outstanding_ping *pings; + size_t ping_count; + size_t ping_capacity; + gpr_int64 ping_counter; +}; + +struct stream { + gpr_uint32 id; + + gpr_uint32 outgoing_window; + gpr_uint32 incoming_window; + gpr_uint8 write_closed; + gpr_uint8 read_closed; + gpr_uint8 cancelled; + gpr_uint8 allow_window_updates; + gpr_uint8 published_close; + + stream_link links[STREAM_LIST_COUNT]; + gpr_uint8 included[STREAM_LIST_COUNT]; + + grpc_stream_op_buffer outgoing_sopb; + + grpc_chttp2_data_parser parser; + + grpc_stream_state callback_state; + grpc_stream_op_buffer callback_sopb; +}; + +static const grpc_transport_vtable vtable; + +static void push_setting(transport *t, grpc_chttp2_setting_id id, + gpr_uint32 value); + +static int prepare_callbacks(transport *t); +static void run_callbacks(transport *t); + +static int prepare_write(transport *t); +static void finish_write(void *t, grpc_endpoint_cb_status status); + +static void lock(transport *t); +static void unlock(transport *t); + +static void drop_connection(transport *t); +static void end_all_the_calls(transport *t); + +static stream *stream_list_remove_head(transport *t, stream_list_id id); +static void stream_list_remove(transport *t, stream *s, stream_list_id id); +static void stream_list_add_tail(transport *t, stream *s, stream_list_id id); +static void stream_list_join(transport *t, stream *s, stream_list_id id); + +static void cancel_stream_id(transport *t, gpr_uint32 id, + grpc_status_code local_status, + grpc_chttp2_error_code error_code, int send_rst); +static void cancel_stream(transport *t, stream *s, + grpc_status_code local_status, + grpc_chttp2_error_code error_code, int send_rst); +static stream *lookup_stream(transport *t, gpr_uint32 id); +static void remove_from_stream_map(transport *t, stream *s); +static void maybe_start_some_streams(transport *t); + +static void become_skip_parser(transport *t); + +/* + * CONSTRUCTION/DESTRUCTION/REFCOUNTING + */ + +static void unref_transport(transport *t) { + size_t i; + + if (!gpr_unref(&t->refs)) return; + + gpr_mu_lock(&t->mu); + + GPR_ASSERT(t->ep == NULL); + + gpr_slice_buffer_destroy(&t->outbuf); + gpr_slice_buffer_destroy(&t->qbuf); + grpc_chttp2_hpack_parser_destroy(&t->hpack_parser); + grpc_chttp2_hpack_compressor_destroy(&t->hpack_compressor); + + grpc_mdstr_unref(t->str_grpc_timeout); + + 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->stream_map) == 0); + + grpc_chttp2_stream_map_destroy(&t->stream_map); + + 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 */ + for (i = 0; i < t->ping_count; i++) { + t->pings[i].cb(t->pings[i].user_data); + } + gpr_free(t->pings); + + gpr_free(t); +} + +static void ref_transport(transport *t) { gpr_ref(&t->refs); } + +static void init_transport(transport *t, grpc_transport_setup_callback setup, + void *arg, const grpc_channel_args *channel_args, + grpc_endpoint *ep, grpc_mdctx *mdctx, + int is_client) { + size_t i; + int j; + grpc_transport_setup_result sr; + + GPR_ASSERT(strlen(CLIENT_CONNECT_STRING) == CLIENT_CONNECT_STRLEN); + + 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); + gpr_mu_init(&t->mu); + gpr_cv_init(&t->cv); + t->metadata_context = mdctx; + t->str_grpc_timeout = + grpc_mdstr_from_string(t->metadata_context, "grpc-timeout"); + t->reading = 1; + t->writing = 0; + t->error_state = ERROR_STATE_NONE; + t->next_stream_id = is_client ? 1 : 2; + t->is_client = is_client; + t->outgoing_window = DEFAULT_WINDOW; + t->incoming_window = DEFAULT_WINDOW; + t->deframe_state = is_client ? DTS_FH_0 : DTS_CLIENT_PREFIX_0; + t->expect_continuation_stream_id = 0; + t->pings = NULL; + t->ping_count = 0; + t->ping_capacity = 0; + t->ping_counter = gpr_now().tv_nsec; + grpc_chttp2_hpack_compressor_init(&t->hpack_compressor, mdctx); + gpr_slice_buffer_init(&t->outbuf); + gpr_slice_buffer_init(&t->qbuf); + if (is_client) { + gpr_slice_buffer_add(&t->qbuf, + gpr_slice_from_copied_string(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->stream_map, 8); + memset(&t->lists, 0, sizeof(t->lists)); + + /* copy in initial settings to all setting sets */ + for (i = 0; i < NUM_SETTING_SETS; i++) { + for (j = 0; j < GRPC_CHTTP2_NUM_SETTINGS; j++) { + t->settings[i][j] = grpc_chttp2_settings_parameters[j].default_value; + } + } + t->dirtied_local_settings = 1; + t->sent_local_settings = 0; + + /* configure http2 the way we like it */ + if (t->is_client) { + push_setting(t, GRPC_CHTTP2_SETTINGS_ENABLE_PUSH, 0); + push_setting(t, GRPC_CHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 0); + } + + 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 (t->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, + channel_args->args[i].value.integer); + } + } + } + } + + gpr_mu_lock(&t->mu); + t->calling_back = 1; + ref_transport(t); + gpr_mu_unlock(&t->mu); + + sr = setup(arg, &t->base, t->metadata_context); + + lock(t); + t->cb = sr.callbacks; + t->cb_user_data = sr.user_data; + grpc_chttp2_hpack_parser_init(&t->hpack_parser, t->metadata_context); + t->calling_back = 0; + gpr_cv_broadcast(&t->cv); + unlock(t); + unref_transport(t); +} + +static void destroy_transport(grpc_transport *gt) { + transport *t = (transport *)gt; + + gpr_mu_lock(&t->mu); + while (t->calling_back) { + gpr_cv_wait(&t->cv, &t->mu, gpr_inf_future); + } + t->cb = NULL; + gpr_mu_unlock(&t->mu); + + unref_transport(t); +} + +static void close_transport(grpc_transport *gt) { + transport *t = (transport *)gt; + gpr_mu_lock(&t->mu); + if (t->ep) { + grpc_endpoint_shutdown(t->ep); + } + gpr_mu_unlock(&t->mu); +} + +static int init_stream(grpc_transport *gt, grpc_stream *gs, + const void *server_data) { + transport *t = (transport *)gt; + stream *s = (stream *)gs; + + ref_transport(t); + + if (!server_data) { + lock(t); + s->id = 0; + } else { + s->id = (gpr_uint32)(gpr_uintptr)server_data; + t->incoming_stream = s; + grpc_chttp2_stream_map_add(&t->stream_map, s->id, s); + } + + s->outgoing_window = DEFAULT_WINDOW; + s->incoming_window = DEFAULT_WINDOW; + s->write_closed = 0; + s->read_closed = 0; + s->cancelled = 0; + s->allow_window_updates = 0; + s->published_close = 0; + memset(&s->links, 0, sizeof(s->links)); + memset(&s->included, 0, sizeof(s->included)); + grpc_sopb_init(&s->outgoing_sopb); + grpc_chttp2_data_parser_init(&s->parser); + grpc_sopb_init(&s->callback_sopb); + + if (!server_data) { + unlock(t); + } + + return 0; +} + +static void destroy_stream(grpc_transport *gt, grpc_stream *gs) { + transport *t = (transport *)gt; + stream *s = (stream *)gs; + size_t i; + + gpr_mu_lock(&t->mu); + + /* await pending callbacks + TODO(ctiller): this could be optimized to check if this stream is getting + callbacks */ + while (t->calling_back) { + gpr_cv_wait(&t->cv, &t->mu, gpr_inf_future); + } + + /* stop parsing if we're currently parsing this stream */ + if (t->deframe_state == DTS_FRAME && t->incoming_stream_id == s->id && + s->id != 0) { + become_skip_parser(t); + } + + for (i = 0; i < STREAM_LIST_COUNT; i++) { + stream_list_remove(t, s, i); + } + remove_from_stream_map(t, s); + + gpr_cv_broadcast(&t->cv); + gpr_mu_unlock(&t->mu); + + grpc_sopb_destroy(&s->outgoing_sopb); + grpc_chttp2_data_parser_destroy(&s->parser); + grpc_sopb_destroy(&s->callback_sopb); + + unref_transport(t); +} + +/* + * LIST MANAGEMENT + */ + +static stream *stream_list_remove_head(transport *t, stream_list_id id) { + stream *s = t->lists[id].head; + if (s) { + 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; + } + return s; +} + +static void stream_list_remove(transport *t, stream *s, stream_list_id id) { + if (!s->included[id]) return; + 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 void stream_list_add_tail(transport *t, stream *s, stream_list_id id) { + 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 { + s->links[id].prev = NULL; + t->lists[id].head = s; + } + t->lists[id].tail = s; + s->included[id] = 1; +} + +static void stream_list_join(transport *t, stream *s, stream_list_id id) { + if (s->included[id]) { + return; + } + stream_list_add_tail(t, s, id); +} + +static void remove_from_stream_map(transport *t, stream *s) { + if (s->id == 0) return; + if (grpc_chttp2_stream_map_delete(&t->stream_map, s->id)) { + maybe_start_some_streams(t); + } +} + +/* + * LOCK MANAGEMENT + */ + +/* We take a 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(transport *t) { gpr_mu_lock(&t->mu); } + +static void unlock(transport *t) { + int start_write = 0; + int perform_callbacks = 0; + int call_closed = 0; + grpc_endpoint *ep = t->ep; + + /* see if we need to trigger a write - and if so, get the data ready */ + if (ep && !t->writing) { + t->writing = start_write = prepare_write(t); + if (start_write) { + ref_transport(t); + } + } + + /* gather any callbacks that need to be made */ + if (!t->calling_back && t->cb) { + perform_callbacks = prepare_callbacks(t); + if (perform_callbacks) { + t->calling_back = 1; + } + if (t->error_state == ERROR_STATE_SEEN) { + call_closed = 1; + t->calling_back = 1; + t->error_state = ERROR_STATE_NOTIFIED; + } + } + + if (perform_callbacks || call_closed) { + ref_transport(t); + } + + /* finally unlock */ + gpr_mu_unlock(&t->mu); + + /* perform some callbacks if necessary */ + if (perform_callbacks) { + run_callbacks(t); + } + + if (call_closed) { + t->cb->closed(t->cb_user_data, &t->base); + } + + /* write some bytes if necessary */ + while (start_write) { + switch (grpc_endpoint_write(ep, t->outbuf.slices, t->outbuf.count, + finish_write, t, gpr_inf_future)) { + case GRPC_ENDPOINT_WRITE_DONE: + /* grab the lock directly without wrappers since we just want to + continue writes if we loop: no need to check read callbacks again */ + gpr_mu_lock(&t->mu); + t->outbuf.count = 0; + t->outbuf.length = 0; + t->writing = start_write = prepare_write(t); + if (!start_write) { + if (!t->reading) { + grpc_endpoint_destroy(t->ep); + t->ep = NULL; + gpr_cv_broadcast(&t->cv); + /* endpoint ref: safe because we'll still have the ref for write */ + unref_transport(t); + } + } + gpr_mu_unlock(&t->mu); + if (!start_write) { + unref_transport(t); + } + break; + case GRPC_ENDPOINT_WRITE_ERROR: + start_write = 0; + /* use the wrapper lock/unlock here as we drop_connection, causing + read callbacks to be queued (which will be cleared during unlock) */ + lock(t); + t->outbuf.count = 0; + t->outbuf.length = 0; + t->writing = 0; + drop_connection(t); + if (!t->reading) { + grpc_endpoint_destroy(t->ep); + t->ep = NULL; + gpr_cv_broadcast(&t->cv); + /* endpoint ref: safe because we'll still have the ref for write */ + unref_transport(t); + } + unlock(t); + unref_transport(t); + break; + case GRPC_ENDPOINT_WRITE_PENDING: + start_write = 0; + break; + } + } + + if (perform_callbacks || call_closed) { + lock(t); + t->calling_back = 0; + gpr_cv_broadcast(&t->cv); + unlock(t); + unref_transport(t); + } +} + +/* + * OUTPUT PROCESSING + */ + +static void push_setting(transport *t, grpc_chttp2_setting_id id, + gpr_uint32 value) { + const grpc_chttp2_setting_parameters *sp = + &grpc_chttp2_settings_parameters[id]; + gpr_uint32 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->settings[LOCAL_SETTINGS][id]) { + t->settings[LOCAL_SETTINGS][id] = use_value; + t->dirtied_local_settings = 1; + } +} + +static void finish_write(void *tp, grpc_endpoint_cb_status error) { + transport *t = tp; + + lock(t); + if (error != GRPC_ENDPOINT_CB_OK) { + drop_connection(t); + } + t->outbuf.count = 0; + t->outbuf.length = 0; + /* leave the writing flag up on shutdown to prevent further writes in unlock() + from starting */ + t->writing = 0; + if (!t->reading) { + grpc_endpoint_destroy(t->ep); + t->ep = NULL; + gpr_cv_broadcast(&t->cv); + unref_transport(t); /* safe because we'll still have the ref for write */ + } + unlock(t); + + unref_transport(t); +} + +static int prepare_write(transport *t) { + stream *s; + gpr_slice_buffer tempbuf; + + /* simple writes are queued to qbuf, and flushed here */ + tempbuf = t->qbuf; + t->qbuf = t->outbuf; + t->outbuf = tempbuf; + GPR_ASSERT(t->qbuf.count == 0); + + if (t->dirtied_local_settings && !t->sent_local_settings) { + gpr_slice_buffer_add( + &t->outbuf, grpc_chttp2_settings_create(t->settings[SENT_SETTINGS], + t->settings[LOCAL_SETTINGS], + GRPC_CHTTP2_NUM_SETTINGS)); + t->dirtied_local_settings = 0; + t->sent_local_settings = 1; + } + + /* for each stream that's become writable, frame it's data (according to + available window sizes) and add to the output buffer */ + while (t->outgoing_window && (s = stream_list_remove_head(t, WRITABLE))) { + gpr_uint32 written = grpc_chttp2_encode_some( + s->outgoing_sopb.ops, &s->outgoing_sopb.nops, s->write_closed, + &t->outbuf, GPR_MIN(t->outgoing_window, s->outgoing_window), s->id, + &t->hpack_compressor); + t->outgoing_window -= written; + s->outgoing_window -= written; + + /* if there are no more writes to do and writes are closed, we need to + queue a callback to let the application know */ + if (s->write_closed && s->outgoing_sopb.nops == 0) { + stream_list_join(t, s, PENDING_CALLBACKS); + } + + /* if there are still writes to do and the stream still has window + available, then schedule a further write */ + if (s->outgoing_sopb.nops && s->outgoing_window) { + GPR_ASSERT(!t->outgoing_window); + stream_list_add_tail(t, s, WRITABLE); + } + } + + /* for each stream that wants to update its window, add that window here */ + while ((s = stream_list_remove_head(t, WINDOW_UPDATE))) { + gpr_uint32 window_add = DEFAULT_WINDOW - s->incoming_window; + if (!s->read_closed && window_add) { + gpr_slice_buffer_add(&t->outbuf, + grpc_chttp2_window_update_create(s->id, window_add)); + s->incoming_window += window_add; + } + } + + /* if the transport is ready to send a window update, do so here also */ + if (t->incoming_window < DEFAULT_WINDOW / 2) { + gpr_uint32 window_add = DEFAULT_WINDOW - t->incoming_window; + gpr_slice_buffer_add(&t->outbuf, + grpc_chttp2_window_update_create(0, window_add)); + t->incoming_window += window_add; + } + + return t->outbuf.length > 0; +} + +static void maybe_start_some_streams(transport *t) { + while ( + grpc_chttp2_stream_map_size(&t->stream_map) < + t->settings[PEER_SETTINGS][GRPC_CHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS]) { + stream *s = stream_list_remove_head(t, WAITING_FOR_CONCURRENCY); + if (!s) break; + + GPR_ASSERT(s->id == 0); + s->id = t->next_stream_id; + t->next_stream_id += 2; + grpc_chttp2_stream_map_add(&t->stream_map, s->id, s); + stream_list_join(t, s, WRITABLE); + } +} + +static void send_batch(grpc_transport *gt, grpc_stream *gs, grpc_stream_op *ops, + size_t ops_count, int is_last) { + transport *t = (transport *)gt; + stream *s = (stream *)gs; + + lock(t); + + if (is_last) { + s->write_closed = 1; + } + if (!s->cancelled) { + grpc_sopb_append(&s->outgoing_sopb, ops, ops_count); + if (is_last && s->outgoing_sopb.nops == 0) { + if (s->id != 0) { + gpr_slice_buffer_add(&t->qbuf, + grpc_chttp2_data_frame_create_empty_close(s->id)); + } + } else if (s->id == 0) { + stream_list_join(t, s, WAITING_FOR_CONCURRENCY); + maybe_start_some_streams(t); + } else if (s->outgoing_window) { + stream_list_join(t, s, WRITABLE); + } + } else { + grpc_stream_ops_unref_owned_objects(ops, ops_count); + } + if (is_last && s->outgoing_sopb.nops == 0 && s->read_closed) { + stream_list_join(t, s, PENDING_CALLBACKS); + } + + unlock(t); +} + +static void abort_stream(grpc_transport *gt, grpc_stream *gs, + grpc_status_code status) { + transport *t = (transport *)gt; + stream *s = (stream *)gs; + + lock(t); + cancel_stream(t, s, status, grpc_chttp2_grpc_status_to_http2_error(status), + 1); + unlock(t); +} + +static void send_ping(grpc_transport *gt, void (*cb)(void *user_data), + void *user_data) { + transport *t = (transport *)gt; + outstanding_ping *p; + + lock(t); + if (t->ping_capacity == t->ping_count) { + t->ping_capacity = GPR_MAX(1, t->ping_capacity * 3 / 2); + t->pings = + gpr_realloc(t->pings, sizeof(outstanding_ping) * t->ping_capacity); + } + p = &t->pings[t->ping_count++]; + p->id[0] = t->ping_counter >> 56; + p->id[1] = t->ping_counter >> 48; + p->id[2] = t->ping_counter >> 40; + p->id[3] = t->ping_counter >> 32; + p->id[4] = t->ping_counter >> 24; + p->id[5] = t->ping_counter >> 16; + p->id[6] = t->ping_counter >> 8; + p->id[7] = t->ping_counter; + p->cb = cb; + p->user_data = user_data; + gpr_slice_buffer_add(&t->qbuf, grpc_chttp2_ping_create(0, p->id)); + unlock(t); +} + +/* + * INPUT PROCESSING + */ + +static void cancel_stream_inner(transport *t, stream *s, gpr_uint32 id, + grpc_status_code local_status, + grpc_chttp2_error_code error_code, + int send_rst) { + char buffer[32]; + int had_outgoing; + + if (s) { + /* clear out any unreported input & output: nobody cares anymore */ + grpc_sopb_reset(&s->parser.incoming_sopb); + had_outgoing = s->outgoing_sopb.nops != 0; + grpc_sopb_reset(&s->outgoing_sopb); + if (s->cancelled) { + send_rst = 0; + } else if (!s->read_closed || !s->write_closed || had_outgoing) { + s->cancelled = 1; + s->read_closed = 1; + s->write_closed = 1; + + sprintf(buffer, "%d", local_status); + grpc_sopb_add_metadata( + &s->parser.incoming_sopb, + grpc_mdelem_from_strings(t->metadata_context, "grpc-status", buffer)); + + stream_list_join(t, s, PENDING_CALLBACKS); + } + } + if (!id) send_rst = 0; + if (send_rst) { + gpr_slice_buffer_add(&t->qbuf, + grpc_chttp2_rst_stream_create(id, error_code)); + } +} + +static void cancel_stream_id(transport *t, gpr_uint32 id, + grpc_status_code local_status, + grpc_chttp2_error_code error_code, int send_rst) { + cancel_stream_inner(t, lookup_stream(t, id), id, local_status, error_code, + send_rst); +} + +static void cancel_stream(transport *t, stream *s, + grpc_status_code local_status, + grpc_chttp2_error_code error_code, int send_rst) { + cancel_stream_inner(t, s, s->id, local_status, error_code, send_rst); +} + +static void cancel_stream_cb(void *user_data, gpr_uint32 id, void *stream) { + cancel_stream(user_data, stream, GRPC_STATUS_UNAVAILABLE, + GRPC_CHTTP2_INTERNAL_ERROR, 0); +} + +static void end_all_the_calls(transport *t) { + grpc_chttp2_stream_map_for_each(&t->stream_map, cancel_stream_cb, t); +} + +static void drop_connection(transport *t) { + if (t->error_state == ERROR_STATE_NONE) { + t->error_state = ERROR_STATE_SEEN; + } + end_all_the_calls(t); +} + +static void maybe_join_window_updates(transport *t, stream *s) { + if (s->allow_window_updates && s->incoming_window < DEFAULT_WINDOW / 2) { + stream_list_join(t, s, WINDOW_UPDATE); + } +} + +static void set_allow_window_updates(grpc_transport *tp, grpc_stream *sp, + int allow) { + transport *t = (transport *)tp; + stream *s = (stream *)sp; + + lock(t); + s->allow_window_updates = allow; + if (allow) { + maybe_join_window_updates(t, s); + } else { + stream_list_remove(t, s, WINDOW_UPDATE); + } + unlock(t); +} + +static grpc_chttp2_parse_error update_incoming_window(transport *t, stream *s) { + if (t->incoming_frame_size > t->incoming_window) { + gpr_log(GPR_ERROR, "frame of size %d overflows incoming window of %d", + t->incoming_frame_size, t->incoming_window); + return GRPC_CHTTP2_CONNECTION_ERROR; + } + + if (t->incoming_frame_size > s->incoming_window) { + gpr_log(GPR_ERROR, "frame of size %d overflows incoming window of %d", + t->incoming_frame_size, s->incoming_window); + return GRPC_CHTTP2_CONNECTION_ERROR; + } + + t->incoming_window -= t->incoming_frame_size; + s->incoming_window -= t->incoming_frame_size; + + /* if the stream incoming window is getting low, schedule an update */ + maybe_join_window_updates(t, s); + + return GRPC_CHTTP2_PARSE_OK; +} + +static stream *lookup_stream(transport *t, gpr_uint32 id) { + return grpc_chttp2_stream_map_find(&t->stream_map, id); +} + +static grpc_chttp2_parse_error skip_parser(void *parser, + grpc_chttp2_parse_state *st, + 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(transport *t, int is_header) { + if (is_header) { + int is_eoh = t->expect_continuation_stream_id != 0; + t->parser = grpc_chttp2_header_parser_parse; + t->parser_data = &t->hpack_parser; + t->hpack_parser.on_header = skip_header; + t->hpack_parser.on_header_user_data = NULL; + t->hpack_parser.is_boundary = is_eoh; + t->hpack_parser.is_eof = is_eoh ? t->header_eof : 0; + } else { + t->parser = skip_parser; + } + return 1; +} + +static void become_skip_parser(transport *t) { + init_skip_frame(t, t->parser == grpc_chttp2_header_parser_parse); +} + +static int init_data_frame_parser(transport *t) { + stream *s = lookup_stream(t, t->incoming_stream_id); + grpc_chttp2_parse_error err = GRPC_CHTTP2_PARSE_OK; + if (!s || s->read_closed) return init_skip_frame(t, 0); + if (err == GRPC_CHTTP2_PARSE_OK) { + err = update_incoming_window(t, s); + } + if (err == GRPC_CHTTP2_PARSE_OK) { + err = grpc_chttp2_data_parser_begin_frame(&s->parser, + t->incoming_frame_flags); + } + switch (err) { + case GRPC_CHTTP2_PARSE_OK: + t->incoming_stream = s; + t->parser = grpc_chttp2_data_parser_parse; + t->parser_data = &s->parser; + return 1; + case GRPC_CHTTP2_STREAM_ERROR: + cancel_stream(t, s, grpc_chttp2_http2_error_to_grpc_status( + GRPC_CHTTP2_INTERNAL_ERROR), + GRPC_CHTTP2_INTERNAL_ERROR, 1); + return init_skip_frame(t, 0); + case GRPC_CHTTP2_CONNECTION_ERROR: + drop_connection(t); + return 0; + } + gpr_log(GPR_ERROR, "should never reach here"); + abort(); + return 0; +} + +static void free_timeout(void *p) { gpr_free(p); } + +static void on_header(void *tp, grpc_mdelem *md) { + transport *t = tp; + stream *s = t->incoming_stream; + + GPR_ASSERT(s); + stream_list_join(t, s, PENDING_CALLBACKS); + if (md->key == t->str_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; + } + grpc_mdelem_set_user_data(md, free_timeout, cached_timeout); + } + grpc_sopb_add_deadline(&s->parser.incoming_sopb, + gpr_time_add(gpr_now(), *cached_timeout)); + grpc_mdelem_unref(md); + } else { + grpc_sopb_add_metadata(&s->parser.incoming_sopb, md); + } +} + +static int init_header_frame_parser(transport *t, int is_continuation) { + int is_eoh = + (t->incoming_frame_flags & GRPC_CHTTP2_DATA_FLAG_END_HEADERS) != 0; + stream *s; + + if (is_eoh) { + t->expect_continuation_stream_id = 0; + } else { + t->expect_continuation_stream_id = t->incoming_stream_id; + } + + if (!is_continuation) { + t->header_eof = + (t->incoming_frame_flags & GRPC_CHTTP2_DATA_FLAG_END_STREAM) != 0; + } + + /* could be a new stream or an existing stream */ + s = lookup_stream(t, t->incoming_stream_id); + if (!s) { + if (is_continuation) { + gpr_log(GPR_ERROR, "stream disbanded before CONTINUATION received"); + return init_skip_frame(t, 1); + } + if (t->is_client) { + if ((t->incoming_stream_id & 1) && + t->incoming_stream_id < t->next_stream_id) { + /* this is an old (probably cancelled) stream */ + } else { + gpr_log(GPR_ERROR, "ignoring new stream creation on client"); + } + return init_skip_frame(t, 1); + } + t->incoming_stream = NULL; + /* if stream is accepted, we set incoming_stream in init_stream */ + t->cb->accept_stream(t->cb_user_data, &t->base, + (void *)(gpr_uintptr)t->incoming_stream_id); + s = t->incoming_stream; + if (!s) { + gpr_log(GPR_ERROR, "stream not accepted"); + return init_skip_frame(t, 1); + } + } else { + t->incoming_stream = s; + } + if (t->incoming_stream->read_closed) { + gpr_log(GPR_ERROR, "skipping already closed stream header"); + t->incoming_stream = NULL; + return init_skip_frame(t, 1); + } + t->parser = grpc_chttp2_header_parser_parse; + t->parser_data = &t->hpack_parser; + t->hpack_parser.on_header = on_header; + t->hpack_parser.on_header_user_data = t; + t->hpack_parser.is_boundary = is_eoh; + t->hpack_parser.is_eof = is_eoh ? t->header_eof : 0; + if (!is_continuation && + (t->incoming_frame_flags & GRPC_CHTTP2_FLAG_HAS_PRIORITY)) { + grpc_chttp2_hpack_parser_set_has_priority(&t->hpack_parser); + } + return 1; +} + +static int init_window_update_frame_parser(transport *t) { + int ok = GRPC_CHTTP2_PARSE_OK == grpc_chttp2_window_update_parser_begin_frame( + &t->simple_parsers.window_update, + t->incoming_frame_size, + t->incoming_frame_flags); + if (!ok) { + drop_connection(t); + } + t->parser = grpc_chttp2_window_update_parser_parse; + t->parser_data = &t->simple_parsers.window_update; + return ok; +} + +static int init_ping_parser(transport *t) { + int ok = GRPC_CHTTP2_PARSE_OK == + grpc_chttp2_ping_parser_begin_frame(&t->simple_parsers.ping, + t->incoming_frame_size, + t->incoming_frame_flags); + if (!ok) { + drop_connection(t); + } + t->parser = grpc_chttp2_ping_parser_parse; + t->parser_data = &t->simple_parsers.ping; + return ok; +} + +static int init_settings_frame_parser(transport *t) { + int ok = GRPC_CHTTP2_PARSE_OK == + grpc_chttp2_settings_parser_begin_frame( + &t->simple_parsers.settings, t->incoming_frame_size, + t->incoming_frame_flags, t->settings[PEER_SETTINGS]); + if (!ok) { + drop_connection(t); + } + if (t->incoming_frame_flags & GRPC_CHTTP2_FLAG_ACK) { + memcpy(t->settings[ACKED_SETTINGS], t->settings[SENT_SETTINGS], + GRPC_CHTTP2_NUM_SETTINGS * sizeof(gpr_uint32)); + } + t->parser = grpc_chttp2_settings_parser_parse; + t->parser_data = &t->simple_parsers.settings; + return ok; +} + +static int init_frame_parser(transport *t) { + if (t->expect_continuation_stream_id != 0) { + if (t->incoming_frame_type != GRPC_CHTTP2_FRAME_CONTINUATION) { + gpr_log(GPR_ERROR, "Expected CONTINUATION frame, got frame type %02x", + t->incoming_frame_type); + return 0; + } + if (t->expect_continuation_stream_id != t->incoming_stream_id) { + gpr_log(GPR_ERROR, + "Expected CONTINUATION frame for stream %08x, got stream %08x", + t->expect_continuation_stream_id, t->incoming_stream_id); + return 0; + } + return init_header_frame_parser(t, 1); + } + switch (t->incoming_frame_type) { + case GRPC_CHTTP2_FRAME_DATA: + return init_data_frame_parser(t); + case GRPC_CHTTP2_FRAME_HEADER: + return init_header_frame_parser(t, 0); + case GRPC_CHTTP2_FRAME_CONTINUATION: + gpr_log(GPR_ERROR, "Unexpected CONTINUATION frame"); + return 0; + case GRPC_CHTTP2_FRAME_RST_STREAM: + /* TODO(ctiller): actually parse the reason */ + cancel_stream_id( + t, t->incoming_stream_id, + grpc_chttp2_http2_error_to_grpc_status(GRPC_CHTTP2_CANCEL), + GRPC_CHTTP2_CANCEL, 0); + return init_skip_frame(t, 0); + case GRPC_CHTTP2_FRAME_SETTINGS: + return init_settings_frame_parser(t); + case GRPC_CHTTP2_FRAME_WINDOW_UPDATE: + return init_window_update_frame_parser(t); + case GRPC_CHTTP2_FRAME_PING: + return init_ping_parser(t); + default: + gpr_log(GPR_ERROR, "Unknown frame type %02x", t->incoming_frame_type); + return init_skip_frame(t, 0); + } +} + +static int is_window_update_legal(gpr_uint32 window_update, gpr_uint32 window) { + return window_update < MAX_WINDOW - window; +} + +static int parse_frame_slice(transport *t, gpr_slice slice, int is_last) { + grpc_chttp2_parse_state st; + size_t i; + memset(&st, 0, sizeof(st)); + switch (t->parser(t->parser_data, &st, slice, is_last)) { + case GRPC_CHTTP2_PARSE_OK: + if (st.end_of_stream) { + t->incoming_stream->read_closed = 1; + stream_list_join(t, t->incoming_stream, PENDING_CALLBACKS); + } + if (st.need_flush_reads) { + stream_list_join(t, t->incoming_stream, PENDING_CALLBACKS); + } + if (st.metadata_boundary) { + grpc_sopb_add_metadata_boundary( + &t->incoming_stream->parser.incoming_sopb); + stream_list_join(t, t->incoming_stream, PENDING_CALLBACKS); + } + if (st.ack_settings) { + gpr_slice_buffer_add(&t->qbuf, grpc_chttp2_settings_ack_create()); + maybe_start_some_streams(t); + } + if (st.send_ping_ack) { + gpr_slice_buffer_add( + &t->qbuf, + grpc_chttp2_ping_create(1, t->simple_parsers.ping.opaque_8bytes)); + } + if (st.process_ping_reply) { + for (i = 0; i < t->ping_count; i++) { + if (0 == + memcmp(t->pings[i].id, t->simple_parsers.ping.opaque_8bytes, 8)) { + t->pings[i].cb(t->pings[i].user_data); + memmove(&t->pings[i], &t->pings[i + 1], + (t->ping_count - i - 1) * sizeof(outstanding_ping)); + t->ping_count--; + break; + } + } + } + if (st.window_update) { + if (t->incoming_stream_id) { + /* if there was a stream id, this is for some stream */ + stream *s = lookup_stream(t, t->incoming_stream_id); + if (s) { + int was_window_empty = s->outgoing_window == 0; + if (!is_window_update_legal(st.window_update, s->outgoing_window)) { + cancel_stream(t, s, grpc_chttp2_http2_error_to_grpc_status( + GRPC_CHTTP2_FLOW_CONTROL_ERROR), + GRPC_CHTTP2_FLOW_CONTROL_ERROR, 1); + } else { + s->outgoing_window += st.window_update; + /* if this window update makes outgoing ops writable again, + flag that */ + if (was_window_empty && s->outgoing_sopb.nops) { + stream_list_join(t, s, WRITABLE); + } + } + } + } else { + /* transport level window update */ + if (!is_window_update_legal(st.window_update, t->outgoing_window)) { + drop_connection(t); + } else { + t->outgoing_window += st.window_update; + } + } + } + return 1; + case GRPC_CHTTP2_STREAM_ERROR: + become_skip_parser(t); + cancel_stream_id( + t, t->incoming_stream_id, + grpc_chttp2_http2_error_to_grpc_status(GRPC_CHTTP2_INTERNAL_ERROR), + GRPC_CHTTP2_INTERNAL_ERROR, 1); + return 1; + case GRPC_CHTTP2_CONNECTION_ERROR: + drop_connection(t); + return 0; + } + gpr_log(GPR_ERROR, "should never reach here"); + abort(); + return 0; +} + +static int process_read(transport *t, gpr_slice slice) { + gpr_uint8 *beg = GPR_SLICE_START_PTR(slice); + gpr_uint8 *end = GPR_SLICE_END_PTR(slice); + gpr_uint8 *cur = beg; + + if (cur == end) return 1; + + switch (t->deframe_state) { + case DTS_CLIENT_PREFIX_0: + case DTS_CLIENT_PREFIX_1: + case DTS_CLIENT_PREFIX_2: + case DTS_CLIENT_PREFIX_3: + case DTS_CLIENT_PREFIX_4: + case DTS_CLIENT_PREFIX_5: + case DTS_CLIENT_PREFIX_6: + case DTS_CLIENT_PREFIX_7: + case DTS_CLIENT_PREFIX_8: + case DTS_CLIENT_PREFIX_9: + case DTS_CLIENT_PREFIX_10: + case DTS_CLIENT_PREFIX_11: + case DTS_CLIENT_PREFIX_12: + case DTS_CLIENT_PREFIX_13: + case DTS_CLIENT_PREFIX_14: + case DTS_CLIENT_PREFIX_15: + case DTS_CLIENT_PREFIX_16: + case DTS_CLIENT_PREFIX_17: + case DTS_CLIENT_PREFIX_18: + case DTS_CLIENT_PREFIX_19: + case DTS_CLIENT_PREFIX_20: + case DTS_CLIENT_PREFIX_21: + case DTS_CLIENT_PREFIX_22: + case DTS_CLIENT_PREFIX_23: + while (cur != end && t->deframe_state != DTS_FH_0) { + if (*cur != CLIENT_CONNECT_STRING[t->deframe_state]) { + gpr_log(GPR_ERROR, + "Connect string mismatch: expected '%c' (%d) got '%c' (%d) " + "at byte %d", + CLIENT_CONNECT_STRING[t->deframe_state], + (int)(gpr_uint8)CLIENT_CONNECT_STRING[t->deframe_state], *cur, + (int)*cur, t->deframe_state); + return 0; + } + ++cur; + ++t->deframe_state; + } + if (cur == end) { + return 1; + } + /* fallthrough */ + dts_fh_0: + case DTS_FH_0: + GPR_ASSERT(cur < end); + t->incoming_frame_size = ((gpr_uint32)*cur) << 16; + if (++cur == end) { + t->deframe_state = DTS_FH_1; + return 1; + } + /* fallthrough */ + case DTS_FH_1: + GPR_ASSERT(cur < end); + t->incoming_frame_size |= ((gpr_uint32)*cur) << 8; + if (++cur == end) { + t->deframe_state = DTS_FH_2; + return 1; + } + /* fallthrough */ + case DTS_FH_2: + GPR_ASSERT(cur < end); + t->incoming_frame_size |= *cur; + if (++cur == end) { + t->deframe_state = DTS_FH_3; + return 1; + } + /* fallthrough */ + case DTS_FH_3: + GPR_ASSERT(cur < end); + t->incoming_frame_type = *cur; + if (++cur == end) { + t->deframe_state = DTS_FH_4; + return 1; + } + /* fallthrough */ + case DTS_FH_4: + GPR_ASSERT(cur < end); + t->incoming_frame_flags = *cur; + if (++cur == end) { + t->deframe_state = DTS_FH_5; + return 1; + } + /* fallthrough */ + case DTS_FH_5: + GPR_ASSERT(cur < end); + t->incoming_stream_id = (((gpr_uint32)*cur) << 24) & 0x7f; + if (++cur == end) { + t->deframe_state = DTS_FH_6; + return 1; + } + /* fallthrough */ + case DTS_FH_6: + GPR_ASSERT(cur < end); + t->incoming_stream_id |= ((gpr_uint32)*cur) << 16; + if (++cur == end) { + t->deframe_state = DTS_FH_7; + return 1; + } + /* fallthrough */ + case DTS_FH_7: + GPR_ASSERT(cur < end); + t->incoming_stream_id |= ((gpr_uint32)*cur) << 8; + if (++cur == end) { + t->deframe_state = DTS_FH_8; + return 1; + } + /* fallthrough */ + case DTS_FH_8: + GPR_ASSERT(cur < end); + t->incoming_stream_id |= ((gpr_uint32)*cur); + t->deframe_state = DTS_FRAME; + if (!init_frame_parser(t)) { + return 0; + } + if (t->incoming_frame_size == 0) { + if (!parse_frame_slice(t, gpr_empty_slice(), 1)) { + return 0; + } + if (++cur == end) { + t->deframe_state = DTS_FH_0; + return 1; + } + goto dts_fh_0; /* loop */ + } + if (++cur == end) { + return 1; + } + /* fallthrough */ + case DTS_FRAME: + GPR_ASSERT(cur < end); + if (end - cur == t->incoming_frame_size) { + if (!parse_frame_slice( + t, gpr_slice_sub_no_ref(slice, cur - beg, end - beg), 1)) { + return 0; + } + t->deframe_state = DTS_FH_0; + return 1; + } else if (end - cur > t->incoming_frame_size) { + if (!parse_frame_slice( + t, gpr_slice_sub_no_ref(slice, cur - beg, + cur + t->incoming_frame_size - beg), + 1)) { + return 0; + } + cur += t->incoming_frame_size; + goto dts_fh_0; /* loop */ + } else { + if (!parse_frame_slice( + t, gpr_slice_sub_no_ref(slice, cur - beg, end - beg), 0)) { + return 0; + } + t->incoming_frame_size -= (end - cur); + return 1; + } + gpr_log(GPR_ERROR, "should never reach here"); + abort(); + } + + gpr_log(GPR_ERROR, "should never reach here"); + abort(); +} + +/* tcp read callback */ +static void recv_data(void *tp, gpr_slice *slices, size_t nslices, + grpc_endpoint_cb_status error) { + transport *t = tp; + size_t i; + int keep_reading = 0; + + switch (error) { + case GRPC_ENDPOINT_CB_SHUTDOWN: + case GRPC_ENDPOINT_CB_EOF: + case GRPC_ENDPOINT_CB_ERROR: + case GRPC_ENDPOINT_CB_TIMED_OUT: + lock(t); + drop_connection(t); + t->reading = 0; + if (!t->writing && t->ep) { + grpc_endpoint_destroy(t->ep); + t->ep = NULL; + gpr_cv_broadcast(&t->cv); + unref_transport(t); /* safe as we still have a ref for read */ + } + unlock(t); + unref_transport(t); + break; + case GRPC_ENDPOINT_CB_OK: + lock(t); + for (i = 0; i < nslices && process_read(t, slices[i]); i++) + ; + unlock(t); + keep_reading = 1; + break; + } + + for (i = 0; i < nslices; i++) gpr_slice_unref(slices[i]); + + if (keep_reading) { + grpc_endpoint_notify_on_read(t->ep, recv_data, t, gpr_inf_future); + } +} + +/* + * CALLBACK LOOP + */ + +static grpc_stream_state compute_state(gpr_uint8 write_closed, + gpr_uint8 read_closed) { + if (write_closed && read_closed) return GRPC_STREAM_CLOSED; + if (write_closed) return GRPC_STREAM_SEND_CLOSED; + if (read_closed) return GRPC_STREAM_RECV_CLOSED; + return GRPC_STREAM_OPEN; +} + +static int prepare_callbacks(transport *t) { + stream *s; + grpc_stream_op_buffer temp_sopb; + int n = 0; + while ((s = stream_list_remove_head(t, PENDING_CALLBACKS))) { + int execute = 1; + temp_sopb = s->parser.incoming_sopb; + s->parser.incoming_sopb = s->callback_sopb; + s->callback_sopb = temp_sopb; + + s->callback_state = compute_state( + s->write_closed && s->outgoing_sopb.nops == 0, s->read_closed); + if (s->callback_state == GRPC_STREAM_CLOSED) { + remove_from_stream_map(t, s); + if (s->published_close) { + execute = 0; + } + s->published_close = 1; + } + + if (execute) { + stream_list_add_tail(t, s, EXECUTING_CALLBACKS); + n = 1; + } + } + return n; +} + +static void run_callbacks(transport *t) { + stream *s; + while ((s = stream_list_remove_head(t, EXECUTING_CALLBACKS))) { + size_t nops = s->callback_sopb.nops; + s->callback_sopb.nops = 0; + t->cb->recv_batch(t->cb_user_data, &t->base, (grpc_stream *)s, + s->callback_sopb.ops, nops, s->callback_state); + } +} + +/* + * INTEGRATION GLUE + */ + +static const grpc_transport_vtable vtable = { + sizeof(stream), init_stream, send_batch, set_allow_window_updates, + destroy_stream, abort_stream, close_transport, send_ping, + destroy_transport}; + +void grpc_create_chttp2_transport(grpc_transport_setup_callback setup, + void *arg, + const grpc_channel_args *channel_args, + grpc_endpoint *ep, gpr_slice *slices, + size_t nslices, grpc_mdctx *mdctx, + int is_client) { + transport *t = gpr_malloc(sizeof(transport)); + init_transport(t, setup, arg, channel_args, ep, mdctx, is_client); + ref_transport(t); + recv_data(t, slices, nslices, GRPC_ENDPOINT_CB_OK); +} diff --git a/src/core/transport/chttp2_transport.h b/src/core/transport/chttp2_transport.h new file mode 100644 index 0000000000..37eb84ed02 --- /dev/null +++ b/src/core/transport/chttp2_transport.h @@ -0,0 +1,47 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_TRANSPORT_CHTTP2_TRANSPORT_H__ +#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_TRANSPORT_H__ + +#include "src/core/endpoint/tcp.h" +#include "src/core/transport/transport.h" + +void grpc_create_chttp2_transport(grpc_transport_setup_callback setup, + void *arg, + const grpc_channel_args *channel_args, + grpc_endpoint *ep, gpr_slice *slices, + size_t nslices, grpc_mdctx *metadata_context, + int is_client); + +#endif /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_TRANSPORT_H__ */ diff --git a/src/core/transport/metadata.c b/src/core/transport/metadata.c new file mode 100644 index 0000000000..ceb77df34d --- /dev/null +++ b/src/core/transport/metadata.c @@ -0,0 +1,525 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/transport/metadata.h" + +#include <stddef.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include "src/core/support/murmur_hash.h" +#include <grpc/support/time.h> + +#define INITIAL_STRTAB_CAPACITY 4 +#define INITIAL_MDTAB_CAPACITY 4 + +#define KV_HASH(key, value) ((key)->hash ^ (value)->hash) + +typedef struct internal_string { + /* must be byte compatible with grpc_mdstr */ + gpr_slice slice; + gpr_uint32 hash; + + /* private only data */ + gpr_uint32 refs; + gpr_slice_refcount refcount; + + grpc_mdctx *context; + + struct internal_string *bucket_next; +} internal_string; + +typedef struct internal_metadata { + /* must be byte compatible with grpc_mdelem */ + internal_string *key; + internal_string *value; + + /* private only data */ + void *user_data; + void (*destroy_user_data)(void *user_data); + + gpr_uint32 refs; + grpc_mdctx *context; + struct internal_metadata *bucket_next; +} internal_metadata; + +struct grpc_mdctx { + gpr_uint32 hash_seed; + int orphaned; + + gpr_mu mu; + + internal_string **strtab; + size_t strtab_count; + size_t strtab_capacity; + + internal_metadata **mdtab; + size_t mdtab_count; + size_t mdtab_free; + size_t mdtab_capacity; +}; + +static void internal_string_ref(internal_string *s); +static void internal_string_unref(internal_string *s); +static void discard_metadata(grpc_mdctx *ctx); +static void gc_mdtab(grpc_mdctx *ctx); +static void metadata_context_destroy(grpc_mdctx *ctx); + +static void lock(grpc_mdctx *ctx) { gpr_mu_lock(&ctx->mu); } + +static void unlock(grpc_mdctx *ctx) { + /* If the context has been orphaned we'd like to delete it soon. We check + conditions in unlock as it signals the end of mutations on a context. + + We need to ensure all grpc_mdelem and grpc_mdstr elements have been deleted + first. This is equivalent to saying that both tables have zero counts, + which is equivalent to saying that strtab_count is zero (as mdelem's MUST + reference an mdstr for their key and value slots). + + To encourage that to happen, we start discarding zero reference count + mdelems on every unlock (instead of the usual 'I'm too loaded' trigger + case), since otherwise we can be stuck waiting for a garbage collection + that will never happen. */ + if (ctx->orphaned) { + /* uncomment if you're having trouble diagnosing an mdelem leak to make + things clearer (slows down destruction a lot, however) */ + /* gc_mdtab(ctx); */ + if (ctx->mdtab_count && ctx->mdtab_count == ctx->mdtab_free) { + discard_metadata(ctx); + } + if (ctx->strtab_count == 0) { + gpr_mu_unlock(&ctx->mu); + metadata_context_destroy(ctx); + return; + } + } + gpr_mu_unlock(&ctx->mu); +} + +static void ref_md(internal_metadata *md) { + if (0 == md->refs++) { + md->context->mdtab_free--; + } +} + +grpc_mdctx *grpc_mdctx_create_with_seed(gpr_uint32 seed) { + grpc_mdctx *ctx = gpr_malloc(sizeof(grpc_mdctx)); + + ctx->orphaned = 0; + ctx->hash_seed = seed; + gpr_mu_init(&ctx->mu); + ctx->strtab = gpr_malloc(sizeof(internal_string *) * INITIAL_STRTAB_CAPACITY); + memset(ctx->strtab, 0, sizeof(grpc_mdstr *) * INITIAL_STRTAB_CAPACITY); + ctx->strtab_count = 0; + ctx->strtab_capacity = INITIAL_STRTAB_CAPACITY; + ctx->mdtab = gpr_malloc(sizeof(internal_metadata *) * INITIAL_MDTAB_CAPACITY); + memset(ctx->mdtab, 0, sizeof(grpc_mdelem *) * INITIAL_MDTAB_CAPACITY); + ctx->mdtab_count = 0; + ctx->mdtab_capacity = INITIAL_MDTAB_CAPACITY; + ctx->mdtab_free = 0; + + return ctx; +} + +grpc_mdctx *grpc_mdctx_create() { + /* This seed is used to prevent remote connections from controlling hash table + * collisions. It needs to be somewhat unpredictable to a remote connection. + */ + return grpc_mdctx_create_with_seed(gpr_now().tv_nsec); +} + +static void discard_metadata(grpc_mdctx *ctx) { + size_t i; + internal_metadata *next, *cur; + + for (i = 0; i < ctx->mdtab_capacity; i++) { + cur = ctx->mdtab[i]; + while (cur) { + GPR_ASSERT(cur->refs == 0); + next = cur->bucket_next; + internal_string_unref(cur->key); + internal_string_unref(cur->value); + if (cur->user_data) { + cur->destroy_user_data(cur->user_data); + } + gpr_free(cur); + cur = next; + ctx->mdtab_free--; + ctx->mdtab_count--; + } + ctx->mdtab[i] = NULL; + } +} + +static void metadata_context_destroy(grpc_mdctx *ctx) { + gpr_mu_lock(&ctx->mu); + GPR_ASSERT(ctx->strtab_count == 0); + GPR_ASSERT(ctx->mdtab_count == 0); + GPR_ASSERT(ctx->mdtab_free == 0); + gpr_free(ctx->strtab); + gpr_free(ctx->mdtab); + gpr_mu_unlock(&ctx->mu); + gpr_mu_destroy(&ctx->mu); + gpr_free(ctx); +} + +void grpc_mdctx_orphan(grpc_mdctx *ctx) { + lock(ctx); + GPR_ASSERT(!ctx->orphaned); + ctx->orphaned = 1; + unlock(ctx); +} + +static void grow_strtab(grpc_mdctx *ctx) { + size_t capacity = ctx->strtab_capacity * 2; + size_t i; + internal_string **strtab = gpr_malloc(sizeof(internal_string *) * capacity); + internal_string *s, *next; + memset(strtab, 0, sizeof(internal_string *) * capacity); + + for (i = 0; i < ctx->strtab_capacity; i++) { + for (s = ctx->strtab[i]; s; s = next) { + next = s->bucket_next; + s->bucket_next = strtab[s->hash % capacity]; + strtab[s->hash % capacity] = s; + } + } + + gpr_free(ctx->strtab); + ctx->strtab = strtab; + ctx->strtab_capacity = capacity; +} + +static void internal_destroy_string(internal_string *is) { + internal_string **prev_next; + internal_string *cur; + grpc_mdctx *ctx = is->context; + for (prev_next = &ctx->strtab[is->hash % ctx->strtab_capacity], + cur = *prev_next; + cur != is; prev_next = &cur->bucket_next, cur = cur->bucket_next) + ; + *prev_next = cur->bucket_next; + ctx->strtab_count--; + gpr_free(is); +} + +static void internal_string_ref(internal_string *s) { ++s->refs; } + +static void internal_string_unref(internal_string *s) { + GPR_ASSERT(s->refs > 0); + if (0 == --s->refs) { + internal_destroy_string(s); + } +} + +static void slice_ref(void *p) { + internal_string *is = + (internal_string *)((char *)p - offsetof(internal_string, refcount)); + grpc_mdctx *ctx = is->context; + lock(ctx); + internal_string_ref(is); + unlock(ctx); +} + +static void slice_unref(void *p) { + internal_string *is = + (internal_string *)((char *)p - offsetof(internal_string, refcount)); + grpc_mdctx *ctx = is->context; + lock(ctx); + internal_string_unref(is); + unlock(ctx); +} + +grpc_mdstr *grpc_mdstr_from_string(grpc_mdctx *ctx, const char *str) { + return grpc_mdstr_from_buffer(ctx, (const gpr_uint8 *)str, strlen(str)); +} + +grpc_mdstr *grpc_mdstr_from_slice(grpc_mdctx *ctx, gpr_slice slice) { + grpc_mdstr *result = grpc_mdstr_from_buffer(ctx, GPR_SLICE_START_PTR(slice), + GPR_SLICE_LENGTH(slice)); + gpr_slice_unref(slice); + return result; +} + +grpc_mdstr *grpc_mdstr_from_buffer(grpc_mdctx *ctx, const gpr_uint8 *buf, + size_t length) { + gpr_uint32 hash = gpr_murmur_hash3(buf, length, ctx->hash_seed); + internal_string *s; + + lock(ctx); + + /* search for an existing string */ + for (s = ctx->strtab[hash % ctx->strtab_capacity]; 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)) { + internal_string_ref(s); + unlock(ctx); + 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)); + s->refs = 1; + s->slice.refcount = NULL; + memcpy(s->slice.data.inlined.bytes, buf, length); + s->slice.data.inlined.bytes[length] = 0; + s->slice.data.inlined.length = length; + } else { + /* string data goes after the internal_string header, and we +1 for null + terminator */ + s = gpr_malloc(sizeof(internal_string) + length + 1); + s->refs = 1; + s->refcount.ref = slice_ref; + s->refcount.unref = slice_unref; + s->slice.refcount = &s->refcount; + s->slice.data.refcounted.bytes = (gpr_uint8 *)(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->hash = hash; + s->context = ctx; + s->bucket_next = ctx->strtab[hash % ctx->strtab_capacity]; + ctx->strtab[hash % ctx->strtab_capacity] = s; + + ctx->strtab_count++; + + if (ctx->strtab_count > ctx->strtab_capacity * 2) { + grow_strtab(ctx); + } + + unlock(ctx); + + return (grpc_mdstr *)s; +} + +static void gc_mdtab(grpc_mdctx *ctx) { + size_t i; + internal_metadata **prev_next; + internal_metadata *md, *next; + + for (i = 0; i < ctx->mdtab_capacity; i++) { + prev_next = &ctx->mdtab[i]; + for (md = ctx->mdtab[i]; md; md = next) { + next = md->bucket_next; + if (md->refs == 0) { + internal_string_unref(md->key); + internal_string_unref(md->value); + if (md->user_data) { + md->destroy_user_data(md->user_data); + } + gpr_free(md); + *prev_next = next; + ctx->mdtab_free--; + ctx->mdtab_count--; + } else { + prev_next = &md->bucket_next; + } + } + } + + GPR_ASSERT(ctx->mdtab_free == 0); +} + +static void grow_mdtab(grpc_mdctx *ctx) { + size_t capacity = ctx->mdtab_capacity * 2; + size_t i; + internal_metadata **mdtab = + gpr_malloc(sizeof(internal_metadata *) * capacity); + internal_metadata *md, *next; + gpr_uint32 hash; + memset(mdtab, 0, sizeof(internal_metadata *) * capacity); + + for (i = 0; i < ctx->mdtab_capacity; i++) { + for (md = ctx->mdtab[i]; md; md = next) { + hash = KV_HASH(md->key, md->value); + next = md->bucket_next; + md->bucket_next = mdtab[hash % capacity]; + mdtab[hash % capacity] = md; + } + } + + gpr_free(ctx->mdtab); + ctx->mdtab = mdtab; + ctx->mdtab_capacity = capacity; +} + +static void rehash_mdtab(grpc_mdctx *ctx) { + if (ctx->mdtab_free > ctx->mdtab_capacity / 4) { + gc_mdtab(ctx); + } else { + grow_mdtab(ctx); + } +} + +grpc_mdelem *grpc_mdelem_from_metadata_strings(grpc_mdctx *ctx, + grpc_mdstr *mkey, + grpc_mdstr *mvalue) { + internal_string *key = (internal_string *)mkey; + internal_string *value = (internal_string *)mvalue; + gpr_uint32 hash = KV_HASH(mkey, mvalue); + internal_metadata *md; + + GPR_ASSERT(key->context == ctx); + GPR_ASSERT(value->context == ctx); + + lock(ctx); + + /* search for an existing pair */ + for (md = ctx->mdtab[hash % ctx->mdtab_capacity]; md; md = md->bucket_next) { + if (md->key == key && md->value == value) { + ref_md(md); + internal_string_unref(key); + internal_string_unref(value); + unlock(ctx); + return (grpc_mdelem *)md; + } + } + + /* not found: create a new pair */ + md = gpr_malloc(sizeof(internal_metadata)); + md->refs = 1; + md->context = ctx; + md->key = key; + md->value = value; + md->user_data = NULL; + md->destroy_user_data = NULL; + md->bucket_next = ctx->mdtab[hash % ctx->mdtab_capacity]; + ctx->mdtab[hash % ctx->mdtab_capacity] = md; + ctx->mdtab_count++; + + if (ctx->mdtab_count > ctx->mdtab_capacity * 2) { + rehash_mdtab(ctx); + } + + unlock(ctx); + + return (grpc_mdelem *)md; +} + +grpc_mdelem *grpc_mdelem_from_strings(grpc_mdctx *ctx, const char *key, + const char *value) { + return grpc_mdelem_from_metadata_strings(ctx, + grpc_mdstr_from_string(ctx, key), + grpc_mdstr_from_string(ctx, value)); +} + +grpc_mdelem *grpc_mdelem_from_slices(grpc_mdctx *ctx, gpr_slice key, + gpr_slice value) { + return grpc_mdelem_from_metadata_strings(ctx, grpc_mdstr_from_slice(ctx, key), + grpc_mdstr_from_slice(ctx, value)); +} + +grpc_mdelem *grpc_mdelem_from_string_and_buffer(grpc_mdctx *ctx, + const char *key, + const gpr_uint8 *value, + size_t value_length) { + return grpc_mdelem_from_metadata_strings( + ctx, grpc_mdstr_from_string(ctx, key), + grpc_mdstr_from_buffer(ctx, value, value_length)); +} + +grpc_mdelem *grpc_mdelem_ref(grpc_mdelem *gmd) { + internal_metadata *md = (internal_metadata *)gmd; + grpc_mdctx *ctx = md->context; + lock(ctx); + ref_md(md); + unlock(ctx); + return gmd; +} + +void grpc_mdelem_unref(grpc_mdelem *gmd) { + internal_metadata *md = (internal_metadata *)gmd; + grpc_mdctx *ctx = md->context; + lock(ctx); + GPR_ASSERT(md->refs); + if (0 == --md->refs) { + ctx->mdtab_free++; + } + unlock(ctx); +} + +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) { + internal_string *s = (internal_string *)gs; + grpc_mdctx *ctx = s->context; + lock(ctx); + internal_string_ref(s); + unlock(ctx); + return gs; +} + +void grpc_mdstr_unref(grpc_mdstr *gs) { + internal_string *s = (internal_string *)gs; + grpc_mdctx *ctx = s->context; + lock(ctx); + internal_string_unref(s); + unlock(ctx); +} + +size_t grpc_mdctx_get_mdtab_capacity_test_only(grpc_mdctx *ctx) { + return ctx->mdtab_capacity; +} + +size_t grpc_mdctx_get_mdtab_count_test_only(grpc_mdctx *ctx) { + return ctx->mdtab_count; +} + +size_t grpc_mdctx_get_mdtab_free_test_only(grpc_mdctx *ctx) { + return ctx->mdtab_free; +} + +void *grpc_mdelem_get_user_data(grpc_mdelem *md, + void (*if_destroy_func)(void *)) { + internal_metadata *im = (internal_metadata *)md; + return im->destroy_user_data == if_destroy_func ? im->user_data : NULL; +} + +void grpc_mdelem_set_user_data(grpc_mdelem *md, void (*destroy_func)(void *), + void *user_data) { + internal_metadata *im = (internal_metadata *)md; + GPR_ASSERT((user_data == NULL) == (destroy_func == NULL)); + if (im->destroy_user_data) { + im->destroy_user_data(im->user_data); + } + im->destroy_user_data = destroy_func; + im->user_data = user_data; +} diff --git a/src/core/transport/metadata.h b/src/core/transport/metadata.h new file mode 100644 index 0000000000..4b87f70e31 --- /dev/null +++ b/src/core/transport/metadata.h @@ -0,0 +1,132 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_TRANSPORT_METADATA_H__ +#define __GRPC_INTERNAL_TRANSPORT_METADATA_H__ + +#include <grpc/support/slice.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. */ + +/* Forward declarations */ +typedef struct grpc_mdctx grpc_mdctx; +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 gpr_uint32 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 */ +}; + +/* Create/orphan a metadata context */ +grpc_mdctx *grpc_mdctx_create(); +grpc_mdctx *grpc_mdctx_create_with_seed(gpr_uint32 seed); +void grpc_mdctx_orphan(grpc_mdctx *mdctx); + +/* Test only accessors to internal state - only for testing this code - do not + rely on it outside of metadata_test.c */ +size_t grpc_mdctx_get_mdtab_capacity_test_only(grpc_mdctx *mdctx); +size_t grpc_mdctx_get_mdtab_count_test_only(grpc_mdctx *mdctx); +size_t grpc_mdctx_get_mdtab_free_test_only(grpc_mdctx *mdctx); + +/* Constructors for grpc_mdstr instances; take a variety of data types that + clients may have handy */ +grpc_mdstr *grpc_mdstr_from_string(grpc_mdctx *ctx, const char *str); +grpc_mdstr *grpc_mdstr_from_slice(grpc_mdctx *ctx, gpr_slice slice); +grpc_mdstr *grpc_mdstr_from_buffer(grpc_mdctx *ctx, const gpr_uint8 *str, + size_t length); + +/* Constructors for grpc_mdelem instances; take a variety of data types that + clients may have handy */ +grpc_mdelem *grpc_mdelem_from_metadata_strings(grpc_mdctx *ctx, grpc_mdstr *key, + grpc_mdstr *value); +grpc_mdelem *grpc_mdelem_from_strings(grpc_mdctx *ctx, const char *key, + const char *value); +grpc_mdelem *grpc_mdelem_from_slices(grpc_mdctx *ctx, gpr_slice key, + gpr_slice value); +grpc_mdelem *grpc_mdelem_from_string_and_buffer(grpc_mdctx *ctx, + const char *key, + const gpr_uint8 *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 */ +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); + +/* 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); + +#endif /* __GRPC_INTERNAL_TRANSPORT_METADATA_H__ */ diff --git a/src/core/transport/stream_op.c b/src/core/transport/stream_op.c new file mode 100644 index 0000000000..c77c8cde1f --- /dev/null +++ b/src/core/transport/stream_op.c @@ -0,0 +1,165 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/transport/stream_op.h" + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +#include <string.h> + +/* Initial number of operations to allocate */ +#define INITIAL_SLOTS 8 +/* Exponential growth function: Given x, return a larger x. + Currently we grow by 1.5 times upon reallocation. + Assumes INITIAL_SLOTS > 1 */ +#define GROW(x) (3 * (x) / 2) + +void grpc_sopb_init(grpc_stream_op_buffer *sopb) { + sopb->ops = gpr_malloc(sizeof(grpc_stream_op) * INITIAL_SLOTS); + GPR_ASSERT(sopb->ops); + sopb->nops = 0; + sopb->capacity = INITIAL_SLOTS; +} + +void grpc_sopb_destroy(grpc_stream_op_buffer *sopb) { + grpc_stream_ops_unref_owned_objects(sopb->ops, sopb->nops); + gpr_free(sopb->ops); +} + +void grpc_sopb_reset(grpc_stream_op_buffer *sopb) { + grpc_stream_ops_unref_owned_objects(sopb->ops, sopb->nops); + sopb->nops = 0; +} + +void grpc_stream_ops_unref_owned_objects(grpc_stream_op *ops, size_t nops) { + int i; + for (i = 0; i < nops; i++) { + switch (ops[i].type) { + case GRPC_OP_SLICE: + gpr_slice_unref(ops[i].data.slice); + break; + case GRPC_OP_METADATA: + grpc_mdelem_unref(ops[i].data.metadata); + break; + case GRPC_OP_FLOW_CTL_CB: + ops[i].data.flow_ctl_cb.cb(ops[i].data.flow_ctl_cb.arg, GRPC_OP_ERROR); + break; + case GRPC_NO_OP: + case GRPC_OP_DEADLINE: + case GRPC_OP_METADATA_BOUNDARY: + case GRPC_OP_BEGIN_MESSAGE: + break; + } + } +} + +static void expand(grpc_stream_op_buffer *sopb) { + sopb->capacity = GROW(sopb->capacity); + sopb->ops = gpr_realloc(sopb->ops, sizeof(grpc_stream_op) * sopb->capacity); + GPR_ASSERT(sopb->ops); +} + +static grpc_stream_op *add(grpc_stream_op_buffer *sopb) { + grpc_stream_op *out; + + if (sopb->nops == sopb->capacity) { + expand(sopb); + } + out = sopb->ops + sopb->nops; + sopb->nops++; + return out; +} + +void grpc_sopb_add_no_op(grpc_stream_op_buffer *sopb) { + add(sopb)->type = GRPC_NO_OP; +} + +void grpc_sopb_add_begin_message(grpc_stream_op_buffer *sopb, gpr_uint32 length, + gpr_uint32 flags) { + grpc_stream_op *op = add(sopb); + op->type = GRPC_OP_BEGIN_MESSAGE; + op->data.begin_message.length = length; + op->data.begin_message.flags = flags; +} + +void grpc_sopb_add_metadata_boundary(grpc_stream_op_buffer *sopb) { + grpc_stream_op *op = add(sopb); + op->type = GRPC_OP_METADATA_BOUNDARY; +} + +void grpc_sopb_add_metadata(grpc_stream_op_buffer *sopb, grpc_mdelem *md) { + grpc_stream_op *op = add(sopb); + op->type = GRPC_OP_METADATA; + op->data.metadata = md; +} + +void grpc_sopb_add_deadline(grpc_stream_op_buffer *sopb, + gpr_timespec deadline) { + grpc_stream_op *op = add(sopb); + op->type = GRPC_OP_DEADLINE; + op->data.deadline = deadline; +} + +void grpc_sopb_add_slice(grpc_stream_op_buffer *sopb, gpr_slice slice) { + grpc_stream_op *op = add(sopb); + op->type = GRPC_OP_SLICE; + op->data.slice = slice; +} + +void grpc_sopb_add_flow_ctl_cb(grpc_stream_op_buffer *sopb, + void (*cb)(void *arg, grpc_op_error error), + void *arg) { + grpc_stream_op *op = add(sopb); + op->type = GRPC_OP_FLOW_CTL_CB; + op->data.flow_ctl_cb.cb = cb; + op->data.flow_ctl_cb.arg = arg; +} + +void grpc_sopb_append(grpc_stream_op_buffer *sopb, grpc_stream_op *ops, + size_t nops) { + size_t orig_nops = sopb->nops; + size_t new_nops = orig_nops + nops; + + if (new_nops > sopb->capacity) { + size_t new_capacity = GROW(sopb->capacity); + if (new_capacity < new_nops) { + new_capacity = new_nops; + } + sopb->ops = gpr_realloc(sopb->ops, sizeof(grpc_stream_op) * new_capacity); + sopb->capacity = new_capacity; + } + + memcpy(sopb->ops + orig_nops, ops, sizeof(grpc_stream_op) * nops); + sopb->nops = new_nops; +} diff --git a/src/core/transport/stream_op.h b/src/core/transport/stream_op.h new file mode 100644 index 0000000000..be60bc2da6 --- /dev/null +++ b/src/core/transport/stream_op.h @@ -0,0 +1,128 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_TRANSPORT_STREAM_OP_H__ +#define __GRPC_INTERNAL_TRANSPORT_STREAM_OP_H__ + +#include <grpc/grpc.h> +#include <grpc/support/port_platform.h> +#include <grpc/support/slice.h> +#include <grpc/support/time.h> +#include "src/core/transport/metadata.h" + +/* Operations that can be performed on a stream. + Used by grpc_stream_op. */ +typedef enum grpc_stream_op_code { + /* Do nothing code. Useful if rewriting a batch to exclude some operations. + Must be ignored by receivers */ + GRPC_NO_OP, + GRPC_OP_METADATA, + GRPC_OP_DEADLINE, + GRPC_OP_METADATA_BOUNDARY, + /* Begin a message/metadata element/status - as defined by + grpc_message_type. */ + GRPC_OP_BEGIN_MESSAGE, + /* Add a slice of data to the current message/metadata element/status. + Must not overflow the forward declared length. */ + GRPC_OP_SLICE, + /* Call some function once this operation has passed flow control. */ + GRPC_OP_FLOW_CTL_CB +} grpc_stream_op_code; + +/* Arguments for GRPC_OP_BEGIN */ +typedef struct grpc_begin_message { + /* How many bytes of data will this message contain */ + gpr_uint32 length; + /* Write flags for the message: see grpc.h GRPC_WRITE_xxx */ + gpr_uint32 flags; +} grpc_begin_message; + +/* Arguments for GRPC_OP_FLOW_CTL_CB */ +typedef struct grpc_flow_ctl_cb { + void (*cb)(void *arg, grpc_op_error error); + void *arg; +} grpc_flow_ctl_cb; + +/* Represents a single operation performed on a stream/transport */ +typedef struct grpc_stream_op { + /* the operation to be applied */ + enum grpc_stream_op_code type; + /* the arguments to this operation. union fields are named according to the + associated op-code */ + union { + grpc_begin_message begin_message; + grpc_mdelem *metadata; + gpr_timespec deadline; + gpr_slice slice; + grpc_flow_ctl_cb flow_ctl_cb; + } data; +} grpc_stream_op; + +/* A stream op buffer is a wrapper around stream operations that is dynamically + extendable. + TODO(ctiller): inline a few elements into the struct, to avoid common case + per-call allocations. */ +typedef struct grpc_stream_op_buffer { + grpc_stream_op *ops; + size_t nops; + size_t capacity; +} grpc_stream_op_buffer; + +/* Initialize a stream op buffer */ +void grpc_sopb_init(grpc_stream_op_buffer *sopb); +/* Destroy a stream op buffer */ +void grpc_sopb_destroy(grpc_stream_op_buffer *sopb); +/* Reset a sopb to no elements */ +void grpc_sopb_reset(grpc_stream_op_buffer *sopb); + +void grpc_stream_ops_unref_owned_objects(grpc_stream_op *ops, size_t nops); + +/* Append a GRPC_NO_OP to a buffer */ +void grpc_sopb_add_no_op(grpc_stream_op_buffer *sopb); +/* Append a GRPC_OP_BEGIN to a buffer */ +void grpc_sopb_add_begin_message(grpc_stream_op_buffer *sopb, gpr_uint32 length, + gpr_uint32 flags); +void grpc_sopb_add_metadata(grpc_stream_op_buffer *sopb, grpc_mdelem *metadata); +void grpc_sopb_add_deadline(grpc_stream_op_buffer *sopb, gpr_timespec deadline); +void grpc_sopb_add_metadata_boundary(grpc_stream_op_buffer *sopb); +/* Append a GRPC_SLICE to a buffer - does not ref/unref the slice */ +void grpc_sopb_add_slice(grpc_stream_op_buffer *sopb, gpr_slice slice); +/* Append a GRPC_OP_FLOW_CTL_CB to a buffer */ +void grpc_sopb_add_flow_ctl_cb(grpc_stream_op_buffer *sopb, + void (*cb)(void *arg, grpc_op_error error), + void *arg); +/* Append a buffer to a buffer - does not ref/unref any internal objects */ +void grpc_sopb_append(grpc_stream_op_buffer *sopb, grpc_stream_op *ops, + size_t nops); + +#endif /* __GRPC_INTERNAL_TRANSPORT_STREAM_OP_H__ */ diff --git a/src/core/transport/transport.c b/src/core/transport/transport.c new file mode 100644 index 0000000000..d3291bbbb9 --- /dev/null +++ b/src/core/transport/transport.c @@ -0,0 +1,85 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/transport/transport.h" +#include "src/core/transport/transport_impl.h" + +size_t grpc_transport_stream_size(grpc_transport *transport) { + return transport->vtable->sizeof_stream; +} + +void grpc_transport_close(grpc_transport *transport) { + transport->vtable->close(transport); +} + +void grpc_transport_destroy(grpc_transport *transport) { + transport->vtable->destroy(transport); +} + +int grpc_transport_init_stream(grpc_transport *transport, grpc_stream *stream, + const void *server_data) { + return transport->vtable->init_stream(transport, stream, server_data); +} + +void grpc_transport_send_batch(grpc_transport *transport, grpc_stream *stream, + grpc_stream_op *ops, size_t nops, int is_last) { + transport->vtable->send_batch(transport, stream, ops, nops, is_last); +} + +void grpc_transport_set_allow_window_updates(grpc_transport *transport, + grpc_stream *stream, int allow) { + transport->vtable->set_allow_window_updates(transport, stream, allow); +} + +void grpc_transport_destroy_stream(grpc_transport *transport, + grpc_stream *stream) { + transport->vtable->destroy_stream(transport, stream); +} + +void grpc_transport_abort_stream(grpc_transport *transport, grpc_stream *stream, + grpc_status_code status) { + transport->vtable->abort_stream(transport, stream, status); +} + +void grpc_transport_ping(grpc_transport *transport, void (*cb)(void *user_data), + void *user_data) { + transport->vtable->ping(transport, cb, user_data); +} + +void grpc_transport_setup_cancel(grpc_transport_setup *setup) { + setup->vtable->cancel(setup); +} + +void grpc_transport_setup_initiate(grpc_transport_setup *setup) { + setup->vtable->initiate(setup); +} diff --git a/src/core/transport/transport.h b/src/core/transport/transport.h new file mode 100644 index 0000000000..1872947208 --- /dev/null +++ b/src/core/transport/transport.h @@ -0,0 +1,245 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_TRANSPORT_TRANSPORT_H__ +#define __GRPC_INTERNAL_TRANSPORT_TRANSPORT_H__ + +#include <stddef.h> + +#include "src/core/transport/stream_op.h" + +/* forward declarations */ +typedef struct grpc_transport grpc_transport; +typedef struct grpc_transport_callbacks grpc_transport_callbacks; + +/* 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; + +/* Represents the send/recv closed state of a stream. */ +typedef enum grpc_stream_state { + /* the stream is open for sends and receives */ + GRPC_STREAM_OPEN, + /* the stream is closed for sends, but may still receive data */ + GRPC_STREAM_SEND_CLOSED, + /* the stream is closed for receives, but may still send data */ + GRPC_STREAM_RECV_CLOSED, + /* the stream is closed for both sends and receives */ + GRPC_STREAM_CLOSED +} grpc_stream_state; + +/* Callbacks made from the transport to the upper layers of grpc. */ +struct grpc_transport_callbacks { + /* Allocate a buffer to receive data into. + It's safe to call grpc_slice_new() to do this, but performance minded + proxies may want to carefully place data into optimal locations for + transports. + This function must return a valid, non-empty slice. + + Arguments: + user_data - the transport user data set at transport creation time + transport - the grpc_transport instance making this call + stream - the grpc_stream instance the buffer will be used for, or + NULL if this is not known + size_hint - how big of a buffer would the transport optimally like? + the actual returned buffer can be smaller or larger than + size_hint as the implementation finds convenient */ + struct gpr_slice (*alloc_recv_buffer)(void *user_data, + grpc_transport *transport, + grpc_stream *stream, size_t size_hint); + + /* Initialize a new stream on behalf of the transport. + Must result in a call to + grpc_transport_init_stream(transport, ..., request) in the same call + stack. + Must not result in any other calls to the transport. + + Arguments: + user_data - the transport user data set at transport creation time + transport - the grpc_transport instance making this call + request - request parameters for this stream (owned by the caller) + server_data - opaque transport dependent argument that should be passed + to grpc_transport_init_stream + */ + void (*accept_stream)(void *user_data, grpc_transport *transport, + const void *server_data); + + /* Process a set of stream ops that have been received by the transport. + Called by network threads, so must be careful not to block on network + activity. + + If final_state == GRPC_STREAM_CLOSED, the upper layers should arrange to + call grpc_transport_destroy_stream. + + Ownership of any objects contained in ops is transferred to the callee. + + Arguments: + user_data - the transport user data set at transport creation time + transport - the grpc_transport instance making this call + stream - the stream this data was received for + ops - stream operations that are part of this batch + ops_count - the number of stream operations in this batch + final_state - the state of the stream as of the final operation in this + batch */ + void (*recv_batch)(void *user_data, grpc_transport *transport, + grpc_stream *stream, grpc_stream_op *ops, size_t ops_count, + grpc_stream_state final_state); + + /* The transport has been closed */ + void (*closed)(void *user_data, grpc_transport *transport); +}; + +/* 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_transport *transport, grpc_stream *stream, + const void *server_data); + +/* 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_transport *transport, + grpc_stream *stream); + +/* Enable/disable incoming data for a stream. + + This effectively disables new window becoming available for a given stream, + but does not prevent existing window from being consumed by a sender: the + caller must still be prepared to receive some additional data after this + call. + + 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) + allow - is it allowed that new window be opened up? */ +void grpc_transport_set_allow_window_updates(grpc_transport *transport, + grpc_stream *stream, int allow); + +/* 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. + ops - an array of operations to apply to the stream - can be NULL + if ops_count == 0. + ops_count - the number of elements in ops + is_last - is this the last batch of operations to be sent out */ +void grpc_transport_send_batch(grpc_transport *transport, grpc_stream *stream, + grpc_stream_op *ops, size_t ops_count, + int is_last); + +/* Send a ping on a transport + + Calls cb with user data when a response is received. + cb *MAY* be called with arbitrary transport level locks held. It is not safe + to call into the transport during cb. */ +void grpc_transport_ping(grpc_transport *transport, void (*cb)(void *user_data), + void *user_data); + +/* Abort a stream + + Terminate reading and writing for a stream. A final recv_batch with no + operations and final_state == GRPC_STREAM_CLOSED will be received locally, + and no more data will be presented to the up-layer. + + TODO(ctiller): consider adding a HTTP/2 reason to this function. */ +void grpc_transport_abort_stream(grpc_transport *transport, grpc_stream *stream, + grpc_status_code status); + +/* Close a transport. Aborts all open streams. */ +void grpc_transport_close(struct grpc_transport *transport); + +/* Destroy the transport */ +void grpc_transport_destroy(struct grpc_transport *transport); + +/* Return type for grpc_transport_setup_callback */ +typedef struct grpc_transport_setup_result { + void *user_data; + const grpc_transport_callbacks *callbacks; +} grpc_transport_setup_result; + +/* Given a transport, return callbacks for that transport. Used to finalize + setup as a transport is being created */ +typedef grpc_transport_setup_result (*grpc_transport_setup_callback)( + void *setup_arg, grpc_transport *transport, grpc_mdctx *mdctx); + +typedef struct grpc_transport_setup grpc_transport_setup; +typedef struct grpc_transport_setup_vtable grpc_transport_setup_vtable; + +struct grpc_transport_setup_vtable { + void (*initiate)(grpc_transport_setup *setup); + void (*cancel)(grpc_transport_setup *setup); +}; + +/* Transport setup is an asynchronous utility interface for client channels to + establish connections. It's transport agnostic. */ +struct grpc_transport_setup { + const grpc_transport_setup_vtable *vtable; +}; + +/* Initiate transport setup: e.g. for TCP+DNS trigger a resolve of the name + given at transport construction time, create the tcp connection, perform + handshakes, and call some grpc_transport_setup_result function provided at + setup construction time. + This *may* be implemented as a no-op if the setup process monitors something + continuously. */ +void grpc_transport_setup_initiate(grpc_transport_setup *setup); +/* Cancel transport setup. After this returns, no new transports should be + created, and all pending transport setup callbacks should be completed. + After this call completes, setup should be considered invalid (this can be + used as a destruction call by setup). */ +void grpc_transport_setup_cancel(grpc_transport_setup *setup); + +#endif /* __GRPC_INTERNAL_TRANSPORT_TRANSPORT_H__ */ diff --git a/src/core/transport/transport_impl.h b/src/core/transport/transport_impl.h new file mode 100644 index 0000000000..6acdbf2525 --- /dev/null +++ b/src/core/transport/transport_impl.h @@ -0,0 +1,80 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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_INTERNAL_TRANSPORT_TRANSPORT_IMPL_H__ +#define __GRPC_INTERNAL_TRANSPORT_TRANSPORT_IMPL_H__ + +#include "src/core/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) */ + + /* implementation of grpc_transport_init_stream */ + int (*init_stream)(grpc_transport *self, grpc_stream *stream, + const void *server_data); + + /* implementation of grpc_transport_send_batch */ + void (*send_batch)(grpc_transport *self, grpc_stream *stream, + grpc_stream_op *ops, size_t ops_count, int is_last); + + /* implementation of grpc_transport_set_allow_window_updates */ + void (*set_allow_window_updates)(grpc_transport *self, grpc_stream *stream, + int allow); + + /* implementation of grpc_transport_destroy_stream */ + void (*destroy_stream)(grpc_transport *self, grpc_stream *stream); + + /* implementation of grpc_transport_abort_stream */ + void (*abort_stream)(grpc_transport *self, grpc_stream *stream, + grpc_status_code status); + + /* implementation of grpc_transport_close */ + void (*close)(grpc_transport *self); + + /* implementation of grpc_transport_ping */ + void (*ping)(grpc_transport *self, void (*cb)(void *user_data), + void *user_data); + + /* implementation of grpc_transport_destroy */ + void (*destroy)(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_INTERNAL_TRANSPORT_TRANSPORT_IMPL_H__ */ diff --git a/src/core/tsi/fake_transport_security.c b/src/core/tsi/fake_transport_security.c new file mode 100644 index 0000000000..7807e71949 --- /dev/null +++ b/src/core/tsi/fake_transport_security.c @@ -0,0 +1,515 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/tsi/fake_transport_security.h" + +#include <stdlib.h> +#include <string.h> + +#include <grpc/support/log.h> +#include "src/core/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; + uint32_t size; + uint32_t allocated_size; + uint32_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; + uint32_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) { + int 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]))) { + *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, + uint32_t* incoming_bytes_size, + tsi_fake_frame* frame) { + uint32_t available_size = *incoming_bytes_size; + uint32_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 = 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 = 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 = 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, + uint32_t* outgoing_bytes_size, + tsi_fake_frame* frame) { + uint32_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, uint32_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(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, + uint32_t* unprotected_bytes_size, unsigned char* protected_output_frames, + uint32_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; + uint32_t saved_output_size = *protected_output_frames_size; + uint32_t drained_size = 0; + uint32_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. */ + uint32_t written_in_frame_size = 0; + store32_little_endian(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, + uint32_t* protected_output_frames_size, uint32_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(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, + uint32_t* protected_frames_bytes_size, unsigned char* unprotected_bytes, + uint32_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; + uint32_t saved_output_size = *unprotected_bytes_size; + uint32_t drained_size = 0; + uint32_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, uint32_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) { + int 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; + } + 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. */ + 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, uint32_t* bytes_size) { + tsi_result result = TSI_OK; + tsi_fake_handshaker* impl = (tsi_fake_handshaker*)self; + int 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)); + } + 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. */ + 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, uint32_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( + uint32_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/tsi/fake_transport_security.h b/src/core/tsi/fake_transport_security.h new file mode 100644 index 0000000000..075d51871b --- /dev/null +++ b/src/core/tsi/fake_transport_security.h @@ -0,0 +1,62 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 __FAKE_TRANSPORT_SECURITY_H_ +#define __FAKE_TRANSPORT_SECURITY_H_ + +#include "src/core/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( + uint32_t* max_protected_frame_size); + +#ifdef __cplusplus +} +#endif + +#endif /* __FAKE_TRANSPORT_SECURITY_H_ */ diff --git a/src/core/tsi/fake_transport_security_test.cc b/src/core/tsi/fake_transport_security_test.cc new file mode 100644 index 0000000000..0ae88e0c9a --- /dev/null +++ b/src/core/tsi/fake_transport_security_test.cc @@ -0,0 +1,151 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/tsi/fake_transport_security.h" + +#include "src/core/tsi/transport_security_test_lib.h" +#include <gtest/gtest.h> +#include "util/random/permute-inl.h" + +namespace { + +void CheckStringPeerProperty(const tsi_peer& peer, int property_index, + const char* expected_name, + const char* expected_value) { + EXPECT_LT(property_index, peer.property_count); + const tsi_peer_property* property = &peer.properties[property_index]; + EXPECT_EQ(TSI_PEER_PROPERTY_TYPE_STRING, property->type); + EXPECT_EQ(string(expected_name), string(property->name)); + EXPECT_EQ(string(expected_value), + string(property->value.string.data, property->value.string.length)); +} + +class FakeTransportSecurityTest : public tsi::test::TransportSecurityTest { + protected: + void SetupHandshakers() override { + client_handshaker_.reset(tsi_create_fake_handshaker(1)); + server_handshaker_.reset(tsi_create_fake_handshaker(0)); + } + + void CheckPeer(tsi_handshaker* handshaker) { + tsi_peer peer; + EXPECT_EQ(TSI_OK, tsi_handshaker_extract_peer(handshaker, &peer)); + EXPECT_EQ(1, peer.property_count); + CheckStringPeerProperty(peer, 0, TSI_CERTIFICATE_TYPE_PEER_PROPERTY, + TSI_FAKE_CERTIFICATE_TYPE); + tsi_peer_destruct(&peer); + } + + void CheckHandshakeResults() override { + CheckPeer(client_handshaker_.get()); + CheckPeer(server_handshaker_.get()); + } + + const tsi::test::TestConfig* config() { + return &config_; + } + + tsi::test::TestConfig config_; +}; + +TEST_F(FakeTransportSecurityTest, Handshake) { + PerformHandshake(); +} + +TEST_F(FakeTransportSecurityTest, HandshakeSmallBuffer) { + config_.handshake_buffer_size = 3; + PerformHandshake(); +} +TEST_F(FakeTransportSecurityTest, PingPong) { + PingPong(); +} + +TEST_F(FakeTransportSecurityTest, RoundTrip) { + config_.client_message = big_message_; + config_.server_message = small_message_; + DoRoundTrip(); +} + +TEST_F(FakeTransportSecurityTest, RoundTripSmallMessageBuffer) { + config_.message_buffer_allocated_size = 42; + config_.client_message = big_message_; + config_.server_message = small_message_; + DoRoundTrip(); +} + +TEST_F(FakeTransportSecurityTest, RoundTripSmallProtectedBufferSize) { + config_.protected_buffer_size = 37; + config_.client_message = big_message_; + config_.server_message = small_message_; + DoRoundTrip(); +} + +TEST_F(FakeTransportSecurityTest, RoundTripSmallReadBufferSize) { + config_.read_buffer_allocated_size = 41; + config_.client_message = big_message_; + config_.server_message = small_message_; + DoRoundTrip(); +} + +TEST_F(FakeTransportSecurityTest, RoundTripSmallClientFrames) { + config_.set_client_max_output_protected_frame_size(39); + config_.client_message = big_message_; + config_.server_message = small_message_; + DoRoundTrip(); +} + +TEST_F(FakeTransportSecurityTest, RoundTripSmallServerFrames) { + config_.set_server_max_output_protected_frame_size(43); + config_.client_message = small_message_; + config_.server_message = big_message_; + DoRoundTrip(); +} + +TEST_F(FakeTransportSecurityTest, RoundTripOddBufferSizes) { + int odd_sizes[] = {33, 67, 135, 271, 523}; + RandomPermutation<int> permute(odd_sizes, arraysize(odd_sizes), + random_.get()); + permute.Permute(); + LOG(ERROR) << odd_sizes[0] << "\t" << odd_sizes[1] << "\t" << odd_sizes[2] + << "\t" << odd_sizes[3] << "\t" << odd_sizes[4]; + config_.message_buffer_allocated_size = odd_sizes[0]; + config_.protected_buffer_size = odd_sizes[1]; + config_.read_buffer_allocated_size = odd_sizes[2]; + config_.set_client_max_output_protected_frame_size(odd_sizes[3]); + config_.set_server_max_output_protected_frame_size(odd_sizes[4]); + config_.client_message = big_message_; + config_.server_message = small_message_; + DoRoundTrip(); +} + +} // namespace diff --git a/src/core/tsi/ssl_transport_security.c b/src/core/tsi/ssl_transport_security.c new file mode 100644 index 0000000000..7bd178b372 --- /dev/null +++ b/src/core/tsi/ssl_transport_security.c @@ -0,0 +1,1294 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/tsi/ssl_transport_security.h" + +#include <limits.h> +#include <pthread.h> + +#include <grpc/support/log.h> +#include "src/core/tsi/transport_security.h" + +#include <openssl/bio.h> +#include <openssl/err.h> +#include <openssl/ssl.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> + +/* --- Constants. ---*/ + +#define TSI_SSL_MAX_PROTECTED_FRAME_SIZE_UPPER_BOUND 16384 +#define TSI_SSL_MAX_PROTECTED_FRAME_SIZE_LOWER_BOUND 1024 + +/* 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; +} 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; + uint32_t ssl_context_count; + unsigned char* alpn_protocol_list; + uint32_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; + uint32_t buffer_size; + uint32_t buffer_offset; +} tsi_ssl_frame_protector; + + +/* --- Library Initialization. ---*/ + +static pthread_once_t init_openssl_once = PTHREAD_ONCE_INIT; + +static void init_openssl(void) { + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); +} + +/* --- 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) { + 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"); +} + +/* Gets the subject CN from an X509 cert. */ +static tsi_result ssl_get_x509_common_name(X509* cert, unsigned char** utf8, + uint32_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 = 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; + uint32_t common_name_size; + tsi_result result = + ssl_get_x509_common_name(cert, &common_name, &common_name_size); + if (result != TSI_OK) return result; + result = tsi_construct_string_peer_property( + TSI_X509_SUBJECT_COMMON_NAME_PEER_PROPERTY, (const char*)common_name, + common_name_size, property); + OPENSSL_free(common_name); + return result; +} + +/* Gets the subject SANs from an X509 cert as a tsi_peer_property. */ +static tsi_result peer_property_from_x509_subject_alt_names( + X509* cert, tsi_peer_property* property) { + int i = 0; + int subject_alt_name_count = 0; + tsi_result result = TSI_OK; + GENERAL_NAMES* subject_alt_names = + X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0); + if (subject_alt_names == NULL) { + /* Empty list. */ + return tsi_construct_list_peer_property( + TSI_X509_SUBJECT_ALTERNATIVE_NAMES_PEER_PROPERTY, 0, property); + } + + subject_alt_name_count = sk_GENERAL_NAME_num(subject_alt_names); + result = tsi_construct_list_peer_property( + TSI_X509_SUBJECT_ALTERNATIVE_NAMES_PEER_PROPERTY, subject_alt_name_count, + property); + if (result != TSI_OK) return result; + + /* Reset for DNS entries filtering. */ + subject_alt_name_count = property->value.list.child_count; + property->value.list.child_count = 0; + + for (i = 0; i < subject_alt_name_count; i++) { + tsi_peer_property* child_property = NULL; + GENERAL_NAME* subject_alt_name = + sk_GENERAL_NAME_value(subject_alt_names, i); + /* Filter out the non-dns entries names. */ + if (subject_alt_name->type == GEN_DNS) { + unsigned char* dns_name = NULL; + int dns_name_size = + ASN1_STRING_to_UTF8(&dns_name, subject_alt_name->d.dNSName); + if (dns_name_size < 0) { + gpr_log(GPR_ERROR, "Could not get utf8 from asn1 string."); + result = TSI_INTERNAL_ERROR; + break; + } + child_property = + &property->value.list.children[property->value.list.child_count++]; + result = tsi_construct_string_peer_property( + NULL, (const char*)dns_name, dns_name_size, child_property); + OPENSSL_free(dns_name); + if (result != TSI_OK) break; + } + } + if (result != TSI_OK) tsi_peer_property_destruct(property); + sk_GENERAL_NAME_pop_free(subject_alt_names, GENERAL_NAME_free); + return TSI_OK; +} + +/* 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. */ + uint32_t property_count = include_certificate_type ? 3 : 2; + tsi_result result = tsi_construct_peer(property_count, peer); + if (result != TSI_OK) return result; + do { + result = peer_property_from_x509_common_name(cert, &peer->properties[0]); + if (result != TSI_OK) break; + result = + peer_property_from_x509_subject_alt_names(cert, &peer->properties[1]); + if (result != TSI_OK) break; + if (include_certificate_type) { + result = tsi_construct_string_peer_property_from_cstring( + TSI_CERTIFICATE_TYPE_PEER_PROPERTY, TSI_X509_CERTIFICATE_TYPE, + &peer->properties[2]); + if (result != TSI_OK) break; + } + } while (0); + + if (result != TSI_OK) tsi_peer_destruct(peer); + return result; +} + +/* Performs an SSL_read and handle errors. */ +static tsi_result do_ssl_read(SSL* ssl, unsigned char* unprotected_bytes, + uint32_t* unprotected_bytes_size) { + int read_from_ssl = SSL_read(ssl, unprotected_bytes, + *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."); + 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 = 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, + uint32_t unprotected_bytes_size) { + int ssl_write_result = + SSL_write(ssl, unprotected_bytes, 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, + uint32_t pem_cert_chain_size) { + tsi_result result = TSI_OK; + X509* certificate = NULL; + BIO* pem = BIO_new_mem_buf((void*)pem_cert_chain, 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) 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, + uint32_t pem_key_size) { + tsi_result result = TSI_OK; + EVP_PKEY* private_key = NULL; + BIO* pem = BIO_new_mem_buf((void*)pem_key, 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, + uint32_t pem_roots_size, STACK_OF(X509_NAME)** root_names) { + tsi_result result = TSI_OK; + uint32_t num_roots = 0; + X509* root = NULL; + X509_NAME* root_name = NULL; + BIO* pem = BIO_new_mem_buf((void*)pem_roots, pem_roots_size); + X509_STORE* 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) 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, + uint32_t pem_private_key_size, + const unsigned char* pem_certificate_chain, + uint32_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."); + result = 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, uint32_t pem_cert_size, tsi_peer* peer) { + tsi_result result = TSI_OK; + X509* cert = NULL; + BIO* pem = BIO_new_mem_buf((void*)pem_cert, 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, uint32_t* protocol_name_list_length) { + uint16_t i; + unsigned char* current; + *protocol_name_list = NULL; + *protocol_name_list_length = 0; + 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 += 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) != *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, + uint32_t* unprotected_bytes_size, + unsigned char* protected_output_frames, + uint32_t* protected_output_frames_size) { + tsi_ssl_frame_protector* impl = (tsi_ssl_frame_protector*)self; + int read_from_ssl; + uint32_t available; + tsi_result result = TSI_OK; + + /* First see if we have some pending data in the SSL BIO. */ + uint32_t pending_in_ssl = BIO_ctrl_pending(impl->from_ssl); + if (pending_in_ssl > 0) { + *unprotected_bytes_size = 0; + read_from_ssl = BIO_read(impl->from_ssl, protected_output_frames, + *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 = 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; + + read_from_ssl = BIO_read(impl->from_ssl, protected_output_frames, + *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 = 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, + uint32_t* protected_output_frames_size, + uint32_t* still_pending_size) { + tsi_result result = TSI_OK; + tsi_ssl_frame_protector* impl = (tsi_ssl_frame_protector*)self; + int read_from_ssl = 0; + + 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; + } + + *still_pending_size = BIO_ctrl_pending(impl->from_ssl); + if (*still_pending_size == 0) return TSI_OK; + + read_from_ssl = BIO_read(impl->from_ssl, protected_output_frames, + *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 = read_from_ssl; + *still_pending_size = BIO_ctrl_pending(impl->from_ssl); + return TSI_OK; +} + +static tsi_result ssl_protector_unprotect( + tsi_frame_protector* self, const unsigned char* protected_frames_bytes, + uint32_t* protected_frames_bytes_size, + unsigned char* unprotected_bytes, + uint32_t* unprotected_bytes_size) { + tsi_result result = TSI_OK; + int written_into_ssl = 0; + uint32_t output_bytes_size = *unprotected_bytes_size; + uint32_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. */ + written_into_ssl = BIO_write( + impl->into_ssl, protected_frames_bytes, *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 = 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, uint32_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; + } + bytes_read_from_ssl = BIO_read(impl->from_ssl, bytes, *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 = (uint32_t)bytes_read_from_ssl; + return BIO_ctrl_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, + uint32_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; + } + bytes_written_into_ssl_size = BIO_write(impl->into_ssl, bytes, *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 = 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_ctrl_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; + 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; + } + SSL_get0_alpn_selected(impl->ssl, &alpn_selected, &alpn_selected_len); + if (alpn_selected != NULL) { + uint32_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, uint32_t* max_output_protected_frame_size, + tsi_frame_protector** protector) { + uint32_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; +} + + +/* --- 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; + SSL_CTX_free(impl->ssl_context); + free(impl); +} + + +/* --- 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; + uint32_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, uint32_t entry_length, + const char* name) { + const char* name_subdomain = NULL; + if (entry_length == 0) return 0; + if (!strncmp(name, entry, entry_length) && (strlen(name) == entry_length)) { + 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 || strlen(name_subdomain) < 2) return 0; + name_subdomain++; /* Starts after the dot. */ + entry += 2; /* Remove *. */ + entry_length -= 2; + return (!strncmp(entry, name_subdomain, entry_length) && + (strlen(name_subdomain) == entry_length)); +} + +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; + uint32_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; +} + +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; + const unsigned char* client_current = in; + while ((client_current - in) < inlen) { + unsigned char client_current_len = *(client_current++); + const unsigned char* server_current = factory->alpn_protocol_list; + while ((server_current - factory->alpn_protocol_list) < + factory->alpn_protocol_list_length) { + 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_handshaker_factory constructors. --- */ + +tsi_result tsi_create_ssl_client_handshaker_factory( + const unsigned char* pem_private_key, uint32_t pem_private_key_size, + const unsigned char* pem_cert_chain, uint32_t pem_cert_chain_size, + const unsigned char* pem_root_certs, uint32_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; + + pthread_once(&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; + } + 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) { + unsigned char* alpn_protocol_list = NULL; + uint32_t alpn_protocol_list_length = 0; + int ssl_failed; + result = build_alpn_protocol_name_list( + alpn_protocols, alpn_protocols_lengths, num_alpn_protocols, + &alpn_protocol_list, &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; + } + ssl_failed = SSL_CTX_set_alpn_protos(ssl_context, alpn_protocol_list, + alpn_protocol_list_length); + free(alpn_protocol_list); + if (ssl_failed) { + gpr_log(GPR_ERROR, "Could not set alpn protocol list to context."); + result = TSI_INVALID_ARGUMENT; + break; + } + } + } while (0); + if (result != TSI_OK) { + SSL_CTX_free(ssl_context); + return result; + } + SSL_CTX_set_verify(ssl_context, SSL_VERIFY_PEER, NULL); + /* TODO(jboeuf): Add revocation verification. */ + + 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; + 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 uint32_t* pem_private_keys_sizes, + const unsigned char** pem_cert_chains, + const uint32_t* pem_cert_chains_sizes, uint32_t key_cert_pair_count, + const unsigned char* pem_client_root_certs, + uint32_t pem_client_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) { + tsi_ssl_server_handshaker_factory* impl = NULL; + tsi_result result = TSI_OK; + uint32_t i = 0; + + pthread_once(&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) { + 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); + SSL_CTX_set_verify(impl->ssl_contexts[i], SSL_VERIFY_PEER, 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); + SSL_CTX_set_alpn_select_cb(impl->ssl_contexts[i], + server_handshaker_factory_alpn_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) { + uint32_t i = 0; + const tsi_peer_property* property = tsi_peer_get_property_by_name( + peer, TSI_X509_SUBJECT_COMMON_NAME_PEER_PROPERTY); + if (property == NULL || + property->type != TSI_PEER_PROPERTY_TYPE_STRING) { + gpr_log(GPR_ERROR, + "Invalid x509 subject common name property."); + return 0; + } + if (does_entry_match_name(property->value.string.data, + property->value.string.length, name)) { + return 1; + } + + property = tsi_peer_get_property_by_name( + peer, TSI_X509_SUBJECT_ALTERNATIVE_NAMES_PEER_PROPERTY); + if (property == NULL || property->type != TSI_PEER_PROPERTY_TYPE_LIST) { + gpr_log(GPR_ERROR, + "Invalid x509 subject alternative names property."); + return 0; + } + + for (i = 0; i < property->value.list.child_count; i++) { + const tsi_peer_property* alt_name_property = + &property->value.list.children[i]; + if (alt_name_property->type != TSI_PEER_PROPERTY_TYPE_STRING) { + gpr_log(GPR_ERROR, "Invalid x509 subject alternative name property."); + return 0; + } + if (does_entry_match_name(alt_name_property->value.string.data, + alt_name_property->value.string.length, name)) { + return 1; + } + } + return 0; /* Not found. */ +} diff --git a/src/core/tsi/ssl_transport_security.h b/src/core/tsi/ssl_transport_security.h new file mode 100644 index 0000000000..2ed3ed861b --- /dev/null +++ b/src/core/tsi/ssl_transport_security.h @@ -0,0 +1,159 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 __SSL_TRANSPORT_SECURITY_H_ +#define __SSL_TRANSPORT_SECURITY_H_ + +#include "src/core/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" + +/* --- 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, uint32_t pem_private_key_size, + const unsigned char* pem_cert_chain, uint32_t pem_cert_chain_size, + const unsigned char* pem_root_certs, uint32_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 ask the client to authenticate itself with a certificate (server- + only authentication mode). + - pem_client_roots_size is the size of the associated buffer. + - 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 uint32_t* pem_private_keys_sizes, + const unsigned char** pem_cert_chains, + const uint32_t* pem_cert_chains_sizes, uint32_t key_cert_pair_count, + const unsigned char* pem_client_root_certs, + uint32_t pem_client_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 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. */ +int tsi_ssl_peer_matches_name(const tsi_peer* peer, const char* name); + + +#ifdef __cplusplus +} +#endif + +#endif /* __SSL_TRANSPORT_SECURITY_H_ */ diff --git a/src/core/tsi/ssl_transport_security_test.cc b/src/core/tsi/ssl_transport_security_test.cc new file mode 100644 index 0000000000..a759403126 --- /dev/null +++ b/src/core/tsi/ssl_transport_security_test.cc @@ -0,0 +1,534 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 <memory> + +#include "base/commandlineflags.h" +#include "file/base/helpers.h" +#include "file/base/options.pb.h" +#include "file/base/path.h" +#include "src/core/tsi/transport_security_test_lib.h" +#include "src/core/tsi/ssl_transport_security.h" +#include "util/random/permute-inl.h" + +namespace { + +const char kTestCredsDir[] = + "/internal/tsi/test_creds/"; + +enum AlpnMode { + NO_ALPN, + ALPN_CLIENT_NO_SERVER, + ALPN_SERVER_NO_CLIENT, + ALPN_CLIENT_SERVER_OK, + ALPN_CLIENT_SERVER_MISMATCH +}; + +class SslTestConfig : public tsi::test::TestConfig { + public: + SslTestConfig() + : do_client_authentication(false), + subject_name_indication(nullptr), + use_bad_client_cert(false), + use_bad_server_cert(false), + alpn_mode(NO_ALPN) {} + bool do_client_authentication; + const char* subject_name_indication; + bool use_bad_client_cert; + bool use_bad_server_cert; + AlpnMode alpn_mode; +}; + +struct TsiSslHandshakerFactoryDeleter { + inline void operator()(tsi_ssl_handshaker_factory* ptr) { + tsi_ssl_handshaker_factory_destroy(ptr); + } +}; +typedef std::unique_ptr<tsi_ssl_handshaker_factory, + TsiSslHandshakerFactoryDeleter> + TsiSslHandshakerFactoryUniquePtr; + +class SslTransportSecurityTest : public tsi::test::TransportSecurityTest { + protected: + void CheckSubjectAltName(const tsi_peer_property& property, + const string& expected_subject_alt_name) { + EXPECT_EQ(property.type, TSI_PEER_PROPERTY_TYPE_STRING); + EXPECT_EQ(property.name, nullptr); + EXPECT_EQ( + string(property.value.string.data, property.value.string.length), + expected_subject_alt_name); + } + + const tsi_peer_property* CheckBasicAuthenticatedPeerAndGetCommonName( + const tsi_peer* peer) { + const tsi_peer_property* property = + tsi_peer_get_property_by_name(peer, TSI_CERTIFICATE_TYPE_PEER_PROPERTY); + EXPECT_NE(property, nullptr); + EXPECT_EQ(property->type, TSI_PEER_PROPERTY_TYPE_STRING); + EXPECT_EQ( + string(property->value.string.data, property->value.string.length), + string(TSI_X509_CERTIFICATE_TYPE)); + property = tsi_peer_get_property_by_name( + peer, TSI_X509_SUBJECT_COMMON_NAME_PEER_PROPERTY); + EXPECT_EQ(property->type, TSI_PEER_PROPERTY_TYPE_STRING); + return property; + } + + void CheckServer0Peer(tsi_peer* peer) { + const tsi_peer_property* property = + CheckBasicAuthenticatedPeerAndGetCommonName(peer); + EXPECT_EQ( + string(property->value.string.data, property->value.string.length), + string("*.test.google.com.au")); + property = tsi_peer_get_property_by_name( + peer, TSI_X509_SUBJECT_ALTERNATIVE_NAMES_PEER_PROPERTY); + EXPECT_EQ(property->type, TSI_PEER_PROPERTY_TYPE_LIST); + EXPECT_EQ(property->value.list.child_count, 0); + EXPECT_EQ(1, tsi_ssl_peer_matches_name(peer, "foo.test.google.com.au")); + EXPECT_EQ(1, tsi_ssl_peer_matches_name(peer, "bar.test.google.com.au")); + EXPECT_EQ(0, tsi_ssl_peer_matches_name(peer, "bar.test.google.blah")); + EXPECT_EQ(0, tsi_ssl_peer_matches_name(peer, "foo.bar.test.google.com.au")); + EXPECT_EQ(0, tsi_ssl_peer_matches_name(peer, "test.google.com.au")); + tsi_peer_destruct(peer); + } + + void CheckServer1Peer(tsi_peer* peer) { + const tsi_peer_property* property = + CheckBasicAuthenticatedPeerAndGetCommonName(peer); + EXPECT_EQ( + string(property->value.string.data, property->value.string.length), + string("*.test.google.com")); + property = tsi_peer_get_property_by_name( + peer, TSI_X509_SUBJECT_ALTERNATIVE_NAMES_PEER_PROPERTY); + EXPECT_EQ(property->type, TSI_PEER_PROPERTY_TYPE_LIST); + EXPECT_EQ(property->value.list.child_count, 3); + CheckSubjectAltName(property->value.list.children[0], "*.test.google.fr"); + CheckSubjectAltName(property->value.list.children[1], + "waterzooi.test.google.be"); + CheckSubjectAltName(property->value.list.children[2], "*.test.youtube.com"); + EXPECT_EQ(1, tsi_ssl_peer_matches_name(peer, "foo.test.google.com")); + EXPECT_EQ(1, tsi_ssl_peer_matches_name(peer, "bar.test.google.fr")); + EXPECT_EQ(1, tsi_ssl_peer_matches_name(peer, "waterzooi.test.google.be")); + EXPECT_EQ(1, tsi_ssl_peer_matches_name(peer, "foo.test.youtube.com")); + EXPECT_EQ(0, tsi_ssl_peer_matches_name(peer, "bar.foo.test.google.com")); + EXPECT_EQ(0, tsi_ssl_peer_matches_name(peer, "test.google.fr")); + EXPECT_EQ(0, tsi_ssl_peer_matches_name(peer, "tartines.test.google.be")); + EXPECT_EQ(0, tsi_ssl_peer_matches_name(peer, "tartines.youtube.com")); + tsi_peer_destruct(peer); + } + + void CheckClientPeer(tsi_peer* peer, bool is_authenticated) { + if (!is_authenticated) { + EXPECT_EQ(peer->property_count, + config_.alpn_mode == ALPN_CLIENT_SERVER_OK ? 1 : 0); + } else { + const tsi_peer_property* property = + CheckBasicAuthenticatedPeerAndGetCommonName(peer); + EXPECT_EQ( + string(property->value.string.data, property->value.string.length), + string("testclient")); + } + tsi_peer_destruct(peer); + } + + void SetupHandshakers() override { + tsi_ssl_handshaker_factory* client_handshaker_factory; + const unsigned char* client_cert = NULL; + unsigned int client_cert_size = 0; + const unsigned char* client_key = NULL; + unsigned int client_key_size = 0; + if (config_.do_client_authentication) { + if (config_.use_bad_client_cert) { + client_cert = + reinterpret_cast<const unsigned char*>(badclient_cert_.data()); + client_cert_size = badclient_cert_.size(); + client_key = + reinterpret_cast<const unsigned char*>(badclient_key_.data()); + client_key_size = badclient_key_.size(); + } else { + client_cert = + reinterpret_cast<const unsigned char*>(client_cert_.data()); + client_cert_size = client_cert_.size(); + client_key = reinterpret_cast<const unsigned char*>(client_key_.data()); + client_key_size = client_key_.size(); + } + } + const unsigned char** client_alpn_protocols(nullptr); + const unsigned char* client_alpn_protocols_lengths(nullptr); + uint16_t num_client_alpn_protocols = 0; + if (config_.alpn_mode == ALPN_CLIENT_NO_SERVER || + config_.alpn_mode == ALPN_CLIENT_SERVER_OK || + config_.alpn_mode == ALPN_CLIENT_SERVER_MISMATCH) { + client_alpn_protocols = + reinterpret_cast<const unsigned char**>(&client_alpn_protocols_[0]); + client_alpn_protocols_lengths = &client_alpn_protocols_lengths_[0]; + num_client_alpn_protocols = client_alpn_protocols_.size(); + } + + EXPECT_EQ(tsi_create_ssl_client_handshaker_factory( + client_key, client_key_size, client_cert, client_cert_size, + reinterpret_cast<const unsigned char*>(root_certs_.data()), + root_certs_.size(), NULL, client_alpn_protocols, + client_alpn_protocols_lengths, num_client_alpn_protocols, + &client_handshaker_factory), + TSI_OK); + client_handshaker_factory_.reset(client_handshaker_factory); + + const unsigned char** server_alpn_protocols(nullptr); + const unsigned char* server_alpn_protocols_lengths(nullptr); + uint16_t num_server_alpn_protocols = 0; + if (config_.alpn_mode == ALPN_SERVER_NO_CLIENT || + config_.alpn_mode == ALPN_CLIENT_SERVER_OK || + config_.alpn_mode == ALPN_CLIENT_SERVER_MISMATCH) { + server_alpn_protocols = + reinterpret_cast<const unsigned char**>(&server_alpn_protocols_[0]); + server_alpn_protocols_lengths = &server_alpn_protocols_lengths_[0]; + num_server_alpn_protocols = server_alpn_protocols_.size(); + if (config_.alpn_mode == ALPN_CLIENT_SERVER_MISMATCH) { + // Remove the last element that is common. + num_server_alpn_protocols--; + } + } + tsi_ssl_handshaker_factory* server_handshaker_factory; + EXPECT_EQ( + tsi_create_ssl_server_handshaker_factory( + config_.use_bad_server_cert ? &badserver_keys_[0] + : &server_keys_[0], + config_.use_bad_server_cert ? &badserver_keys_sizes_[0] + : &server_keys_sizes_[0], + config_.use_bad_server_cert ? &badserver_certs_[0] + : &server_certs_[0], + config_.use_bad_server_cert ? &badserver_certs_sizes_[0] + : &server_certs_sizes_[0], + config_.use_bad_server_cert ? badserver_keys_.size() + : server_keys_.size(), + config_.do_client_authentication + ? reinterpret_cast<const unsigned char*>(root_certs_.data()) + : NULL, + config_.do_client_authentication ? root_certs_.size() : 0, NULL, + server_alpn_protocols, server_alpn_protocols_lengths, + num_server_alpn_protocols, &server_handshaker_factory), + TSI_OK); + server_handshaker_factory_.reset(server_handshaker_factory); + + tsi_handshaker* client_handshaker; + EXPECT_EQ(tsi_ssl_handshaker_factory_create_handshaker( + client_handshaker_factory, config_.subject_name_indication, + &client_handshaker), + TSI_OK); + client_handshaker_.reset(client_handshaker); + + tsi_handshaker* server_handshaker; + EXPECT_EQ(tsi_ssl_handshaker_factory_create_handshaker( + server_handshaker_factory, NULL, &server_handshaker), + TSI_OK); + server_handshaker_.reset(server_handshaker); + } + + void CheckAlpn(const tsi_peer* peer) { + const tsi_peer_property* alpn_property = + tsi_peer_get_property_by_name(peer, TSI_SSL_ALPN_SELECTED_PROTOCOL); + if (config_.alpn_mode != ALPN_CLIENT_SERVER_OK) { + EXPECT_EQ(nullptr, alpn_property); + } else { + EXPECT_NE(nullptr, alpn_property); + EXPECT_EQ(TSI_PEER_PROPERTY_TYPE_STRING, alpn_property->type); + string expected_match("baz"); + EXPECT_EQ(expected_match, string(alpn_property->value.string.data, + alpn_property->value.string.length)); + } + } + + void CheckHandshakeResults() override { + tsi_peer peer; + + bool expect_success = + !(config_.use_bad_server_cert || + (config_.use_bad_client_cert && config_.do_client_authentication)); + tsi_result result = tsi_handshaker_get_result(client_handshaker_.get()); + EXPECT_NE(result, TSI_HANDSHAKE_IN_PROGRESS); + if (expect_success) { + EXPECT_EQ(result, TSI_OK); + EXPECT_EQ(tsi_handshaker_extract_peer(client_handshaker_.get(), &peer), + TSI_OK); + CheckAlpn(&peer); + // TODO(jboeuf): This is a bit fragile. Maybe revisit. + if (config_.subject_name_indication != nullptr) { + CheckServer1Peer(&peer); + } else { + CheckServer0Peer(&peer); + } + } else { + EXPECT_NE(result, TSI_OK); + EXPECT_NE(tsi_handshaker_extract_peer(client_handshaker_.get(), &peer), + TSI_OK); + } + + result = tsi_handshaker_get_result(server_handshaker_.get()); + EXPECT_NE(result, TSI_HANDSHAKE_IN_PROGRESS); + if (expect_success) { + EXPECT_EQ(result, TSI_OK); + EXPECT_EQ(tsi_handshaker_extract_peer(server_handshaker_.get(), &peer), + TSI_OK); + CheckAlpn(&peer); + CheckClientPeer(&peer, config_.do_client_authentication); + } else { + EXPECT_NE(result, TSI_OK); + EXPECT_NE(tsi_handshaker_extract_peer(server_handshaker_.get(), &peer), + TSI_OK); + } + } + + const tsi::test::TestConfig* config() override { + return &config_; + } + + SslTransportSecurityTest() + : client_alpn_protocols_({"foo", "toto", "baz"}), + server_alpn_protocols_({"boooo", "far", "baz"}), + client_alpn_protocols_lengths_({3, 4, 3}), + server_alpn_protocols_lengths_({5, 3, 3}) { + CHECK_OK(file::GetContents( + file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "badserver.key"), + &badserver_key_, file::Options())); + CHECK_OK(file::GetContents( + file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "badserver.pem"), + &badserver_cert_, file::Options())); + CHECK_OK(file::GetContents( + file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "badclient.key"), + &badclient_key_, file::Options())); + CHECK_OK(file::GetContents( + file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "badclient.pem"), + &badclient_cert_, file::Options())); + CHECK_OK(file::GetContents( + file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "server0.key"), + &server0_key_, file::Options())); + CHECK_OK(file::GetContents( + file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "server0.pem"), + &server0_cert_, file::Options())); + CHECK_OK(file::GetContents( + file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "server1.key"), + &server1_key_, file::Options())); + CHECK_OK(file::GetContents( + file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "server1.pem"), + &server1_cert_, file::Options())); + CHECK_OK(file::GetContents( + file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "client.key"), + &client_key_, file::Options())); + CHECK_OK(file::GetContents( + file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "client.pem"), + &client_cert_, file::Options())); + CHECK_OK(file::GetContents( + file::JoinPath(FLAGS_test_srcdir, kTestCredsDir, "ca.pem"), + &root_certs_, file::Options())); + badserver_keys_.push_back( + reinterpret_cast<const unsigned char*>(badserver_key_.data())); + badserver_certs_.push_back( + reinterpret_cast<const unsigned char*>(badserver_cert_.data())); + server_keys_.push_back( + reinterpret_cast<const unsigned char*>(server0_key_.data())); + server_keys_.push_back( + reinterpret_cast<const unsigned char*>(server1_key_.data())); + server_certs_.push_back( + reinterpret_cast<const unsigned char*>(server0_cert_.data())); + server_certs_.push_back( + reinterpret_cast<const unsigned char*>(server1_cert_.data())); + badserver_keys_sizes_.push_back(badserver_key_.size()); + badserver_certs_sizes_.push_back(badserver_cert_.size()); + server_keys_sizes_.push_back(server0_key_.size()); + server_keys_sizes_.push_back(server1_key_.size()); + server_certs_sizes_.push_back(server0_cert_.size()); + server_certs_sizes_.push_back(server1_cert_.size()); + } + + string badserver_key_; + string badserver_cert_; + string badclient_key_; + string badclient_cert_; + string server0_key_; + string server0_cert_; + string server1_key_; + string server1_cert_; + string client_key_; + string client_cert_; + string root_certs_; + std::vector<const unsigned char*> badserver_keys_; + std::vector<const unsigned char*> badserver_certs_; + std::vector<const unsigned char*> server_keys_; + std::vector<const unsigned char*> server_certs_; + std::vector<unsigned int> badserver_keys_sizes_; + std::vector<unsigned int> badserver_certs_sizes_; + std::vector<unsigned int> server_keys_sizes_; + std::vector<unsigned int> server_certs_sizes_; + TsiSslHandshakerFactoryUniquePtr client_handshaker_factory_; + TsiSslHandshakerFactoryUniquePtr server_handshaker_factory_; + std::vector<const char*> client_alpn_protocols_; + std::vector<const char*> server_alpn_protocols_; + std::vector<unsigned char> client_alpn_protocols_lengths_; + std::vector<unsigned char> server_alpn_protocols_lengths_; + string matched_alpn_; + SslTestConfig config_; +}; + + +TEST_F(SslTransportSecurityTest, LoadInvalidRoots) { + tsi_ssl_handshaker_factory* client_handshaker_factory; + string invalid_roots("Invalid roots!"); + EXPECT_EQ( + TSI_INVALID_ARGUMENT, + tsi_create_ssl_client_handshaker_factory( + NULL, 0, NULL, 0, + reinterpret_cast<const unsigned char*>(invalid_roots.data()), + invalid_roots.size(), NULL, NULL, 0, 0, &client_handshaker_factory)); +} + +TEST_F(SslTransportSecurityTest, Handshake) { + PerformHandshake(); +} + +TEST_F(SslTransportSecurityTest, HandshakeClientAuthentication) { + config_.do_client_authentication = true; + PerformHandshake(); +} + +TEST_F(SslTransportSecurityTest, HandshakeSmallBuffer) { + config_.handshake_buffer_size = 128; + PerformHandshake(); +} + +TEST_F(SslTransportSecurityTest, HandshakeSNIExactDomain) { + // server1 cert contains waterzooi.test.google.be in SAN. + config_.subject_name_indication = "waterzooi.test.google.be"; + PerformHandshake(); +} + +TEST_F(SslTransportSecurityTest, HandshakeSNIWildstarDomain) { + // server1 cert contains *.test.google.fr in SAN. + config_.subject_name_indication = "juju.test.google.fr"; + PerformHandshake(); +} + +TEST_F(SslTransportSecurityTest, BadServerCertFailure) { + config_.use_bad_server_cert = true; + PerformHandshake(); +} + +TEST_F(SslTransportSecurityTest, BadClientCertFailure) { + config_.use_bad_client_cert = true; + config_.do_client_authentication = true; + PerformHandshake(); +} + +TEST_F(SslTransportSecurityTest, AlpnClientNoServer) { + config_.alpn_mode = ALPN_CLIENT_NO_SERVER; + PerformHandshake(); +} + +TEST_F(SslTransportSecurityTest, AlpnServerNoClient) { + config_.alpn_mode = ALPN_SERVER_NO_CLIENT; + PerformHandshake(); +} + +TEST_F(SslTransportSecurityTest, AlpnClientServeMismatch) { + config_.alpn_mode = ALPN_CLIENT_SERVER_MISMATCH; + PerformHandshake(); +} + +TEST_F(SslTransportSecurityTest, AlpnClientServerOk) { + config_.alpn_mode = ALPN_CLIENT_SERVER_OK; + PerformHandshake(); +} + +TEST_F(SslTransportSecurityTest, PingPong) { + PingPong(); +} + +TEST_F(SslTransportSecurityTest, RoundTrip) { + config_.client_message = big_message_; + config_.server_message = small_message_; + DoRoundTrip(); +} + +TEST_F(SslTransportSecurityTest, RoundTripSmallMessageBuffer) { + config_.message_buffer_allocated_size = 42; + config_.client_message = big_message_; + config_.server_message = small_message_; + DoRoundTrip(); +} + +TEST_F(SslTransportSecurityTest, RoundTripSmallProtectedBufferSize) { + config_.protected_buffer_size = 37; + config_.client_message = big_message_; + config_.server_message = small_message_; + DoRoundTrip(); +} + +TEST_F(SslTransportSecurityTest, RoundTripSmallReadBufferSize) { + config_.read_buffer_allocated_size = 41; + config_.client_message = big_message_; + config_.server_message = small_message_; + DoRoundTrip(); +} + +TEST_F(SslTransportSecurityTest, RoundTripSmallClientFrames) { + config_.set_client_max_output_protected_frame_size(39); + config_.client_message = big_message_; + config_.server_message = small_message_; + DoRoundTrip(); +} + +TEST_F(SslTransportSecurityTest, RoundTripSmallServerFrames) { + config_.set_server_max_output_protected_frame_size(43); + config_.client_message = small_message_; + config_.server_message = big_message_; + DoRoundTrip(); +} + +TEST_F(SslTransportSecurityTest, RoundTripOddBufferSizes) { + int odd_sizes[] = {33, 67, 135, 271, 523}; + RandomPermutation<int> permute(odd_sizes, arraysize(odd_sizes), + random_.get()); + permute.Permute(); + LOG(ERROR) << odd_sizes[0] << "\t" << odd_sizes[1] << "\t" << odd_sizes[2] + << "\t" << odd_sizes[3] << "\t" << odd_sizes[4]; + config_.message_buffer_allocated_size = odd_sizes[0]; + config_.protected_buffer_size = odd_sizes[1]; + config_.read_buffer_allocated_size = odd_sizes[2]; + config_.set_client_max_output_protected_frame_size(odd_sizes[3]); + config_.set_server_max_output_protected_frame_size(odd_sizes[4]); + config_.client_message = big_message_; + config_.server_message = small_message_; + DoRoundTrip(); +} + +} // namespace diff --git a/src/core/tsi/test_creds/README b/src/core/tsi/test_creds/README new file mode 100644 index 0000000000..eb8482d648 --- /dev/null +++ b/src/core/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/tsi/test_creds/badclient.key b/src/core/tsi/test_creds/badclient.key new file mode 100644 index 0000000000..5832685122 --- /dev/null +++ b/src/core/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/tsi/test_creds/badclient.pem b/src/core/tsi/test_creds/badclient.pem new file mode 100644 index 0000000000..1785970221 --- /dev/null +++ b/src/core/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/tsi/test_creds/badserver.key b/src/core/tsi/test_creds/badserver.key new file mode 100644 index 0000000000..abfbde10ff --- /dev/null +++ b/src/core/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/tsi/test_creds/badserver.pem b/src/core/tsi/test_creds/badserver.pem new file mode 100644 index 0000000000..983c979f31 --- /dev/null +++ b/src/core/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/tsi/test_creds/ca-openssl.cnf b/src/core/tsi/test_creds/ca-openssl.cnf new file mode 100644 index 0000000000..e97b945e4b --- /dev/null +++ b/src/core/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/tsi/test_creds/ca.key b/src/core/tsi/test_creds/ca.key new file mode 100644 index 0000000000..03c4f950e3 --- /dev/null +++ b/src/core/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/tsi/test_creds/ca.pem b/src/core/tsi/test_creds/ca.pem new file mode 100644 index 0000000000..6c8511a73c --- /dev/null +++ b/src/core/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/tsi/test_creds/client.key b/src/core/tsi/test_creds/client.key new file mode 100644 index 0000000000..f48d0735d9 --- /dev/null +++ b/src/core/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/tsi/test_creds/client.pem b/src/core/tsi/test_creds/client.pem new file mode 100644 index 0000000000..e332091019 --- /dev/null +++ b/src/core/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/tsi/test_creds/server0.key b/src/core/tsi/test_creds/server0.key new file mode 100644 index 0000000000..add153c9ae --- /dev/null +++ b/src/core/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/tsi/test_creds/server0.pem b/src/core/tsi/test_creds/server0.pem new file mode 100644 index 0000000000..ade75d8563 --- /dev/null +++ b/src/core/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/tsi/test_creds/server1-openssl.cnf b/src/core/tsi/test_creds/server1-openssl.cnf new file mode 100644 index 0000000000..8a02108289 --- /dev/null +++ b/src/core/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/tsi/test_creds/server1.key b/src/core/tsi/test_creds/server1.key new file mode 100644 index 0000000000..143a5b8765 --- /dev/null +++ b/src/core/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/tsi/test_creds/server1.pem b/src/core/tsi/test_creds/server1.pem new file mode 100644 index 0000000000..8e582e571f --- /dev/null +++ b/src/core/tsi/test_creds/server1.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICmzCCAgSgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJBVTET +MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ +dHkgTHRkMQ8wDQYDVQQDDAZ0ZXN0Y2EwHhcNMTQwNzIyMDYwMDU3WhcNMjQwNzE5 +MDYwMDU3WjBkMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV +BAcTB0NoaWNhZ28xFDASBgNVBAoTC0dvb2dsZSBJbmMuMRowGAYDVQQDFBEqLnRl +c3QuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4cMVJygs +JUmlgMMzgdi0h1XoCR7+ww1pop04OMMyy7H/i0PJ2W6Y35+b4CM8QrkYeEafUGDO +RYX6yV/cHGGsD/x02ye6ey1UDtkGAD/mpDEx8YCrjAc1Vfvt8Fk6Cn1WVIxV/J30 +3xjBsFgByQ55RBp1OLZfVLo6AleBDSbcxaECAwEAAaNrMGkwCQYDVR0TBAIwADAL +BgNVHQ8EBAMCBeAwTwYDVR0RBEgwRoIQKi50ZXN0Lmdvb2dsZS5mcoIYd2F0ZXJ6 +b29pLnRlc3QuZ29vZ2xlLmJlghIqLnRlc3QueW91dHViZS5jb22HBMCoAQMwDQYJ +KoZIhvcNAQEFBQADgYEAM2Ii0LgTGbJ1j4oqX9bxVcxm+/R5Yf8oi0aZqTJlnLYS +wXcBykxTx181s7WyfJ49WwrYXo78zTDAnf1ma0fPq3e4mpspvyndLh1a+OarHa1e +aT0DIIYk7qeEa1YcVljx2KyLd0r1BBAfrwyGaEPVeJQVYWaOJRU2we/KD4ojf9s= +-----END CERTIFICATE----- diff --git a/src/core/tsi/transport_security.c b/src/core/tsi/transport_security.c new file mode 100644 index 0000000000..94252e36d0 --- /dev/null +++ b/src/core/tsi/transport_security.c @@ -0,0 +1,372 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/tsi/transport_security.h" + +#include <stdlib.h> +#include <string.h> + +/* --- Utils. --- */ + +char* tsi_strdup(const char* src) { + char* dst; + uint32_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, + uint32_t* unprotected_bytes_size, + unsigned char* protected_output_frames, + uint32_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, + uint32_t* protected_output_frames_size, + uint32_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, + uint32_t* protected_frames_bytes_size, + unsigned char* unprotected_bytes, + uint32_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, + uint32_t* bytes_size) { + if (self == 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, + uint32_t* bytes_size) { + if (self == 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, + uint32_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. --- */ + +const tsi_peer_property* tsi_peer_get_property_by_name(const tsi_peer* self, + const char* name) { + uint32_t i; + if (self == NULL) return NULL; + for (i = 0; i < self->property_count; i++) { + const tsi_peer_property* property = &self->properties[i]; + if (name == NULL && property->name == NULL) { + return property; + } + if (name != NULL && property->name != NULL && + !strcmp(property->name, name)) { + return property; + } + } + return NULL; +} + +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, + uint32_t child_count) { + uint32_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); + } + switch (property->type) { + case TSI_PEER_PROPERTY_TYPE_STRING: + if (property->value.string.data != NULL) { + free(property->value.string.data); + } + break; + case TSI_PEER_PROPERTY_TYPE_LIST: + tsi_peer_destroy_list_property(property->value.list.children, + property->value.list.child_count); + default: + /* Nothing to free. */ + break; + } + *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_signed_integer_peer_property( + const char* name, int64_t value, tsi_peer_property* property) { + *property = tsi_init_peer_property(); + property->type = TSI_PEER_PROPERTY_TYPE_SIGNED_INTEGER; + if (name != NULL) { + property->name = tsi_strdup(name); + if (property->name == NULL) return TSI_OUT_OF_RESOURCES; + } + property->value.signed_int = value; + return TSI_OK; +} + +tsi_result tsi_construct_unsigned_integer_peer_property( + const char* name, uint64_t value, tsi_peer_property* property) { + *property = tsi_init_peer_property(); + property->type = TSI_PEER_PROPERTY_TYPE_UNSIGNED_INTEGER; + if (name != NULL) { + property->name = tsi_strdup(name); + if (property->name == NULL) return TSI_OUT_OF_RESOURCES; + } + property->value.unsigned_int = value; + return TSI_OK; +} + +tsi_result tsi_construct_real_peer_property(const char* name, double value, + tsi_peer_property* property) { + *property = tsi_init_peer_property(); + property->type = TSI_PEER_PROPERTY_TYPE_REAL; + if (name != NULL) { + property->name = tsi_strdup(name); + if (property->name == NULL) return TSI_OUT_OF_RESOURCES; + } + property->value.real = value; + return TSI_OK; +} + +tsi_result tsi_construct_allocated_string_peer_property( + const char* name, uint32_t value_length, tsi_peer_property* property) { + *property = tsi_init_peer_property(); + property->type = TSI_PEER_PROPERTY_TYPE_STRING; + if (name != NULL) { + property->name = tsi_strdup(name); + if (property->name == NULL) return TSI_OUT_OF_RESOURCES; + } + if (value_length > 0) { + property->value.string.data = calloc(1, value_length); + if (property->value.string.data == NULL) { + tsi_peer_property_destruct(property); + return TSI_OUT_OF_RESOURCES; + } + property->value.string.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, + uint32_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.string.data, value, value_length); + } + return TSI_OK; +} + +tsi_result tsi_construct_list_peer_property(const char* name, + uint32_t child_count, + tsi_peer_property* property) { + *property = tsi_init_peer_property(); + property->type = TSI_PEER_PROPERTY_TYPE_LIST; + if (name != NULL) { + property->name = tsi_strdup(name); + if (property->name == NULL) return TSI_OUT_OF_RESOURCES; + } + if (child_count > 0) { + property->value.list.children = + calloc(child_count, sizeof(tsi_peer_property)); + if (property->value.list.children == NULL) { + tsi_peer_property_destruct(property); + return TSI_OUT_OF_RESOURCES; + } + property->value.list.child_count = child_count; + } + return TSI_OK; +} + +tsi_result tsi_construct_peer(uint32_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/tsi/transport_security.h b/src/core/tsi/transport_security.h new file mode 100644 index 0000000000..cf9a2b0195 --- /dev/null +++ b/src/core/tsi/transport_security.h @@ -0,0 +1,118 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 __TRANSPORT_SECURITY_H_ +#define __TRANSPORT_SECURITY_H_ + +#include "src/core/tsi/transport_security_interface.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* 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, + uint32_t* unprotected_bytes_size, + unsigned char* protected_output_frames, + uint32_t* protected_output_frames_size); + tsi_result (*protect_flush)(tsi_frame_protector* self, + unsigned char* protected_output_frames, + uint32_t* protected_output_frames_size, + uint32_t* still_pending_size); + tsi_result (*unprotect)(tsi_frame_protector* self, + const unsigned char* protected_frames_bytes, + uint32_t* protected_frames_bytes_size, + unsigned char* unprotected_bytes, + uint32_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, + uint32_t* bytes_size); + tsi_result (*process_bytes_from_peer)(tsi_handshaker* self, + const unsigned char* bytes, + uint32_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, + uint32_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(uint32_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_signed_integer_peer_property( + const char* name, int64_t value, tsi_peer_property* property); +tsi_result tsi_construct_unsigned_integer_peer_property( + const char* name, uint64_t value, tsi_peer_property* property); +tsi_result tsi_construct_real_peer_property(const char* name, double value, + tsi_peer_property* property); +tsi_result tsi_construct_string_peer_property(const char* name, + const char* value, + uint32_t value_length, + tsi_peer_property* property); +tsi_result tsi_construct_allocated_string_peer_property( + const char* name, uint32_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); +tsi_result tsi_construct_list_peer_property(const char* name, + uint32_t child_count, + tsi_peer_property* property); + +/* Utils. */ +char* tsi_strdup(const char* src); /* Sadly, no strdup in C89. */ + +#ifdef __cplusplus +} +#endif + +#endif /* __TRANSPORT_SECURITY_H_ */ diff --git a/src/core/tsi/transport_security_interface.h b/src/core/tsi/transport_security_interface.h new file mode 100644 index 0000000000..6be72c753a --- /dev/null +++ b/src/core/tsi/transport_security_interface.h @@ -0,0 +1,389 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 __TRANSPORT_SECURITY_INTERFACE_H_ +#define __TRANSPORT_SECURITY_INTERFACE_H_ + +#include <stdint.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_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]; + uint32_t protected_buffer_size = sizeof(protected_buffer); + tsi_result result = TSI_OK; + while (message_size > 0) { + uint32_t protected_buffer_size_to_send = protected_buffer_size; + uint32_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) { + uint32_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, + uint32_t* unprotected_bytes_size, + unsigned char* protected_output_frames, + uint32_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, + uint32_t* protected_output_frames_size, + uint32_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. + + - 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, + uint32_t* protected_frames_bytes_size, + unsigned char* unprotected_bytes, + uint32_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" + +/* This property is of type TSI_PEER_PROPERTY_STRING. */ +#define TSI_X509_SUBJECT_COMMON_NAME_PEER_PROPERTY "x509_subject_common_name" + +/* This property is of type TSI_PEER_PROPERTY_LIST and the children contain + unnamed (name == NULL) properties of type TSI_PEER_PROPERTY_STRING. */ +#define TSI_X509_SUBJECT_ALTERNATIVE_NAMES_PEER_PROPERTY \ + "x509_subject_alternative_names" + +/* This property is of type TSI_PEER_PROPERTY_STRING. */ +#define TSI_SSL_ALPN_SELECTED_PROTOCOL "ssl_alpn_selected_protocol" + +/* This property is of type TSI_PEER_PROPERTY_STRING. */ +#define TSI_MDB_USER_NAME_PEER_PROPERTY "mdb_user_name" + +/* This property is of type TSI_PEER_PROPERTY_SIGNED_INTEGER. */ +#define TSI_MDB_GAIA_ID_PEER_PROPERTY "mdb_gaia_id" + +/* Properties of type TSI_PEER_PROPERTY_TYPE_STRING may contain NULL characters + just like C++ strings. The length field gives the length of the string. */ +typedef enum { + TSI_PEER_PROPERTY_TYPE_SIGNED_INTEGER, + TSI_PEER_PROPERTY_TYPE_UNSIGNED_INTEGER, + TSI_PEER_PROPERTY_TYPE_REAL, + TSI_PEER_PROPERTY_TYPE_STRING, + TSI_PEER_PROPERTY_TYPE_LIST +} tsi_peer_property_type; + +/* The relevant field in the union value is dictated by the type field. + name may be NULL in case of an unnamed property. */ +typedef struct tsi_peer_property { + char* name; + tsi_peer_property_type type; + union { + int64_t signed_int; + uint64_t unsigned_int; + double real; + struct { + char* data; + uint32_t length; + } string; + struct { + struct tsi_peer_property* children; + uint32_t child_count; + } list; + } value; +} tsi_peer_property; + +typedef struct { + tsi_peer_property* properties; + uint32_t property_count; +} tsi_peer; + +/* Gets the first property with the specified name. Iteration over the + properties of the peer should be used if the client of the API is expecting + several properties with the same name. + Returns NULL if there is no corresponding property. */ +const tsi_peer_property* tsi_peer_get_property_by_name(const tsi_peer* self, + const char* name); + +/* 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]; + uint32_t buf_offset; + uint32_t buf_size; + while (1) { + // See if we need to send some bytes to the peer. + do { + uint32_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. + uint32_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, + uint32_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, + uint32_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, + uint32_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 /* __TRANSPORT_SECURITY_INTERFACE_H_ */ diff --git a/src/core/tsi/transport_security_test_lib.cc b/src/core/tsi/transport_security_test_lib.cc new file mode 100644 index 0000000000..1b630c9578 --- /dev/null +++ b/src/core/tsi/transport_security_test_lib.cc @@ -0,0 +1,363 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/tsi/transport_security_test_lib.h" + +#include <memory> + +#include "base/commandlineflags.h" +#include "src/core/tsi/transport_security_interface.h" +#include "strings/escaping.h" +#include "strings/strcat.h" +#include <gtest/gtest.h> +#include "util/random/mt_random.h" + +namespace { + +const char kPingRequest[] = "Ping"; +const char kPongResponse[] = "Pong"; +const int kBigMessageSize = 17000; + +} // namespace + +namespace tsi { +namespace test { + +TransportSecurityTest::TransportSecurityTest() : random_(new MTRandom()) { + small_message_ = "Chapi Chapo"; + big_message_ = RandomString(kBigMessageSize); +} + +string TransportSecurityTest::RandomString(int size) { + std::unique_ptr<char[]> buffer(new char[size]); + for (int i = 0; i < size; i++) { + buffer[i] = random_->Rand8(); + } + return string(buffer.get(), size); +} + +void TransportSecurityTest::SendBytesToPeer(bool is_client, unsigned char* buf, + unsigned int buf_size) { + string& channel = is_client ? to_server_channel_ : to_client_channel_; + LOG(INFO) << (is_client ? "Client:" : "Server") << " sending " << buf_size + << " bytes to peer."; + channel.append(reinterpret_cast<const char*>(buf), buf_size); +} + +void TransportSecurityTest::ReadBytesFromPeer(bool is_client, + unsigned char* buf, + unsigned int* buf_size) { + string& channel = is_client ? to_client_channel_ : to_server_channel_; + unsigned int to_read = + *buf_size < channel.size() ? *buf_size : channel.size(); + memcpy(buf, channel.data(), to_read); + *buf_size = to_read; + channel.erase(0, to_read); + LOG(INFO) << (is_client ? "Client:" : "Server") << " read " << to_read + << " bytes from peer."; +} + +void TransportSecurityTest::DoHandshakeStep(bool is_client, + unsigned int buf_allocated_size, + tsi_handshaker* handshaker, + string* remaining_bytes) { + tsi_result result = TSI_OK; + std::unique_ptr<unsigned char[]> buf(new unsigned char[buf_allocated_size]); + unsigned int buf_offset; + unsigned int buf_size; + // See if we need to send some bytes to the peer. + do { + unsigned int buf_size_to_send = buf_allocated_size; + result = tsi_handshaker_get_bytes_to_send_to_peer(handshaker, buf.get(), + &buf_size_to_send); + if (buf_size_to_send > 0) { + SendBytesToPeer(is_client, buf.get(), buf_size_to_send); + } + } while (result == TSI_INCOMPLETE_DATA); + if (!tsi_handshaker_is_in_progress(handshaker)) return; + + do { + // Read bytes from the peer. + buf_size = buf_allocated_size; + buf_offset = 0; + ReadBytesFromPeer(is_client, buf.get(), &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. + unsigned int consumed_by_handshaker = buf_size; + result = tsi_handshaker_process_bytes_from_peer(handshaker, buf.get(), + &consumed_by_handshaker); + buf_size -= consumed_by_handshaker; + buf_offset += consumed_by_handshaker; + } while (result == TSI_INCOMPLETE_DATA); + + if (!tsi_handshaker_is_in_progress(handshaker)) { + remaining_bytes->assign( + reinterpret_cast<const char*>(buf.get()) + buf_offset, buf_size); + } +} + +void TransportSecurityTest::PerformHandshake() { + SetupHandshakers(); + string remaining_bytes; + do { + DoHandshakeStep(true, config()->handshake_buffer_size, + client_handshaker_.get(), &remaining_bytes); + EXPECT_EQ(0, remaining_bytes.size()); + DoHandshakeStep(false, config()->handshake_buffer_size, + server_handshaker_.get(), &remaining_bytes); + EXPECT_EQ(0, remaining_bytes.size()); + } while (tsi_handshaker_is_in_progress(client_handshaker_.get()) || + tsi_handshaker_is_in_progress(server_handshaker_.get())); + CheckHandshakeResults(); +} + +void TransportSecurityTest::SendMessageToPeer( + bool is_client, tsi_frame_protector* protector, const string& message, + unsigned int protected_buffer_size) { + std::unique_ptr<unsigned char[]> protected_buffer( + new unsigned char[protected_buffer_size]); + unsigned int message_size = message.size(); + const unsigned char* message_bytes = + reinterpret_cast<const unsigned char*>(message.data()); + tsi_result result = TSI_OK; + while (message_size > 0 && result == TSI_OK) { + unsigned int protected_buffer_size_to_send = protected_buffer_size; + unsigned int processed_message_size = message_size; + result = tsi_frame_protector_protect( + protector, message_bytes, &processed_message_size, + protected_buffer.get(), &protected_buffer_size_to_send); + EXPECT_EQ(TSI_OK, result); + SendBytesToPeer(is_client, protected_buffer.get(), + protected_buffer_size_to_send); + message_bytes += processed_message_size; + message_size -= processed_message_size; + + // Flush if we're done. + if (message_size == 0) { + unsigned int still_pending_size; + do { + protected_buffer_size_to_send = protected_buffer_size; + result = tsi_frame_protector_protect_flush( + protector, protected_buffer.get(), &protected_buffer_size_to_send, + &still_pending_size); + EXPECT_EQ(TSI_OK, result); + SendBytesToPeer(is_client, protected_buffer.get(), + protected_buffer_size_to_send); + } while (still_pending_size > 0 && result == TSI_OK); + EXPECT_EQ(TSI_OK, result); + } + } + EXPECT_EQ(TSI_OK, result); +} + +void TransportSecurityTest::ReceiveMessageFromPeer( + bool is_client, tsi_frame_protector* protector, + unsigned int read_buf_allocated_size, + unsigned int message_buf_allocated_size, string* message) { + std::unique_ptr<unsigned char[]> read_buffer( + new unsigned char[read_buf_allocated_size]); + unsigned int read_offset = 0; + unsigned int read_from_peer_size = 0; + std::unique_ptr<unsigned char[]> message_buffer( + new unsigned char[message_buf_allocated_size]); + tsi_result result = TSI_OK; + bool done = false; + while (!done && result == TSI_OK) { + if (read_from_peer_size == 0) { + read_from_peer_size = read_buf_allocated_size; + ReadBytesFromPeer(is_client, read_buffer.get(), &read_from_peer_size); + read_offset = 0; + } + if (read_from_peer_size == 0) done = true; + unsigned int message_buffer_size; + do { + message_buffer_size = message_buf_allocated_size; + unsigned int processed_size = read_from_peer_size; + result = tsi_frame_protector_unprotect( + protector, read_buffer.get() + read_offset, &processed_size, + message_buffer.get(), &message_buffer_size); + EXPECT_EQ(TSI_OK, result); + if (message_buffer_size > 0) { + LOG(INFO) << "Wrote " << message_buffer_size << " bytes to message."; + message->append(reinterpret_cast<const char*>(message_buffer.get()), + message_buffer_size); + } + read_offset += processed_size; + read_from_peer_size -= processed_size; + } while ((read_from_peer_size > 0 || message_buffer_size > 0) && + result == TSI_OK); + EXPECT_EQ(TSI_OK, result); + } + EXPECT_EQ(TSI_OK, result); +} + +void TransportSecurityTest::DoRoundTrip(const string& request, + const string& response) { + PerformHandshake(); + + tsi_frame_protector* client_frame_protector; + tsi_frame_protector* server_frame_protector; + unsigned int client_max_output_protected_frame_size = + config()->client_max_output_protected_frame_size(); + EXPECT_EQ(TSI_OK, + tsi_handshaker_create_frame_protector( + client_handshaker_.get(), + config()->use_client_default_max_output_protected_frame_size() + ? nullptr + : &client_max_output_protected_frame_size, + &client_frame_protector)); + + unsigned int server_max_output_protected_frame_size = + config()->server_max_output_protected_frame_size(); + EXPECT_EQ(TSI_OK, + tsi_handshaker_create_frame_protector( + server_handshaker_.get(), + config()->use_server_default_max_output_protected_frame_size() + ? nullptr + : &server_max_output_protected_frame_size, + &server_frame_protector)); + + SendMessageToPeer(true, client_frame_protector, request, + config()->protected_buffer_size); + string retrieved_request; + ReceiveMessageFromPeer( + false, server_frame_protector, config()->read_buffer_allocated_size, + config()->message_buffer_allocated_size, &retrieved_request); + EXPECT_EQ(request.size(), retrieved_request.size()); + EXPECT_EQ(strings::b2a_hex(request), strings::b2a_hex(retrieved_request)); + + SendMessageToPeer(false, server_frame_protector, response, + config()->protected_buffer_size); + string retrieved_response; + ReceiveMessageFromPeer( + true, client_frame_protector, config()->read_buffer_allocated_size, + config()->message_buffer_allocated_size, &retrieved_response); + EXPECT_EQ(response.size(), retrieved_response.size()); + EXPECT_EQ(strings::b2a_hex(response), strings::b2a_hex(retrieved_response)); + + tsi_frame_protector_destroy(client_frame_protector); + tsi_frame_protector_destroy(server_frame_protector); +} + +void TransportSecurityTest::DoRoundTrip() { + DoRoundTrip(config()->client_message, config()->server_message); +} +void TransportSecurityTest::PingPong() { + PerformHandshake(); + + unsigned char to_server[4096]; + unsigned char to_client[4096]; + unsigned int max_frame_size = sizeof(to_client); + tsi_frame_protector* client_frame_protector; + tsi_frame_protector* server_frame_protector; + EXPECT_EQ( + tsi_handshaker_create_frame_protector( + client_handshaker_.get(), &max_frame_size, &client_frame_protector), + TSI_OK); + EXPECT_EQ(max_frame_size, sizeof(to_client)); + EXPECT_EQ( + tsi_handshaker_create_frame_protector( + server_handshaker_.get(), &max_frame_size, &server_frame_protector), + TSI_OK); + EXPECT_EQ(max_frame_size, sizeof(to_client)); + + // Send Ping. + unsigned int ping_length = strlen(kPingRequest); + unsigned int protected_size = sizeof(to_server); + EXPECT_EQ(tsi_frame_protector_protect( + client_frame_protector, + reinterpret_cast<const unsigned char*>(kPingRequest), + &ping_length, to_server, &protected_size), + TSI_OK); + EXPECT_EQ(ping_length, strlen(kPingRequest)); + EXPECT_EQ(protected_size, 0); + protected_size = sizeof(to_server); + unsigned int still_pending_size; + EXPECT_EQ( + tsi_frame_protector_protect_flush(client_frame_protector, to_server, + &protected_size, &still_pending_size), + TSI_OK); + EXPECT_EQ(still_pending_size, 0); + EXPECT_GT(protected_size, strlen(kPingRequest)); + + // Receive Ping. + unsigned int unprotected_size = sizeof(to_server); + unsigned int saved_protected_size = protected_size; + EXPECT_EQ(tsi_frame_protector_unprotect(server_frame_protector, to_server, + &protected_size, to_server, + &unprotected_size), + TSI_OK); + EXPECT_EQ(saved_protected_size, protected_size); + EXPECT_EQ(ping_length, unprotected_size); + EXPECT_EQ(string(kPingRequest), + string(reinterpret_cast<const char*>(to_server), unprotected_size)); + + // Send back Pong. + unsigned int pong_length = strlen(kPongResponse); + protected_size = sizeof(to_client); + EXPECT_EQ(tsi_frame_protector_protect( + server_frame_protector, + reinterpret_cast<const unsigned char*>(kPongResponse), + &pong_length, to_client, &protected_size), + TSI_OK); + EXPECT_EQ(pong_length, strlen(kPongResponse)); + EXPECT_EQ(protected_size, 0); + protected_size = sizeof(to_client); + EXPECT_EQ( + tsi_frame_protector_protect_flush(server_frame_protector, to_client, + &protected_size, &still_pending_size), + TSI_OK); + EXPECT_EQ(still_pending_size, 0); + EXPECT_GT(protected_size, strlen(kPongResponse)); + + // Receive Pong. + unprotected_size = sizeof(to_server); + saved_protected_size = protected_size; + EXPECT_EQ(tsi_frame_protector_unprotect(client_frame_protector, to_client, + &protected_size, to_client, + &unprotected_size), + TSI_OK); + EXPECT_EQ(saved_protected_size, protected_size); + EXPECT_EQ(pong_length, unprotected_size); + EXPECT_EQ(string(kPongResponse), + string(reinterpret_cast<const char*>(to_client), unprotected_size)); + + tsi_frame_protector_destroy(client_frame_protector); + tsi_frame_protector_destroy(server_frame_protector); +} + +} // namespace test +} // namespace tsi diff --git a/src/core/tsi/transport_security_test_lib.h b/src/core/tsi/transport_security_test_lib.h new file mode 100644 index 0000000000..8c9c764c91 --- /dev/null +++ b/src/core/tsi/transport_security_test_lib.h @@ -0,0 +1,154 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 __TRANSPORT_SECURITY_TEST_LIB_H_ +#define __TRANSPORT_SECURITY_TEST_LIB_H_ + +#include <memory> + +#include "base/commandlineflags.h" +#include "src/core/tsi/transport_security_interface.h" +#include "strings/strcat.h" +#include <gtest/gtest.h> +#include "util/random/mt_random.h" + +namespace tsi { +namespace test { + +class TestConfig { + public: + TestConfig() + : client_message("Chapi Chapo"), + server_message("Chapi Chapo"), + handshake_buffer_size(4096), + read_buffer_allocated_size(4096), + message_buffer_allocated_size(4096), + protected_buffer_size(16384), + use_client_default_max_output_protected_frame_size_(true), + use_server_default_max_output_protected_frame_size_(true), + client_max_output_protected_frame_size_(0), + server_max_output_protected_frame_size_(0) {} + + void set_client_max_output_protected_frame_size(unsigned int size) { + use_client_default_max_output_protected_frame_size_ = false; + client_max_output_protected_frame_size_ = size; + } + void set_server_max_output_protected_frame_size(unsigned int size) { + use_server_default_max_output_protected_frame_size_ = false; + server_max_output_protected_frame_size_ = size; + } + bool use_client_default_max_output_protected_frame_size() const { + return use_client_default_max_output_protected_frame_size_; + } + bool use_server_default_max_output_protected_frame_size() const { + return use_server_default_max_output_protected_frame_size_; + } + unsigned int client_max_output_protected_frame_size() const { + return client_max_output_protected_frame_size_; + } + unsigned int server_max_output_protected_frame_size() const { + return server_max_output_protected_frame_size_; + } + + string client_message; + string server_message; + unsigned int handshake_buffer_size; + unsigned int read_buffer_allocated_size; + unsigned int message_buffer_allocated_size; + unsigned int protected_buffer_size; + + private: + bool use_client_default_max_output_protected_frame_size_; + bool use_server_default_max_output_protected_frame_size_; + unsigned int client_max_output_protected_frame_size_; + unsigned int server_max_output_protected_frame_size_; +}; + + +struct TsiHandshakerDeleter { + inline void operator()(tsi_handshaker* ptr) { tsi_handshaker_destroy(ptr); } +}; +typedef std::unique_ptr<tsi_handshaker, TsiHandshakerDeleter> + TsiHandshakerUniquePtr; + +class TransportSecurityTest : public ::testing::Test { + protected: + TransportSecurityTest(); + virtual ~TransportSecurityTest() {} + virtual const TestConfig* config() = 0; + string RandomString(int size); + virtual void SetupHandshakers() = 0; + // An implementation-specific verification of the validity of the handshake. + virtual void CheckHandshakeResults() = 0; + // Do a full handshake. + void PerformHandshake(); + // Send a protected message between the client and server. + void SendMessageToPeer(bool is_client, tsi_frame_protector* protector, + const string& message, + unsigned int protected_buffer_size); + void ReceiveMessageFromPeer(bool is_client, tsi_frame_protector* protector, + unsigned int read_buf_allocated_size, + unsigned int message_buf_allocated_size, + string* message); + + // A simple test that does a handshake and sends a message back and forth + void PingPong(); + // A complicated test that can be configured by modifying config(). + void DoRoundTrip(); + + TsiHandshakerUniquePtr client_handshaker_; + TsiHandshakerUniquePtr server_handshaker_; + + string small_message_; + string big_message_; + std::unique_ptr<RandomBase> random_; + + private: + // Functions to send raw bytes between the client and server. + void SendBytesToPeer(bool is_client, unsigned char* buf, + unsigned int buf_size); + void ReadBytesFromPeer(bool is_client, unsigned char* buf, + unsigned int* buf_size); + // Do a single step of the handshake. + void DoHandshakeStep(bool is_client, unsigned int buf_allocated_size, + tsi_handshaker* handshaker, string* remaining_bytes); + void DoRoundTrip(const string& request, const string& response); + + string to_server_channel_; + string to_client_channel_; +}; + +} // namespace test +} // namespace tsi + +#endif // __TRANSPORT_SECURITY_TEST_LIB_H_ diff --git a/src/cpp/client/channel.cc b/src/cpp/client/channel.cc new file mode 100644 index 0000000000..7a7529104f --- /dev/null +++ b/src/cpp/client/channel.cc @@ -0,0 +1,212 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/cpp/client/channel.h" + +#include <chrono> +#include <string> + +#include <grpc/grpc.h> +#include <grpc/support/log.h> +#include <grpc/support/slice.h> +#include <grpc/support/time.h> + +#include "src/cpp/rpc_method.h" +#include "src/cpp/proto/proto_utils.h" +#include "src/cpp/stream/stream_context.h" +#include "src/cpp/util/time.h" +#include <grpc++/config.h> +#include <google/protobuf/message.h> +#include <grpc++/client_context.h> +#include <grpc++/status.h> + +namespace grpc { + +Channel::Channel(const grpc::string& target) : target_(target) { + c_channel_ = grpc_channel_create(target_.c_str(), nullptr); +} + +Channel::~Channel() { grpc_channel_destroy(c_channel_); } + +namespace { +// Poll one event from the compeletion queue. Return false when an error +// occured or the polled type is not expected. If a finished event has been +// polled, set finished and set status if it has not been set. +bool NextEvent(grpc_completion_queue* cq, grpc_completion_type expected_type, + bool* finished, bool* status_set, Status* status, + google::protobuf::Message* result) { + // We rely on the c layer to enforce deadline and thus do not use deadline + // here. + grpc_event* ev = grpc_completion_queue_next(cq, gpr_inf_future); + if (!ev) { + return false; + } + bool ret = ev->type == expected_type; + switch (ev->type) { + case GRPC_INVOKE_ACCEPTED: + ret = ret && (ev->data.invoke_accepted == GRPC_OP_OK); + break; + case GRPC_READ: + ret = ret && (ev->data.read != nullptr); + if (ret && !DeserializeProto(ev->data.read, result)) { + *status_set = true; + *status = + Status(StatusCode::DATA_LOSS, "Failed to parse response proto."); + ret = false; + } + break; + case GRPC_WRITE_ACCEPTED: + ret = ret && (ev->data.write_accepted == GRPC_OP_OK); + break; + case GRPC_FINISH_ACCEPTED: + ret = ret && (ev->data.finish_accepted == GRPC_OP_OK); + break; + case GRPC_CLIENT_METADATA_READ: + break; + case GRPC_FINISHED: + *finished = true; + if (!*status_set) { + *status_set = true; + StatusCode error_code = static_cast<StatusCode>(ev->data.finished.code); + grpc::string details( + ev->data.finished.details ? ev->data.finished.details : ""); + *status = Status(error_code, details); + } + break; + default: + gpr_log(GPR_ERROR, "Dropping unhandled event with type %d", ev->type); + break; + } + grpc_event_finish(ev); + return ret; +} + +// If finished is not true, get final status by polling until a finished +// event is obtained. +void GetFinalStatus(grpc_completion_queue* cq, bool status_set, bool finished, + Status* status) { + while (!finished) { + NextEvent(cq, GRPC_FINISHED, &finished, &status_set, status, nullptr); + } +} + +} // namespace + +// TODO(yangg) more error handling +Status Channel::StartBlockingRpc(const RpcMethod& method, + ClientContext* context, + const google::protobuf::Message& request, + google::protobuf::Message* result) { + Status status; + bool status_set = false; + bool finished = false; + gpr_timespec absolute_deadline; + AbsoluteDeadlineTimepoint2Timespec(context->absolute_deadline(), + &absolute_deadline); + grpc_call* call = grpc_channel_create_call(c_channel_, method.name(), + // FIXME(yangg) + "localhost", absolute_deadline); + context->set_call(call); + grpc_completion_queue* cq = grpc_completion_queue_create(); + context->set_cq(cq); + // add_metadata from context + // + // invoke + GPR_ASSERT(grpc_call_start_invoke(call, cq, call, call, call, + GRPC_WRITE_BUFFER_HINT) == GRPC_CALL_OK); + if (!NextEvent(cq, GRPC_INVOKE_ACCEPTED, &status_set, &finished, &status, + nullptr)) { + GetFinalStatus(cq, finished, status_set, &status); + return status; + } + // write request + grpc_byte_buffer* write_buffer = nullptr; + bool success = SerializeProto(request, &write_buffer); + if (!success) { + grpc_call_cancel(call); + status_set = true; + status = + Status(StatusCode::DATA_LOSS, "Failed to serialize request proto."); + GetFinalStatus(cq, finished, status_set, &status); + return status; + } + GPR_ASSERT(grpc_call_start_write(call, write_buffer, call, + GRPC_WRITE_BUFFER_HINT) == GRPC_CALL_OK); + grpc_byte_buffer_destroy(write_buffer); + if (!NextEvent(cq, GRPC_WRITE_ACCEPTED, &finished, &status_set, &status, + nullptr)) { + GetFinalStatus(cq, finished, status_set, &status); + return status; + } + // writes done + GPR_ASSERT(grpc_call_writes_done(call, call) == GRPC_CALL_OK); + if (!NextEvent(cq, GRPC_FINISH_ACCEPTED, &finished, &status_set, &status, + nullptr)) { + GetFinalStatus(cq, finished, status_set, &status); + return status; + } + // start read metadata + // + if (!NextEvent(cq, GRPC_CLIENT_METADATA_READ, &finished, &status_set, &status, + nullptr)) { + GetFinalStatus(cq, finished, status_set, &status); + return status; + } + // start read + GPR_ASSERT(grpc_call_start_read(call, call) == GRPC_CALL_OK); + if (!NextEvent(cq, GRPC_READ, &finished, &status_set, &status, result)) { + GetFinalStatus(cq, finished, status_set, &status); + return status; + } + // wait status + GetFinalStatus(cq, finished, status_set, &status); + return status; +} + +StreamContextInterface* Channel::CreateStream(const RpcMethod& method, + ClientContext* context, + const google::protobuf::Message* request, + google::protobuf::Message* result) { + gpr_timespec absolute_deadline; + AbsoluteDeadlineTimepoint2Timespec(context->absolute_deadline(), + &absolute_deadline); + grpc_call* call = grpc_channel_create_call(c_channel_, method.name(), + // FIXME(yangg) + "localhost", absolute_deadline); + context->set_call(call); + grpc_completion_queue* cq = grpc_completion_queue_create(); + context->set_cq(cq); + return new StreamContext(method, context, request, result); +} + +} // namespace grpc diff --git a/src/cpp/client/channel.h b/src/cpp/client/channel.h new file mode 100644 index 0000000000..a97d35efe8 --- /dev/null +++ b/src/cpp/client/channel.h @@ -0,0 +1,71 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 __GRPCPP_INTERNAL_CLIENT_CHANNEL_H__ +#define __GRPCPP_INTERNAL_CLIENT_CHANNEL_H__ + +#include <grpc++/channel_interface.h> +#include <grpc++/config.h> + +struct grpc_channel; + +namespace grpc { +class StreamContextInterface; + +class Channel : public ChannelInterface { + public: + explicit Channel(const grpc::string& target); + ~Channel() override; + + Status StartBlockingRpc(const RpcMethod& method, ClientContext* context, + const google::protobuf::Message& request, + google::protobuf::Message* result) override; + + StreamContextInterface* CreateStream(const RpcMethod& method, + ClientContext* context, + const google::protobuf::Message* request, + google::protobuf::Message* result) override; + + protected: + // TODO(yangg) remove this section when we have the general ssl channel API + Channel() {} + void set_c_channel(grpc_channel* channel) { c_channel_ = channel; } + + private: + const grpc::string target_; + grpc_channel* c_channel_; // owned +}; + +} // namespace grpc + +#endif // __GRPCPP_INTERNAL_CLIENT_CHANNEL_H__ diff --git a/src/cpp/client/client_context.cc b/src/cpp/client/client_context.cc new file mode 100644 index 0000000000..78774a7f12 --- /dev/null +++ b/src/cpp/client/client_context.cc @@ -0,0 +1,73 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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++/client_context.h> + +#include <grpc/grpc.h> + +using std::chrono::system_clock; + +namespace grpc { + +ClientContext::ClientContext() + : call_(nullptr), + cq_(nullptr), + absolute_deadline_(system_clock::time_point::max()) {} + +ClientContext::~ClientContext() { + if (call_) { + grpc_call_destroy(call_); + } + if (cq_) { + grpc_completion_queue_shutdown(cq_); + grpc_completion_queue_destroy(cq_); + } +} + +void ClientContext::set_absolute_deadline( + const system_clock::time_point& deadline) { + absolute_deadline_ = deadline; +} + +system_clock::time_point ClientContext::absolute_deadline() { + return absolute_deadline_; +} + +void ClientContext::AddMetadata(const grpc::string& meta_key, + const grpc::string& meta_value) { + return; +} + +void ClientContext::StartCancel() {} + +} // namespace grpc diff --git a/src/cpp/client/create_channel.cc b/src/cpp/client/create_channel.cc new file mode 100644 index 0000000000..ea2b6a7ba2 --- /dev/null +++ b/src/cpp/client/create_channel.cc @@ -0,0 +1,47 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 <memory> +#include <string> + +#include "src/cpp/client/channel.h" +#include <grpc++/channel_interface.h> +#include <grpc++/create_channel.h> + +namespace grpc { + +std::shared_ptr<ChannelInterface> CreateChannel(const grpc::string& target) { + return std::shared_ptr<ChannelInterface>(new Channel(target)); +} + +} // namespace grpc diff --git a/src/cpp/client/credentials.cc b/src/cpp/client/credentials.cc new file mode 100644 index 0000000000..18fa4fa656 --- /dev/null +++ b/src/cpp/client/credentials.cc @@ -0,0 +1,90 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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> + +#include <grpc/grpc_security.h> + +#include <grpc++/credentials.h> + +namespace grpc { + +Credentials::Credentials(grpc_credentials* c_creds) : creds_(c_creds) {} + +Credentials::~Credentials() { grpc_credentials_release(creds_); } +grpc_credentials* Credentials::GetRawCreds() { return creds_; } + +std::unique_ptr<Credentials> CredentialsFactory::DefaultCredentials() { + grpc_credentials* c_creds = grpc_default_credentials_create(); + std::unique_ptr<Credentials> cpp_creds(new Credentials(c_creds)); + return cpp_creds; +} + +// Builds SSL Credentials given SSL specific options +std::unique_ptr<Credentials> CredentialsFactory::SslCredentials( + const SslCredentialsOptions& options) { + grpc_credentials* c_creds = grpc_ssl_credentials_create( + reinterpret_cast<const unsigned char*>(options.pem_root_certs.c_str()), + options.pem_root_certs.size(), + reinterpret_cast<const unsigned char*>(options.pem_private_key.c_str()), + options.pem_private_key.size(), + reinterpret_cast<const unsigned char*>(options.pem_cert_chain.c_str()), + options.pem_cert_chain.size()); + std::unique_ptr<Credentials> cpp_creds(new Credentials(c_creds)); + return cpp_creds; +} + +// Builds credentials for use when running in GCE +std::unique_ptr<Credentials> CredentialsFactory::ComputeEngineCredentials() { + grpc_credentials* c_creds = grpc_compute_engine_credentials_create(); + std::unique_ptr<Credentials> cpp_creds(new Credentials(c_creds)); + return cpp_creds; +} + + +// Combines two credentials objects into a composite credentials. +std::unique_ptr<Credentials> CredentialsFactory::ComposeCredentials( + const std::unique_ptr<Credentials>& creds1, + const std::unique_ptr<Credentials>& creds2) { + // Note that we are not saving unique_ptrs to the two credentials + // passed in here. This is OK because the underlying C objects (i.e., + // creds1 and creds2) into grpc_composite_credentials_create will see their + // refcounts incremented. + grpc_credentials* c_creds = grpc_composite_credentials_create( + creds1->GetRawCreds(), creds2->GetRawCreds()); + std::unique_ptr<Credentials> cpp_creds(new Credentials(c_creds)); + return cpp_creds; +} + +} // namespace grpc diff --git a/src/cpp/client/internal_stub.cc b/src/cpp/client/internal_stub.cc new file mode 100644 index 0000000000..ec88ba5e7e --- /dev/null +++ b/src/cpp/client/internal_stub.cc @@ -0,0 +1,36 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/cpp/client/internal_stub.h" + +namespace grpc {} // namespace grpc diff --git a/src/cpp/client/internal_stub.h b/src/cpp/client/internal_stub.h new file mode 100644 index 0000000000..0eaa717d0b --- /dev/null +++ b/src/cpp/client/internal_stub.h @@ -0,0 +1,60 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 __GRPCPP_INTERNAL_CLIENT_INTERNAL_STUB_H__ +#define __GRPCPP_INTERNAL_CLIENT_INTERNAL_STUB_H__ + +#include <memory> + +#include <grpc++/channel_interface.h> + +namespace grpc { + +class InternalStub { + public: + InternalStub() {} + virtual ~InternalStub() {} + + void set_channel(const std::shared_ptr<ChannelInterface>& channel) { + channel_ = channel; + } + + ChannelInterface* channel() { return channel_.get(); } + + private: + std::shared_ptr<ChannelInterface> channel_; +}; + +} // namespace grpc + +#endif // __GRPCPP_INTERNAL_CLIENT_INTERNAL_STUB_H__ diff --git a/src/cpp/proto/proto_utils.cc b/src/cpp/proto/proto_utils.cc new file mode 100644 index 0000000000..255d1461a9 --- /dev/null +++ b/src/cpp/proto/proto_utils.cc @@ -0,0 +1,71 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/cpp/proto/proto_utils.h" +#include <grpc++/config.h> + +#include <grpc/grpc.h> +#include <grpc/support/slice.h> +#include <google/protobuf/message.h> + +namespace grpc { + +bool SerializeProto(const google::protobuf::Message& msg, grpc_byte_buffer** bp) { + grpc::string msg_str; + bool success = msg.SerializeToString(&msg_str); + if (success) { + gpr_slice slice = + gpr_slice_from_copied_buffer(msg_str.data(), msg_str.length()); + *bp = grpc_byte_buffer_create(&slice, 1); + gpr_slice_unref(slice); + } + return success; +} + +bool DeserializeProto(grpc_byte_buffer* buffer, google::protobuf::Message* msg) { + grpc::string msg_string; + grpc_byte_buffer_reader* reader = grpc_byte_buffer_reader_create(buffer); + gpr_slice slice; + while (grpc_byte_buffer_reader_next(reader, &slice)) { + const char* data = reinterpret_cast<const char*>( + slice.refcount ? slice.data.refcounted.bytes + : slice.data.inlined.bytes); + msg_string.append(data, slice.refcount ? slice.data.refcounted.length + : slice.data.inlined.length); + gpr_slice_unref(slice); + } + grpc_byte_buffer_reader_destroy(reader); + return msg->ParseFromString(msg_string); +} + +} // namespace grpc diff --git a/src/cpp/proto/proto_utils.h b/src/cpp/proto/proto_utils.h new file mode 100644 index 0000000000..11471f1acb --- /dev/null +++ b/src/cpp/proto/proto_utils.h @@ -0,0 +1,56 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 __GRPCPP_INTERNAL_PROTO_PROTO_UTILS_H__ +#define __GRPCPP_INTERNAL_PROTO_PROTO_UTILS_H__ + +struct grpc_byte_buffer; +namespace google { +namespace protobuf { +class Message; +} +} + +namespace grpc { + +// Serialize the msg into a buffer created inside the function. The caller +// should destroy the returned buffer when done with it. If serialization fails, +// false is returned and buffer is left unchanged. +bool SerializeProto(const google::protobuf::Message& msg, grpc_byte_buffer** buffer); + +// The caller keeps ownership of buffer and msg. +bool DeserializeProto(grpc_byte_buffer* buffer, google::protobuf::Message* msg); + +} // namespace grpc + +#endif // __GRPCPP_INTERNAL_PROTO_PROTO_UTILS_H__ diff --git a/src/cpp/rpc_method.cc b/src/cpp/rpc_method.cc new file mode 100644 index 0000000000..8067f42f85 --- /dev/null +++ b/src/cpp/rpc_method.cc @@ -0,0 +1,36 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/cpp/rpc_method.h" + +namespace grpc {} // namespace grpc diff --git a/src/cpp/rpc_method.h b/src/cpp/rpc_method.h new file mode 100644 index 0000000000..24a34bed89 --- /dev/null +++ b/src/cpp/rpc_method.h @@ -0,0 +1,69 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 __GRPCPP_INTERNAL_RPC_METHOD_H__ +#define __GRPCPP_INTERNAL_RPC_METHOD_H__ + +namespace google { +namespace protobuf { +class Message; +} +} + +namespace grpc { + +class RpcMethod { + public: + enum RpcType { + NORMAL_RPC = 0, + CLIENT_STREAMING, // request streaming + SERVER_STREAMING, // response streaming + BIDI_STREAMING + }; + + explicit RpcMethod(const char* name) + : name_(name), method_type_(NORMAL_RPC) {} + RpcMethod(const char* name, RpcType type) : name_(name), method_type_(type) {} + + const char *name() const { return name_; } + + RpcType method_type() const { return method_type_; } + + private: + const char *name_; + const RpcType method_type_; +}; + +} // namespace grpc + +#endif // __GRPCPP_INTERNAL_RPC_METHOD_H__ diff --git a/src/cpp/server/async_server.cc b/src/cpp/server/async_server.cc new file mode 100644 index 0000000000..aae2c82050 --- /dev/null +++ b/src/cpp/server/async_server.cc @@ -0,0 +1,89 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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++/async_server.h> + +#include <grpc/grpc.h> +#include <grpc/support/log.h> +#include <grpc++/completion_queue.h> + +namespace grpc { + +AsyncServer::AsyncServer(CompletionQueue* cc) + : started_(false), shutdown_(false) { + server_ = grpc_server_create(cc->cq(), nullptr); +} + +AsyncServer::~AsyncServer() { + std::unique_lock<std::mutex> lock(shutdown_mu_); + if (started_ && !shutdown_) { + lock.unlock(); + Shutdown(); + } + grpc_server_destroy(server_); +} + +void AsyncServer::AddPort(const grpc::string& addr) { + GPR_ASSERT(!started_); + int success = grpc_server_add_http2_port(server_, addr.c_str()); + GPR_ASSERT(success); +} + +void AsyncServer::Start() { + GPR_ASSERT(!started_); + started_ = true; + grpc_server_start(server_); +} + +void AsyncServer::RequestOneRpc() { + GPR_ASSERT(started_); + std::unique_lock<std::mutex> lock(shutdown_mu_); + if (shutdown_) { + return; + } + lock.unlock(); + grpc_call_error err = grpc_server_request_call(server_, nullptr); + GPR_ASSERT(err == GRPC_CALL_OK); +} + +void AsyncServer::Shutdown() { + std::unique_lock<std::mutex> lock(shutdown_mu_); + if (started_ && !shutdown_) { + shutdown_ = true; + lock.unlock(); + // TODO(yangg) should we shutdown without start? + grpc_server_shutdown(server_); + } +} + +} // namespace grpc diff --git a/src/cpp/server/async_server_context.cc b/src/cpp/server/async_server_context.cc new file mode 100644 index 0000000000..b231f4b0cf --- /dev/null +++ b/src/cpp/server/async_server_context.cc @@ -0,0 +1,101 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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++/async_server_context.h> + +#include <grpc/grpc.h> +#include <grpc/support/log.h> +#include "src/cpp/proto/proto_utils.h" +#include <google/protobuf/message.h> +#include <grpc++/status.h> + +namespace grpc { + +AsyncServerContext::AsyncServerContext( + grpc_call* call, const grpc::string& method, const grpc::string& host, + system_clock::time_point absolute_deadline) + : method_(method), + host_(host), + absolute_deadline_(absolute_deadline), + request_(nullptr), + call_(call) { +} + +AsyncServerContext::~AsyncServerContext() { grpc_call_destroy(call_); } + +void AsyncServerContext::Accept(grpc_completion_queue* cq) { + grpc_call_accept(call_, cq, this, 0); +} + +bool AsyncServerContext::StartRead(google::protobuf::Message* request) { + GPR_ASSERT(request); + request_ = request; + grpc_call_error err = grpc_call_start_read(call_, this); + return err == GRPC_CALL_OK; +} + +bool AsyncServerContext::StartWrite(const google::protobuf::Message& response, + int flags) { + grpc_byte_buffer* buffer = nullptr; + if (!SerializeProto(response, &buffer)) { + return false; + } + grpc_call_error err = grpc_call_start_write(call_, buffer, this, flags); + grpc_byte_buffer_destroy(buffer); + return err == GRPC_CALL_OK; +} + +namespace { +grpc_status TranslateStatus(const Status& status) { + grpc_status c_status; + // TODO(yangg) + c_status.code = GRPC_STATUS_OK; + c_status.details = nullptr; + return c_status; +} +} // namespace + +bool AsyncServerContext::StartWriteStatus(const Status& status) { + grpc_status c_status = TranslateStatus(status); + grpc_call_error err = grpc_call_start_write_status(call_, c_status, this); + return err == GRPC_CALL_OK; +} + +bool AsyncServerContext::ParseRead(grpc_byte_buffer* read_buffer) { + GPR_ASSERT(request_); + bool success = DeserializeProto(read_buffer, request_); + request_ = nullptr; + return success; +} + +} // namespace grpc diff --git a/src/cpp/server/completion_queue.cc b/src/cpp/server/completion_queue.cc new file mode 100644 index 0000000000..04eb301f7e --- /dev/null +++ b/src/cpp/server/completion_queue.cc @@ -0,0 +1,113 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +// TODO(yangg) maybe move to internal/common +#include <grpc++/completion_queue.h> + +#include <grpc/grpc.h> +#include <grpc/support/log.h> +#include <grpc/support/time.h> +#include "src/cpp/util/time.h" +#include <grpc++/async_server_context.h> + +namespace grpc { + +CompletionQueue::CompletionQueue() { cq_ = grpc_completion_queue_create(); } + +CompletionQueue::~CompletionQueue() { grpc_completion_queue_destroy(cq_); } + +void CompletionQueue::Shutdown() { grpc_completion_queue_shutdown(cq_); } + +CompletionQueue::CompletionType CompletionQueue::Next(void** tag) { + grpc_event* ev; + CompletionType return_type; + bool success; + + ev = grpc_completion_queue_next(cq_, gpr_inf_future); + if (!ev) { + gpr_log(GPR_ERROR, "no next event in queue"); + abort(); + } + switch (ev->type) { + case GRPC_QUEUE_SHUTDOWN: + return_type = QUEUE_CLOSED; + break; + case GRPC_READ: + *tag = ev->tag; + if (ev->data.read) { + success = + static_cast<AsyncServerContext*>(ev->tag)->ParseRead(ev->data.read); + return_type = success ? SERVER_READ_OK : SERVER_READ_ERROR; + } else { + return_type = SERVER_READ_ERROR; + } + break; + case GRPC_WRITE_ACCEPTED: + *tag = ev->tag; + if (ev->data.write_accepted != GRPC_OP_ERROR) { + return_type = SERVER_WRITE_OK; + } else { + return_type = SERVER_WRITE_ERROR; + } + break; + case GRPC_SERVER_RPC_NEW: + GPR_ASSERT(!ev->tag); + // Finishing the pending new rpcs after the server has been shutdown. + if (!ev->call) { + *tag = nullptr; + } else { + *tag = new AsyncServerContext(ev->call, ev->data.server_rpc_new.method, + ev->data.server_rpc_new.host, + AbsoluteDeadlineTimespec2Timepoint( + ev->data.server_rpc_new.deadline)); + } + return_type = SERVER_RPC_NEW; + break; + case GRPC_FINISHED: + *tag = ev->tag; + return_type = RPC_END; + break; + case GRPC_FINISH_ACCEPTED: + *tag = ev->tag; + return_type = HALFCLOSE_OK; + break; + default: + // We do not handle client side messages now + gpr_log(GPR_ERROR, "client-side messages aren't supported yet"); + abort(); + } + grpc_event_finish(ev); + return return_type; +} + +} // namespace grpc diff --git a/src/cpp/server/rpc_service_method.h b/src/cpp/server/rpc_service_method.h new file mode 100644 index 0000000000..ac2badda71 --- /dev/null +++ b/src/cpp/server/rpc_service_method.h @@ -0,0 +1,131 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 __GRPCPP_INTERNAL_SERVER_RPC_SERVICE_METHOD_H__ +#define __GRPCPP_INTERNAL_SERVER_RPC_SERVICE_METHOD_H__ + +#include <functional> +#include <map> +#include <memory> +#include <vector> + +#include "src/cpp/rpc_method.h" +#include <google/protobuf/message.h> +#include <grpc++/status.h> + +namespace grpc { + +// TODO(rocking): we might need to split this file into multiple ones. + +// Base class for running an RPC handler. +class MethodHandler { + public: + virtual ~MethodHandler() {} + struct HandlerParameter { + HandlerParameter(const google::protobuf::Message* req, google::protobuf::Message* resp) + : request(req), response(resp) {} + const google::protobuf::Message* request; + google::protobuf::Message* response; + }; + virtual ::grpc::Status RunHandler(const HandlerParameter& param) = 0; +}; + +// A wrapper class of an application provided rpc method handler. +template <class ServiceType, class RequestType, class ResponseType> +class RpcMethodHandler : public MethodHandler { + public: + RpcMethodHandler(std::function<::grpc::Status( + ServiceType*, const RequestType*, ResponseType*)> func, + ServiceType* service) + : func_(func), service_(service) {} + + ::grpc::Status RunHandler(const HandlerParameter& param) final { + // Invoke application function, cast proto messages to their actual types. + return func_(service_, dynamic_cast<const RequestType*>(param.request), + dynamic_cast<ResponseType*>(param.response)); + } + + private: + // Application provided rpc handler function. + std::function<::grpc::Status(ServiceType*, const RequestType*, ResponseType*)> + func_; + // The class the above handler function lives in. + ServiceType* service_; +}; + +// Server side rpc method class +class RpcServiceMethod : public RpcMethod { + public: + // Takes ownership of the handler and two prototype objects. + RpcServiceMethod(const char* name, MethodHandler* handler, + google::protobuf::Message* request_prototype, + google::protobuf::Message* response_prototype) + : RpcMethod(name), + handler_(handler), + request_prototype_(request_prototype), + response_prototype_(response_prototype) {} + + MethodHandler* handler() { return handler_.get(); } + + google::protobuf::Message* AllocateRequestProto() { return request_prototype_->New(); } + google::protobuf::Message* AllocateResponseProto() { + return response_prototype_->New(); + } + + private: + std::unique_ptr<MethodHandler> handler_; + std::unique_ptr<google::protobuf::Message> request_prototype_; + std::unique_ptr<google::protobuf::Message> response_prototype_; +}; + +// This class contains all the method information for an rpc service. It is +// used for registering a service on a grpc server. +class RpcService { + public: + // Takes ownership. + void AddMethod(RpcServiceMethod* method) { + methods_.push_back(std::unique_ptr<RpcServiceMethod>(method)); + } + + RpcServiceMethod* GetMethod(int i) { + return methods_[i].get(); + } + int GetMethodCount() const { return methods_.size(); } + + private: + std::vector<std::unique_ptr<RpcServiceMethod>> methods_; +}; + +} // namespace grpc + +#endif // __GRPCPP_INTERNAL_SERVER_RPC_SERVICE_METHOD_H__ diff --git a/src/cpp/server/server.cc b/src/cpp/server/server.cc new file mode 100644 index 0000000000..9bf4073238 --- /dev/null +++ b/src/cpp/server/server.cc @@ -0,0 +1,166 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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++/server.h> +#include <utility> + +#include <grpc/grpc.h> +#include <grpc/support/log.h> +#include "src/cpp/server/rpc_service_method.h" +#include "src/cpp/server/server_rpc_handler.h" +#include "src/cpp/server/thread_pool.h" +#include <grpc++/async_server_context.h> +#include <grpc++/completion_queue.h> + +namespace grpc { + +// TODO(rocking): consider a better default value like num of cores. +static const int kNumThreads = 4; + +Server::Server(ThreadPoolInterface* thread_pool) + : started_(false), + shutdown_(false), + num_running_cb_(0), + thread_pool_(thread_pool == nullptr ? new ThreadPool(kNumThreads) + : thread_pool), + thread_pool_owned_(thread_pool == nullptr) { + server_ = grpc_server_create(cq_.cq(), nullptr); +} + +Server::Server() { + // Should not be called. + GPR_ASSERT(false); +} + +Server::~Server() { + std::unique_lock<std::mutex> lock(mu_); + if (started_ && !shutdown_) { + lock.unlock(); + Shutdown(); + } + grpc_server_destroy(server_); + if (thread_pool_owned_) { + delete thread_pool_; + } +} + +void Server::RegisterService(RpcService* service) { + for (int i = 0; i < service->GetMethodCount(); ++i) { + RpcServiceMethod* method = service->GetMethod(i); + method_map_.insert(std::make_pair(method->name(), method)); + } +} + +void Server::AddPort(const grpc::string& addr) { + GPR_ASSERT(!started_); + int success = grpc_server_add_http2_port(server_, addr.c_str()); + GPR_ASSERT(success); +} + +void Server::Start() { + GPR_ASSERT(!started_); + started_ = true; + grpc_server_start(server_); + + // Start processing rpcs. + ScheduleCallback(); +} + +void Server::AllowOneRpc() { + GPR_ASSERT(started_); + grpc_call_error err = grpc_server_request_call(server_, nullptr); + GPR_ASSERT(err == GRPC_CALL_OK); +} + +void Server::Shutdown() { + { + std::unique_lock<std::mutex> lock(mu_); + if (started_ && !shutdown_) { + shutdown_ = true; + grpc_server_shutdown(server_); + + // Wait for running callbacks to finish. + while (num_running_cb_ != 0) { + callback_cv_.wait(lock); + } + } + } + + // Shutdown the completion queue. + cq_.Shutdown(); + void* tag = nullptr; + CompletionQueue::CompletionType t = cq_.Next(&tag); + GPR_ASSERT(t == CompletionQueue::QUEUE_CLOSED); +} + +void Server::ScheduleCallback() { + { + std::unique_lock<std::mutex> lock(mu_); + num_running_cb_++; + } + std::function<void()> callback = std::bind(&Server::RunRpc, this); + thread_pool_->ScheduleCallback(callback); +} + +void Server::RunRpc() { + // Wait for one more incoming rpc. + void* tag = nullptr; + AllowOneRpc(); + CompletionQueue::CompletionType t = cq_.Next(&tag); + GPR_ASSERT(t == CompletionQueue::SERVER_RPC_NEW); + + AsyncServerContext* server_context = static_cast<AsyncServerContext*>(tag); + // server_context could be nullptr during server shutdown. + if (server_context != nullptr) { + // Schedule a new callback to handle more rpcs. + ScheduleCallback(); + + RpcServiceMethod* method = nullptr; + auto iter = method_map_.find(server_context->method()); + if (iter != method_map_.end()) { + method = iter->second; + } + ServerRpcHandler rpc_handler(server_context, method); + rpc_handler.StartRpc(); + } + + { + std::unique_lock<std::mutex> lock(mu_); + num_running_cb_--; + if (shutdown_) { + callback_cv_.notify_all(); + } + } +} + +} // namespace grpc diff --git a/src/cpp/server/server_builder.cc b/src/cpp/server/server_builder.cc new file mode 100644 index 0000000000..d5d0689bc5 --- /dev/null +++ b/src/cpp/server/server_builder.cc @@ -0,0 +1,66 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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++/server_builder.h> + +#include <grpc++/server.h> + +namespace grpc { + +ServerBuilder::ServerBuilder() : thread_pool_(nullptr) {} + +void ServerBuilder::RegisterService(RpcService* service) { + services_.push_back(service); +} + +void ServerBuilder::AddPort(const grpc::string& addr) { + ports_.push_back(addr); +} + +void ServerBuilder::SetThreadPool(ThreadPoolInterface* thread_pool) { + thread_pool_ = thread_pool; +} + +std::unique_ptr<Server> ServerBuilder::BuildAndStart() { + std::unique_ptr<Server> server(new Server(thread_pool_)); + for (auto* service : services_) { + server->RegisterService(service); + } + for (auto& port : ports_) { + server->AddPort(port); + } + server->Start(); + return server; +} + +} // namespace grpc diff --git a/src/cpp/server/server_rpc_handler.cc b/src/cpp/server/server_rpc_handler.cc new file mode 100644 index 0000000000..2d5a081deb --- /dev/null +++ b/src/cpp/server/server_rpc_handler.cc @@ -0,0 +1,108 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/cpp/server/server_rpc_handler.h" + +#include <grpc/support/log.h> +#include "src/cpp/server/rpc_service_method.h" +#include <grpc++/async_server_context.h> + +namespace grpc { + +ServerRpcHandler::ServerRpcHandler(AsyncServerContext* server_context, + RpcServiceMethod* method) + : server_context_(server_context), + method_(method) { +} + +void ServerRpcHandler::StartRpc() { + // Start the rpc on this dedicated completion queue. + server_context_->Accept(cq_.cq()); + + if (method_ == nullptr) { + // Method not supported, finish the rpc with error. + // TODO(rocking): do we need to call read to consume the request? + FinishRpc(Status(StatusCode::UNIMPLEMENTED, "No such method.")); + return; + } + + // Allocate request and response. + std::unique_ptr<google::protobuf::Message> request(method_->AllocateRequestProto()); + std::unique_ptr<google::protobuf::Message> response(method_->AllocateResponseProto()); + + // Read request + server_context_->StartRead(request.get()); + auto type = WaitForNextEvent(); + GPR_ASSERT(type == CompletionQueue::SERVER_READ_OK); + + // Run the application's rpc handler + MethodHandler* handler = method_->handler(); + Status status = handler->RunHandler( + MethodHandler::HandlerParameter(request.get(), response.get())); + + if (status.IsOk()) { + // Send the response if we get an ok status. + server_context_->StartWrite(*response, 0); + type = WaitForNextEvent(); + if (type != CompletionQueue::SERVER_WRITE_OK) { + status = Status(StatusCode::INTERNAL, "Error writing response."); + } + } + + FinishRpc(status); +} + +CompletionQueue::CompletionType ServerRpcHandler::WaitForNextEvent() { + void* tag = nullptr; + CompletionQueue::CompletionType type = cq_.Next(&tag); + if (type != CompletionQueue::QUEUE_CLOSED && + type != CompletionQueue::RPC_END) { + GPR_ASSERT(static_cast<AsyncServerContext*>(tag) == server_context_.get()); + } + return type; +} + +void ServerRpcHandler::FinishRpc(const Status& status) { + server_context_->StartWriteStatus(status); + CompletionQueue::CompletionType type = WaitForNextEvent(); + // TODO(rocking): do we care about this return type? + + type = WaitForNextEvent(); + GPR_ASSERT(type == CompletionQueue::RPC_END); + + cq_.Shutdown(); + type = WaitForNextEvent(); + GPR_ASSERT(type == CompletionQueue::QUEUE_CLOSED); +} + +} // namespace grpc diff --git a/src/cpp/server/server_rpc_handler.h b/src/cpp/server/server_rpc_handler.h new file mode 100644 index 0000000000..ca48fd74dd --- /dev/null +++ b/src/cpp/server/server_rpc_handler.h @@ -0,0 +1,66 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 __GRPCPP_INTERNAL_SERVER_SERVER_RPC_HANDLER_H__ +#define __GRPCPP_INTERNAL_SERVER_SERVER_RPC_HANDLER_H__ + +#include <memory> + +#include <grpc++/completion_queue.h> +#include <grpc++/status.h> + +namespace grpc { + +class AsyncServerContext; +class RpcServiceMethod; + +class ServerRpcHandler { + public: + // Takes ownership of server_context. + ServerRpcHandler(AsyncServerContext* server_context, + RpcServiceMethod* method); + + void StartRpc(); + + private: + CompletionQueue::CompletionType WaitForNextEvent(); + void FinishRpc(const Status& status); + + std::unique_ptr<AsyncServerContext> server_context_; + RpcServiceMethod* method_; + CompletionQueue cq_; +}; + +} // namespace grpc + +#endif // __GRPCPP_INTERNAL_SERVER_SERVER_RPC_HANDLER_H__ diff --git a/src/cpp/server/thread_pool.cc b/src/cpp/server/thread_pool.cc new file mode 100644 index 0000000000..ce364c4795 --- /dev/null +++ b/src/cpp/server/thread_pool.cc @@ -0,0 +1,77 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/cpp/server/thread_pool.h" + +namespace grpc { + +ThreadPool::ThreadPool(int num_threads) { + for (int i = 0; i < num_threads; i++) { + threads_.push_back(std::thread([=]() { + for (;;) { + std::unique_lock<std::mutex> lock(mu_); + // Wait until work is available or we are shutting down. + cv_.wait(lock, [=]() { return shutdown_ || !callbacks_.empty(); }); + // Drain callbacks before considering shutdown to ensure all work + // gets completed. + if (!callbacks_.empty()) { + auto cb = callbacks_.front(); + callbacks_.pop(); + lock.unlock(); + cb(); + } else if (shutdown_) { + return; + } + } + })); + } +} + +ThreadPool::~ThreadPool() { + { + std::lock_guard<std::mutex> lock(mu_); + shutdown_ = true; + cv_.notify_all(); + } + for (auto& t : threads_) { + t.join(); + } +} + +void ThreadPool::ScheduleCallback(const std::function<void()>& callback) { + std::lock_guard<std::mutex> lock(mu_); + callbacks_.push(callback); + cv_.notify_all(); +} + +} // namespace grpc diff --git a/src/cpp/server/thread_pool.h b/src/cpp/server/thread_pool.h new file mode 100644 index 0000000000..6fc71d6695 --- /dev/null +++ b/src/cpp/server/thread_pool.h @@ -0,0 +1,64 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 __GRPCPP_INTERNAL_SERVER_THREAD_POOL_H__ +#define __GRPCPP_INTERNAL_SERVER_THREAD_POOL_H__ + +#include <grpc++/thread_pool_interface.h> + +#include <condition_variable> +#include <thread> +#include <mutex> +#include <queue> +#include <vector> + +namespace grpc { + +class ThreadPool : public ThreadPoolInterface { + public: + explicit ThreadPool(int num_threads); + ~ThreadPool(); + + void ScheduleCallback(const std::function<void()>& callback) final; + + private: + std::mutex mu_; + std::condition_variable cv_; + bool shutdown_ = false; + std::queue<std::function<void()>> callbacks_; + std::vector<std::thread> threads_; +}; + +} // namespace grpc + +#endif // __GRPCPP_INTERNAL_SERVER_THREAD_POOL_H__ diff --git a/src/cpp/stream/stream_context.cc b/src/cpp/stream/stream_context.cc new file mode 100644 index 0000000000..07e771f7e1 --- /dev/null +++ b/src/cpp/stream/stream_context.cc @@ -0,0 +1,276 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/cpp/stream/stream_context.h" + +#include <grpc/grpc.h> +#include <grpc/support/log.h> +#include "src/cpp/rpc_method.h" +#include "src/cpp/proto/proto_utils.h" +#include "src/cpp/util/time.h" +#include <grpc++/client_context.h> +#include <grpc++/config.h> +#include <google/protobuf/message.h> + +namespace grpc { + +// Client only ctor +StreamContext::StreamContext(const RpcMethod& method, ClientContext* context, + const google::protobuf::Message* request, + google::protobuf::Message* result) + : is_client_(true), + method_(&method), + context_(context), + request_(request), + result_(result), + invoke_ev_(nullptr), + read_ev_(nullptr), + write_ev_(nullptr), + reading_(false), + writing_(false), + got_read_(false), + got_write_(false), + peer_halfclosed_(false), + self_halfclosed_(false), + stream_finished_(false), + waiting_(false) { + GPR_ASSERT(method_->method_type() != RpcMethod::RpcType::NORMAL_RPC); +} + +StreamContext::~StreamContext() { cq_poller_.join(); } + +void StreamContext::PollingLoop() { + grpc_event* ev = nullptr; + gpr_timespec absolute_deadline; + AbsoluteDeadlineTimepoint2Timespec(context_->absolute_deadline(), + &absolute_deadline); + std::condition_variable* cv_to_notify = nullptr; + std::unique_lock<std::mutex> lock(mu_, std::defer_lock); + while (1) { + cv_to_notify = nullptr; + lock.lock(); + if (stream_finished_ && !reading_ && !writing_) { + return; + } + lock.unlock(); + ev = grpc_completion_queue_next(context_->cq(), absolute_deadline); + lock.lock(); + if (!ev) { + stream_finished_ = true; + final_status_ = Status(StatusCode::DEADLINE_EXCEEDED); + std::condition_variable* cvs[3] = {reading_ ? &read_cv_ : nullptr, + writing_ ? &write_cv_ : nullptr, + waiting_ ? &finish_cv_ : nullptr}; + got_read_ = reading_; + got_write_ = writing_; + read_ev_ = nullptr; + write_ev_ = nullptr; + lock.unlock(); + for (int i = 0; i < 3; i++) { + if (cvs[i]) cvs[i]->notify_one(); + } + break; + } + switch (ev->type) { + case GRPC_READ: + GPR_ASSERT(reading_); + got_read_ = true; + read_ev_ = ev; + cv_to_notify = &read_cv_; + reading_ = false; + break; + case GRPC_FINISH_ACCEPTED: + case GRPC_WRITE_ACCEPTED: + got_write_ = true; + write_ev_ = ev; + cv_to_notify = &write_cv_; + writing_ = false; + break; + case GRPC_FINISHED: { + grpc::string error_details( + ev->data.finished.details ? ev->data.finished.details : ""); + final_status_ = Status(static_cast<StatusCode>(ev->data.finished.code), + error_details); + grpc_event_finish(ev); + stream_finished_ = true; + if (waiting_) { + cv_to_notify = &finish_cv_; + } + break; + } + case GRPC_INVOKE_ACCEPTED: + invoke_ev_ = ev; + cv_to_notify = &invoke_cv_; + break; + case GRPC_CLIENT_METADATA_READ: + grpc_event_finish(ev); + break; + default: + grpc_event_finish(ev); + // not handling other types now + gpr_log(GPR_ERROR, "unknown event type"); + abort(); + } + lock.unlock(); + if (cv_to_notify) { + cv_to_notify->notify_one(); + } + } +} + +void StreamContext::Start(bool buffered) { + // TODO(yangg) handle metadata send path + int flag = buffered ? GRPC_WRITE_BUFFER_HINT : 0; + grpc_call_error error = grpc_call_start_invoke( + context_->call(), context_->cq(), this, this, this, flag); + GPR_ASSERT(GRPC_CALL_OK == error); + // kicks off the poller thread + cq_poller_ = std::thread(&StreamContext::PollingLoop, this); + std::unique_lock<std::mutex> lock(mu_); + while (!invoke_ev_) { + invoke_cv_.wait(lock); + } + lock.unlock(); + GPR_ASSERT(invoke_ev_->data.invoke_accepted == GRPC_OP_OK); + grpc_event_finish(invoke_ev_); +} + +namespace { +// Wait for got_event with event_cv protected by mu, return event. +grpc_event* WaitForEvent(bool* got_event, std::condition_variable* event_cv, + std::mutex* mu, grpc_event** event) { + std::unique_lock<std::mutex> lock(*mu); + while (*got_event == false) { + event_cv->wait(lock); + } + *got_event = false; + return *event; +} +} // namespace + +bool StreamContext::Read(google::protobuf::Message* msg) { + std::unique_lock<std::mutex> lock(mu_); + if (stream_finished_) { + peer_halfclosed_ = true; + return false; + } + reading_ = true; + lock.unlock(); + + grpc_call_error err = grpc_call_start_read(context_->call(), this); + GPR_ASSERT(err == GRPC_CALL_OK); + + grpc_event* ev = WaitForEvent(&got_read_, &read_cv_, &mu_, &read_ev_); + if (!ev) { + return false; + } + GPR_ASSERT(ev->type == GRPC_READ); + bool ret = true; + if (ev->data.read) { + if (!DeserializeProto(ev->data.read, msg)) { + ret = false; // parse error + // TODO(yangg) cancel the stream. + } + } else { + ret = false; + peer_halfclosed_ = true; + } + grpc_event_finish(ev); + return ret; +} + +bool StreamContext::Write(const google::protobuf::Message* msg, bool is_last) { + bool ret = true; + grpc_event* ev = nullptr; + + std::unique_lock<std::mutex> lock(mu_); + if (stream_finished_) { + self_halfclosed_ = true; + return false; + } + writing_ = true; + lock.unlock(); + + if (msg) { + grpc_byte_buffer* out_buf = nullptr; + if (!SerializeProto(*msg, &out_buf)) { + FinishStream(Status(StatusCode::INVALID_ARGUMENT, + "Failed to serialize request proto"), + true); + return false; + } + int flag = is_last ? GRPC_WRITE_BUFFER_HINT : 0; + grpc_call_error err = + grpc_call_start_write(context_->call(), out_buf, this, flag); + grpc_byte_buffer_destroy(out_buf); + GPR_ASSERT(err == GRPC_CALL_OK); + + ev = WaitForEvent(&got_write_, &write_cv_, &mu_, &write_ev_); + if (!ev) { + return false; + } + GPR_ASSERT(ev->type == GRPC_WRITE_ACCEPTED); + + ret = ev->data.write_accepted == GRPC_OP_OK; + grpc_event_finish(ev); + } + if (is_last) { + grpc_call_error err = grpc_call_writes_done(context_->call(), this); + GPR_ASSERT(err == GRPC_CALL_OK); + ev = WaitForEvent(&got_write_, &write_cv_, &mu_, &write_ev_); + if (!ev) { + return false; + } + GPR_ASSERT(ev->type == GRPC_FINISH_ACCEPTED); + grpc_event_finish(ev); + self_halfclosed_ = true; + } + return ret; +} + +const Status& StreamContext::Wait() { + std::unique_lock<std::mutex> lock(mu_); + // TODO(yangg) if not halfclosed cancel the stream + GPR_ASSERT(self_halfclosed_); + GPR_ASSERT(peer_halfclosed_); + GPR_ASSERT(!waiting_); + waiting_ = true; + while (!stream_finished_) { + finish_cv_.wait(lock); + } + return final_status_; +} + +void StreamContext::FinishStream(const Status& status, bool send) { return; } + +} // namespace grpc diff --git a/src/cpp/stream/stream_context.h b/src/cpp/stream/stream_context.h new file mode 100644 index 0000000000..b7f462f323 --- /dev/null +++ b/src/cpp/stream/stream_context.h @@ -0,0 +1,105 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 __GRPCPP_INTERNAL_STREAM_STREAM_CONTEXT_H__ +#define __GRPCPP_INTERNAL_STREAM_STREAM_CONTEXT_H__ + +#include <condition_variable> +#include <mutex> +#include <thread> + +#include <grpc++/status.h> +#include <grpc++/stream_context_interface.h> + +namespace google { +namespace protobuf { +class Message; +} +} + +struct grpc_event; + +namespace grpc { +class ClientContext; +class RpcMethod; + +class StreamContext : public StreamContextInterface { + public: + StreamContext(const RpcMethod& method, ClientContext* context, + const google::protobuf::Message* request, google::protobuf::Message* result); + ~StreamContext(); + // Start the stream, if there is a final write following immediately, set + // buffered so that the messages can be sent in batch. + void Start(bool buffered) override; + bool Read(google::protobuf::Message* msg) override; + bool Write(const google::protobuf::Message* msg, bool is_last) override; + const Status& Wait() override; + void FinishStream(const Status& status, bool send) override; + + const google::protobuf::Message* request() override { return request_; } + google::protobuf::Message* response() override { return result_; } + + private: + void PollingLoop(); + bool BlockingStart(); + bool is_client_; + const RpcMethod* method_; // not owned + ClientContext* context_; // now owned + const google::protobuf::Message* request_; // not owned + google::protobuf::Message* result_; // not owned + + std::thread cq_poller_; + std::mutex mu_; + std::condition_variable invoke_cv_; + std::condition_variable read_cv_; + std::condition_variable write_cv_; + std::condition_variable finish_cv_; + grpc_event* invoke_ev_; + // TODO(yangg) make these two into queues to support concurrent reads and + // writes + grpc_event* read_ev_; + grpc_event* write_ev_; + bool reading_; + bool writing_; + bool got_read_; + bool got_write_; + bool peer_halfclosed_; + bool self_halfclosed_; + bool stream_finished_; + bool waiting_; + Status final_status_; +}; + +} // namespace grpc + +#endif // __GRPCPP_INTERNAL_STREAM_STREAM_CONTEXT_H__ diff --git a/src/cpp/util/status.cc b/src/cpp/util/status.cc new file mode 100644 index 0000000000..66be26da07 --- /dev/null +++ b/src/cpp/util/status.cc @@ -0,0 +1,42 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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++/status.h> + +namespace grpc { + +const Status& Status::OK = Status(); +const Status& Status::Cancelled = Status(StatusCode::CANCELLED); + +} // namespace grpc diff --git a/src/cpp/util/time.cc b/src/cpp/util/time.cc new file mode 100644 index 0000000000..207bebf568 --- /dev/null +++ b/src/cpp/util/time.cc @@ -0,0 +1,61 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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/cpp/util/time.h" + +#include <grpc/support/time.h> + +using std::chrono::duration_cast; +using std::chrono::nanoseconds; +using std::chrono::seconds; +using std::chrono::system_clock; + +namespace grpc { + +void AbsoluteDeadlineTimepoint2Timespec(const system_clock::time_point& from, + gpr_timespec* to) { + system_clock::duration deadline = from.time_since_epoch(); + seconds secs = duration_cast<seconds>(deadline); + nanoseconds nsecs = duration_cast<nanoseconds>(deadline - secs); + to->tv_sec = secs.count(); + to->tv_nsec = nsecs.count(); +} + +system_clock::time_point AbsoluteDeadlineTimespec2Timepoint(gpr_timespec t) { + system_clock::time_point tp; + tp += seconds(t.tv_sec); + tp += nanoseconds(t.tv_nsec); + return tp; +} + +} // namespace grpc diff --git a/src/cpp/util/time.h b/src/cpp/util/time.h new file mode 100644 index 0000000000..c21fba7ec3 --- /dev/null +++ b/src/cpp/util/time.h @@ -0,0 +1,52 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (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 __GRPCPP_INTERNAL_UTIL_TIME_H__ +#define __GRPCPP_INTERNAL_UTIL_TIME_H__ + +#include <chrono> + +#include <grpc/support/time.h> + +namespace grpc { + +// from and to should be absolute time. +void AbsoluteDeadlineTimepoint2Timespec( + const std::chrono::system_clock::time_point& from, gpr_timespec* to); + +std::chrono::system_clock::time_point AbsoluteDeadlineTimespec2Timepoint( + gpr_timespec t); + +} // namespace grpc + +#endif // __GRPCPP_INTERNAL_UTIL_TIME_H__ |