diff options
Diffstat (limited to 'src/core/transport')
37 files changed, 7792 insertions, 0 deletions
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__ */ |