summaryrefslogtreecommitdiffstats
path: root/lib/nghttp3_stream.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/nghttp3_stream.c1248
1 files changed, 1248 insertions, 0 deletions
diff --git a/lib/nghttp3_stream.c b/lib/nghttp3_stream.c
new file mode 100644
index 0000000..457f5e4
--- /dev/null
+++ b/lib/nghttp3_stream.c
@@ -0,0 +1,1248 @@
+/*
+ * nghttp3
+ *
+ * Copyright (c) 2019 nghttp3 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp3_stream.h"
+
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+
+#include "nghttp3_conv.h"
+#include "nghttp3_macro.h"
+#include "nghttp3_frame.h"
+#include "nghttp3_conn.h"
+#include "nghttp3_str.h"
+#include "nghttp3_http.h"
+#include "nghttp3_vec.h"
+#include "nghttp3_unreachable.h"
+
+/* NGHTTP3_STREAM_MAX_COPY_THRES is the maximum size of buffer which
+ makes a copy to outq. */
+#define NGHTTP3_STREAM_MAX_COPY_THRES 128
+
+/* NGHTTP3_MIN_RBLEN is the minimum length of nghttp3_ringbuf */
+#define NGHTTP3_MIN_RBLEN 4
+
+int nghttp3_stream_new(nghttp3_stream **pstream, int64_t stream_id,
+ const nghttp3_stream_callbacks *callbacks,
+ nghttp3_objalloc *out_chunk_objalloc,
+ nghttp3_objalloc *stream_objalloc,
+ const nghttp3_mem *mem) {
+ nghttp3_stream *stream = nghttp3_objalloc_stream_get(stream_objalloc);
+
+ if (stream == NULL) {
+ return NGHTTP3_ERR_NOMEM;
+ }
+
+ memset(stream, 0, sizeof(*stream));
+
+ stream->out_chunk_objalloc = out_chunk_objalloc;
+ stream->stream_objalloc = stream_objalloc;
+
+ nghttp3_tnode_init(&stream->node, stream_id, NGHTTP3_DEFAULT_URGENCY);
+
+ nghttp3_ringbuf_init(&stream->frq, 0, sizeof(nghttp3_frame_entry), mem);
+ nghttp3_ringbuf_init(&stream->chunks, 0, sizeof(nghttp3_buf), mem);
+ nghttp3_ringbuf_init(&stream->outq, 0, sizeof(nghttp3_typed_buf), mem);
+ nghttp3_ringbuf_init(&stream->inq, 0, sizeof(nghttp3_buf), mem);
+
+ nghttp3_qpack_stream_context_init(&stream->qpack_sctx, stream_id, mem);
+
+ stream->qpack_blocked_pe.index = NGHTTP3_PQ_BAD_INDEX;
+ stream->mem = mem;
+ stream->tx.offset = 0;
+ stream->rx.http.status_code = -1;
+ stream->rx.http.content_length = -1;
+ stream->rx.http.pri = NGHTTP3_DEFAULT_URGENCY;
+ stream->error_code = NGHTTP3_H3_NO_ERROR;
+
+ if (callbacks) {
+ stream->callbacks = *callbacks;
+ }
+
+ *pstream = stream;
+
+ return 0;
+}
+
+static void delete_outq(nghttp3_ringbuf *outq, const nghttp3_mem *mem) {
+ nghttp3_typed_buf *tbuf;
+ size_t i, len = nghttp3_ringbuf_len(outq);
+
+ for (i = 0; i < len; ++i) {
+ tbuf = nghttp3_ringbuf_get(outq, i);
+ if (tbuf->type == NGHTTP3_BUF_TYPE_PRIVATE) {
+ nghttp3_buf_free(&tbuf->buf, mem);
+ }
+ }
+
+ nghttp3_ringbuf_free(outq);
+}
+
+static void delete_chunks(nghttp3_ringbuf *chunks, const nghttp3_mem *mem) {
+ nghttp3_buf *buf;
+ size_t i, len = nghttp3_ringbuf_len(chunks);
+
+ for (i = 0; i < len; ++i) {
+ buf = nghttp3_ringbuf_get(chunks, i);
+ nghttp3_buf_free(buf, mem);
+ }
+
+ nghttp3_ringbuf_free(chunks);
+}
+
+static void delete_out_chunks(nghttp3_ringbuf *chunks,
+ nghttp3_objalloc *out_chunk_objalloc,
+ const nghttp3_mem *mem) {
+ nghttp3_buf *buf;
+ size_t i, len = nghttp3_ringbuf_len(chunks);
+
+ for (i = 0; i < len; ++i) {
+ buf = nghttp3_ringbuf_get(chunks, i);
+
+ if (nghttp3_buf_cap(buf) == NGHTTP3_STREAM_MIN_CHUNK_SIZE) {
+ nghttp3_objalloc_chunk_release(out_chunk_objalloc,
+ (nghttp3_chunk *)(void *)buf->begin);
+ continue;
+ }
+
+ nghttp3_buf_free(buf, mem);
+ }
+
+ nghttp3_ringbuf_free(chunks);
+}
+
+static void delete_frq(nghttp3_ringbuf *frq, const nghttp3_mem *mem) {
+ nghttp3_frame_entry *frent;
+ size_t i, len = nghttp3_ringbuf_len(frq);
+
+ for (i = 0; i < len; ++i) {
+ frent = nghttp3_ringbuf_get(frq, i);
+ switch (frent->fr.hd.type) {
+ case NGHTTP3_FRAME_HEADERS:
+ nghttp3_frame_headers_free(&frent->fr.headers, mem);
+ break;
+ default:
+ break;
+ }
+ }
+
+ nghttp3_ringbuf_free(frq);
+}
+
+void nghttp3_stream_del(nghttp3_stream *stream) {
+ if (stream == NULL) {
+ return;
+ }
+
+ nghttp3_qpack_stream_context_free(&stream->qpack_sctx);
+ delete_chunks(&stream->inq, stream->mem);
+ delete_outq(&stream->outq, stream->mem);
+ delete_out_chunks(&stream->chunks, stream->out_chunk_objalloc, stream->mem);
+ delete_frq(&stream->frq, stream->mem);
+ nghttp3_tnode_free(&stream->node);
+
+ nghttp3_objalloc_stream_release(stream->stream_objalloc, stream);
+}
+
+void nghttp3_varint_read_state_reset(nghttp3_varint_read_state *rvint) {
+ memset(rvint, 0, sizeof(*rvint));
+}
+
+void nghttp3_stream_read_state_reset(nghttp3_stream_read_state *rstate) {
+ memset(rstate, 0, sizeof(*rstate));
+}
+
+nghttp3_ssize nghttp3_read_varint(nghttp3_varint_read_state *rvint,
+ const uint8_t *src, size_t srclen, int fin) {
+ size_t nread = 0;
+ size_t n;
+ size_t i;
+
+ assert(srclen > 0);
+
+ if (rvint->left == 0) {
+ assert(rvint->acc == 0);
+
+ rvint->left = nghttp3_get_varintlen(src);
+ if (rvint->left <= srclen) {
+ rvint->acc = nghttp3_get_varint(&nread, src);
+ rvint->left = 0;
+ return (nghttp3_ssize)nread;
+ }
+
+ if (fin) {
+ return NGHTTP3_ERR_INVALID_ARGUMENT;
+ }
+
+ rvint->acc = nghttp3_get_varint_fb(src);
+ nread = 1;
+ ++src;
+ --srclen;
+ --rvint->left;
+ }
+
+ n = nghttp3_min(rvint->left, srclen);
+
+ for (i = 0; i < n; ++i) {
+ rvint->acc = (rvint->acc << 8) + src[i];
+ }
+
+ rvint->left -= n;
+ nread += n;
+
+ if (fin && rvint->left) {
+ return NGHTTP3_ERR_INVALID_ARGUMENT;
+ }
+
+ return (nghttp3_ssize)nread;
+}
+
+int nghttp3_stream_frq_add(nghttp3_stream *stream,
+ const nghttp3_frame_entry *frent) {
+ nghttp3_ringbuf *frq = &stream->frq;
+ nghttp3_frame_entry *dest;
+ int rv;
+
+ if (nghttp3_ringbuf_full(frq)) {
+ size_t nlen = nghttp3_max(NGHTTP3_MIN_RBLEN, nghttp3_ringbuf_len(frq) * 2);
+ rv = nghttp3_ringbuf_reserve(frq, nlen);
+ if (rv != 0) {
+ return rv;
+ }
+ }
+
+ dest = nghttp3_ringbuf_push_back(frq);
+ *dest = *frent;
+
+ return 0;
+}
+
+int nghttp3_stream_fill_outq(nghttp3_stream *stream) {
+ nghttp3_ringbuf *frq = &stream->frq;
+ nghttp3_frame_entry *frent;
+ int data_eof;
+ int rv;
+
+ for (; nghttp3_ringbuf_len(frq) && !nghttp3_stream_outq_is_full(stream) &&
+ stream->unsent_bytes < NGHTTP3_MIN_UNSENT_BYTES;) {
+ frent = nghttp3_ringbuf_get(frq, 0);
+
+ switch (frent->fr.hd.type) {
+ case NGHTTP3_FRAME_SETTINGS:
+ rv = nghttp3_stream_write_settings(stream, frent);
+ if (rv != 0) {
+ return rv;
+ }
+ break;
+ case NGHTTP3_FRAME_HEADERS:
+ rv = nghttp3_stream_write_headers(stream, frent);
+ if (rv != 0) {
+ return rv;
+ }
+ nghttp3_frame_headers_free(&frent->fr.headers, stream->mem);
+ break;
+ case NGHTTP3_FRAME_DATA:
+ rv = nghttp3_stream_write_data(stream, &data_eof, frent);
+ if (rv != 0) {
+ return rv;
+ }
+ if (stream->flags & NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED) {
+ return 0;
+ }
+ if (!data_eof) {
+ return 0;
+ }
+ break;
+ case NGHTTP3_FRAME_GOAWAY:
+ rv = nghttp3_stream_write_goaway(stream, frent);
+ if (rv != 0) {
+ return rv;
+ }
+ break;
+ case NGHTTP3_FRAME_PRIORITY_UPDATE:
+ rv = nghttp3_stream_write_priority_update(stream, frent);
+ if (rv != 0) {
+ return rv;
+ }
+ break;
+ default:
+ /* TODO Not implemented */
+ break;
+ }
+
+ nghttp3_ringbuf_pop_front(frq);
+ }
+
+ return 0;
+}
+
+static void typed_buf_shared_init(nghttp3_typed_buf *tbuf,
+ const nghttp3_buf *chunk) {
+ nghttp3_typed_buf_init(tbuf, chunk, NGHTTP3_BUF_TYPE_SHARED);
+ tbuf->buf.pos = tbuf->buf.last;
+}
+
+int nghttp3_stream_write_stream_type(nghttp3_stream *stream) {
+ size_t len = nghttp3_put_varintlen((int64_t)stream->type);
+ nghttp3_buf *chunk;
+ nghttp3_typed_buf tbuf;
+ int rv;
+
+ rv = nghttp3_stream_ensure_chunk(stream, len);
+ if (rv != 0) {
+ return rv;
+ }
+
+ chunk = nghttp3_stream_get_chunk(stream);
+ typed_buf_shared_init(&tbuf, chunk);
+
+ chunk->last = nghttp3_put_varint(chunk->last, (int64_t)stream->type);
+ tbuf.buf.last = chunk->last;
+
+ return nghttp3_stream_outq_add(stream, &tbuf);
+}
+
+int nghttp3_stream_write_settings(nghttp3_stream *stream,
+ nghttp3_frame_entry *frent) {
+ size_t len;
+ int rv;
+ nghttp3_buf *chunk;
+ nghttp3_typed_buf tbuf;
+ struct {
+ nghttp3_frame_settings settings;
+ nghttp3_settings_entry iv[15];
+ } fr;
+ nghttp3_settings_entry *iv;
+ nghttp3_settings *local_settings = frent->aux.settings.local_settings;
+
+ fr.settings.hd.type = NGHTTP3_FRAME_SETTINGS;
+ fr.settings.niv = 3;
+ iv = &fr.settings.iv[0];
+
+ iv[0].id = NGHTTP3_SETTINGS_ID_MAX_FIELD_SECTION_SIZE;
+ iv[0].value = local_settings->max_field_section_size;
+ iv[1].id = NGHTTP3_SETTINGS_ID_QPACK_MAX_TABLE_CAPACITY;
+ iv[1].value = local_settings->qpack_max_dtable_capacity;
+ iv[2].id = NGHTTP3_SETTINGS_ID_QPACK_BLOCKED_STREAMS;
+ iv[2].value = local_settings->qpack_blocked_streams;
+
+ if (local_settings->enable_connect_protocol) {
+ ++fr.settings.niv;
+ iv[3].id = NGHTTP3_SETTINGS_ID_ENABLE_CONNECT_PROTOCOL;
+ iv[3].value = 1;
+ }
+
+ len = nghttp3_frame_write_settings_len(&fr.settings.hd.length, &fr.settings);
+
+ rv = nghttp3_stream_ensure_chunk(stream, len);
+ if (rv != 0) {
+ return rv;
+ }
+
+ chunk = nghttp3_stream_get_chunk(stream);
+ typed_buf_shared_init(&tbuf, chunk);
+
+ chunk->last = nghttp3_frame_write_settings(chunk->last, &fr.settings);
+
+ tbuf.buf.last = chunk->last;
+
+ return nghttp3_stream_outq_add(stream, &tbuf);
+}
+
+int nghttp3_stream_write_goaway(nghttp3_stream *stream,
+ nghttp3_frame_entry *frent) {
+ nghttp3_frame_goaway *fr = &frent->fr.goaway;
+ size_t len;
+ int rv;
+ nghttp3_buf *chunk;
+ nghttp3_typed_buf tbuf;
+
+ len = nghttp3_frame_write_goaway_len(&fr->hd.length, fr);
+
+ rv = nghttp3_stream_ensure_chunk(stream, len);
+ if (rv != 0) {
+ return rv;
+ }
+
+ chunk = nghttp3_stream_get_chunk(stream);
+ typed_buf_shared_init(&tbuf, chunk);
+
+ chunk->last = nghttp3_frame_write_goaway(chunk->last, fr);
+
+ tbuf.buf.last = chunk->last;
+
+ return nghttp3_stream_outq_add(stream, &tbuf);
+}
+
+int nghttp3_stream_write_priority_update(nghttp3_stream *stream,
+ nghttp3_frame_entry *frent) {
+ nghttp3_frame_priority_update *fr = &frent->fr.priority_update;
+ size_t len;
+ int rv;
+ nghttp3_buf *chunk;
+ nghttp3_typed_buf tbuf;
+
+ len = nghttp3_frame_write_priority_update_len(&fr->hd.length, fr);
+
+ rv = nghttp3_stream_ensure_chunk(stream, len);
+ if (rv != 0) {
+ return rv;
+ }
+
+ chunk = nghttp3_stream_get_chunk(stream);
+ typed_buf_shared_init(&tbuf, chunk);
+
+ chunk->last = nghttp3_frame_write_priority_update(chunk->last, fr);
+
+ tbuf.buf.last = chunk->last;
+
+ return nghttp3_stream_outq_add(stream, &tbuf);
+}
+
+int nghttp3_stream_write_headers(nghttp3_stream *stream,
+ nghttp3_frame_entry *frent) {
+ nghttp3_frame_headers *fr = &frent->fr.headers;
+ nghttp3_conn *conn = stream->conn;
+
+ assert(conn);
+
+ return nghttp3_stream_write_header_block(
+ stream, &conn->qenc, conn->tx.qenc, &conn->tx.qpack.rbuf,
+ &conn->tx.qpack.ebuf, NGHTTP3_FRAME_HEADERS, fr->nva, fr->nvlen);
+}
+
+int nghttp3_stream_write_header_block(nghttp3_stream *stream,
+ nghttp3_qpack_encoder *qenc,
+ nghttp3_stream *qenc_stream,
+ nghttp3_buf *rbuf, nghttp3_buf *ebuf,
+ int64_t frame_type, const nghttp3_nv *nva,
+ size_t nvlen) {
+ nghttp3_buf pbuf;
+ int rv;
+ size_t len;
+ nghttp3_buf *chunk;
+ nghttp3_typed_buf tbuf;
+ nghttp3_frame_hd hd;
+ uint8_t raw_pbuf[16];
+ size_t pbuflen, rbuflen, ebuflen;
+
+ nghttp3_buf_wrap_init(&pbuf, raw_pbuf, sizeof(raw_pbuf));
+
+ rv = nghttp3_qpack_encoder_encode(qenc, &pbuf, rbuf, ebuf, stream->node.id,
+ nva, nvlen);
+ if (rv != 0) {
+ goto fail;
+ }
+
+ pbuflen = nghttp3_buf_len(&pbuf);
+ rbuflen = nghttp3_buf_len(rbuf);
+ ebuflen = nghttp3_buf_len(ebuf);
+
+ hd.type = frame_type;
+ hd.length = (int64_t)(pbuflen + rbuflen);
+
+ len = nghttp3_frame_write_hd_len(&hd) + pbuflen;
+
+ if (rbuflen <= NGHTTP3_STREAM_MAX_COPY_THRES) {
+ len += rbuflen;
+ }
+
+ rv = nghttp3_stream_ensure_chunk(stream, len);
+ if (rv != 0) {
+ goto fail;
+ }
+
+ chunk = nghttp3_stream_get_chunk(stream);
+ typed_buf_shared_init(&tbuf, chunk);
+
+ chunk->last = nghttp3_frame_write_hd(chunk->last, &hd);
+
+ chunk->last = nghttp3_cpymem(chunk->last, pbuf.pos, pbuflen);
+ nghttp3_buf_init(&pbuf);
+
+ if (rbuflen > NGHTTP3_STREAM_MAX_COPY_THRES) {
+ tbuf.buf.last = chunk->last;
+
+ rv = nghttp3_stream_outq_add(stream, &tbuf);
+ if (rv != 0) {
+ goto fail;
+ }
+
+ nghttp3_typed_buf_init(&tbuf, rbuf, NGHTTP3_BUF_TYPE_PRIVATE);
+ rv = nghttp3_stream_outq_add(stream, &tbuf);
+ if (rv != 0) {
+ goto fail;
+ }
+ nghttp3_buf_init(rbuf);
+ } else if (rbuflen) {
+ chunk->last = nghttp3_cpymem(chunk->last, rbuf->pos, rbuflen);
+ tbuf.buf.last = chunk->last;
+
+ rv = nghttp3_stream_outq_add(stream, &tbuf);
+ if (rv != 0) {
+ goto fail;
+ }
+ nghttp3_buf_reset(rbuf);
+ }
+
+ if (ebuflen > NGHTTP3_STREAM_MAX_COPY_THRES) {
+ assert(qenc_stream);
+
+ nghttp3_typed_buf_init(&tbuf, ebuf, NGHTTP3_BUF_TYPE_PRIVATE);
+ rv = nghttp3_stream_outq_add(qenc_stream, &tbuf);
+ if (rv != 0) {
+ return rv;
+ }
+ nghttp3_buf_init(ebuf);
+ } else if (ebuflen) {
+ assert(qenc_stream);
+
+ rv = nghttp3_stream_ensure_chunk(qenc_stream, ebuflen);
+ if (rv != 0) {
+ goto fail;
+ }
+
+ chunk = nghttp3_stream_get_chunk(qenc_stream);
+ typed_buf_shared_init(&tbuf, chunk);
+
+ chunk->last = nghttp3_cpymem(chunk->last, ebuf->pos, ebuflen);
+ tbuf.buf.last = chunk->last;
+
+ rv = nghttp3_stream_outq_add(qenc_stream, &tbuf);
+ if (rv != 0) {
+ goto fail;
+ }
+ nghttp3_buf_reset(ebuf);
+ }
+
+ assert(0 == nghttp3_buf_len(&pbuf));
+ assert(0 == nghttp3_buf_len(rbuf));
+ assert(0 == nghttp3_buf_len(ebuf));
+
+ return 0;
+
+fail:
+
+ return rv;
+}
+
+int nghttp3_stream_write_data(nghttp3_stream *stream, int *peof,
+ nghttp3_frame_entry *frent) {
+ int rv;
+ size_t len;
+ nghttp3_typed_buf tbuf;
+ nghttp3_buf buf;
+ nghttp3_buf *chunk;
+ nghttp3_read_data_callback read_data = frent->aux.data.dr.read_data;
+ nghttp3_conn *conn = stream->conn;
+ int64_t datalen;
+ uint32_t flags = 0;
+ nghttp3_frame_hd hd;
+ nghttp3_vec vec[8];
+ nghttp3_vec *v;
+ nghttp3_ssize sveccnt;
+ size_t i;
+
+ assert(!(stream->flags & NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED));
+ assert(read_data);
+ assert(conn);
+
+ *peof = 0;
+
+ sveccnt = read_data(conn, stream->node.id, vec, nghttp3_arraylen(vec), &flags,
+ conn->user_data, stream->user_data);
+ if (sveccnt < 0) {
+ if (sveccnt == NGHTTP3_ERR_WOULDBLOCK) {
+ stream->flags |= NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED;
+ return 0;
+ }
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+
+ datalen = nghttp3_vec_len_varint(vec, (size_t)sveccnt);
+ if (datalen == -1) {
+ return NGHTTP3_ERR_STREAM_DATA_OVERFLOW;
+ }
+
+ assert(datalen || flags & NGHTTP3_DATA_FLAG_EOF);
+
+ if (flags & NGHTTP3_DATA_FLAG_EOF) {
+ *peof = 1;
+ if (!(flags & NGHTTP3_DATA_FLAG_NO_END_STREAM)) {
+ stream->flags |= NGHTTP3_STREAM_FLAG_WRITE_END_STREAM;
+ if (datalen == 0) {
+ if (nghttp3_stream_outq_write_done(stream)) {
+ /* If this is the last data and its is 0 length, we don't
+ need send DATA frame. We rely on the non-emptiness of
+ outq to schedule stream, so add empty tbuf to outq to
+ just send fin. */
+ nghttp3_buf_init(&buf);
+ nghttp3_typed_buf_init(&tbuf, &buf, NGHTTP3_BUF_TYPE_PRIVATE);
+ return nghttp3_stream_outq_add(stream, &tbuf);
+ }
+ return 0;
+ }
+ }
+
+ if (datalen == 0) {
+ /* We are going to send more frames, but no DATA frame this
+ time. */
+ return 0;
+ }
+ }
+
+ hd.type = NGHTTP3_FRAME_DATA;
+ hd.length = datalen;
+
+ len = nghttp3_frame_write_hd_len(&hd);
+
+ rv = nghttp3_stream_ensure_chunk(stream, len);
+ if (rv != 0) {
+ return rv;
+ }
+
+ chunk = nghttp3_stream_get_chunk(stream);
+ typed_buf_shared_init(&tbuf, chunk);
+
+ chunk->last = nghttp3_frame_write_hd(chunk->last, &hd);
+
+ tbuf.buf.last = chunk->last;
+
+ rv = nghttp3_stream_outq_add(stream, &tbuf);
+ if (rv != 0) {
+ return rv;
+ }
+
+ if (datalen) {
+ for (i = 0; i < (size_t)sveccnt; ++i) {
+ v = &vec[i];
+ if (v->len == 0) {
+ continue;
+ }
+ nghttp3_buf_wrap_init(&buf, v->base, v->len);
+ buf.last = buf.end;
+ nghttp3_typed_buf_init(&tbuf, &buf, NGHTTP3_BUF_TYPE_ALIEN);
+ rv = nghttp3_stream_outq_add(stream, &tbuf);
+ if (rv != 0) {
+ return rv;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int nghttp3_stream_write_qpack_decoder_stream(nghttp3_stream *stream) {
+ nghttp3_qpack_decoder *qdec;
+ nghttp3_buf *chunk;
+ int rv;
+ nghttp3_typed_buf tbuf;
+ size_t len;
+
+ assert(stream->conn);
+ assert(stream->conn->tx.qdec == stream);
+
+ qdec = &stream->conn->qdec;
+
+ assert(qdec);
+
+ len = nghttp3_qpack_decoder_get_decoder_streamlen(qdec);
+ if (len == 0) {
+ return 0;
+ }
+
+ rv = nghttp3_stream_ensure_chunk(stream, len);
+ if (rv != 0) {
+ return rv;
+ }
+
+ chunk = nghttp3_stream_get_chunk(stream);
+ typed_buf_shared_init(&tbuf, chunk);
+
+ nghttp3_qpack_decoder_write_decoder(qdec, chunk);
+
+ tbuf.buf.last = chunk->last;
+
+ return nghttp3_stream_outq_add(stream, &tbuf);
+}
+
+int nghttp3_stream_outq_is_full(nghttp3_stream *stream) {
+ /* TODO Verify that the limit is reasonable. */
+ return nghttp3_ringbuf_len(&stream->outq) >= 1024;
+}
+
+int nghttp3_stream_outq_add(nghttp3_stream *stream,
+ const nghttp3_typed_buf *tbuf) {
+ nghttp3_ringbuf *outq = &stream->outq;
+ int rv;
+ nghttp3_typed_buf *dest;
+ size_t len = nghttp3_ringbuf_len(outq);
+ size_t buflen = nghttp3_buf_len(&tbuf->buf);
+
+ if (buflen > NGHTTP3_MAX_VARINT - stream->tx.offset) {
+ return NGHTTP3_ERR_STREAM_DATA_OVERFLOW;
+ }
+
+ stream->tx.offset += buflen;
+ stream->unsent_bytes += buflen;
+
+ if (len) {
+ dest = nghttp3_ringbuf_get(outq, len - 1);
+ if (dest->type == tbuf->type && dest->type == NGHTTP3_BUF_TYPE_SHARED &&
+ dest->buf.begin == tbuf->buf.begin && dest->buf.last == tbuf->buf.pos) {
+ /* If we have already written last entry, adjust outq_idx and
+ offset so that this entry is eligible to send. */
+ if (len == stream->outq_idx) {
+ --stream->outq_idx;
+ stream->outq_offset = nghttp3_buf_len(&dest->buf);
+ }
+
+ dest->buf.last = tbuf->buf.last;
+ /* TODO Is this required? */
+ dest->buf.end = tbuf->buf.end;
+
+ return 0;
+ }
+ }
+
+ if (nghttp3_ringbuf_full(outq)) {
+ size_t nlen = nghttp3_max(NGHTTP3_MIN_RBLEN, len * 2);
+ rv = nghttp3_ringbuf_reserve(outq, nlen);
+ if (rv != 0) {
+ return rv;
+ }
+ }
+
+ dest = nghttp3_ringbuf_push_back(outq);
+ *dest = *tbuf;
+
+ return 0;
+}
+
+int nghttp3_stream_ensure_chunk(nghttp3_stream *stream, size_t need) {
+ nghttp3_ringbuf *chunks = &stream->chunks;
+ nghttp3_buf *chunk;
+ size_t len = nghttp3_ringbuf_len(chunks);
+ uint8_t *p;
+ int rv;
+ size_t n = NGHTTP3_STREAM_MIN_CHUNK_SIZE;
+
+ if (len) {
+ chunk = nghttp3_ringbuf_get(chunks, len - 1);
+ if (nghttp3_buf_left(chunk) >= need) {
+ return 0;
+ }
+ }
+
+ for (; n < need; n *= 2)
+ ;
+
+ if (n == NGHTTP3_STREAM_MIN_CHUNK_SIZE) {
+ p = (uint8_t *)nghttp3_objalloc_chunk_len_get(stream->out_chunk_objalloc,
+ n);
+ } else {
+ p = nghttp3_mem_malloc(stream->mem, n);
+ }
+ if (p == NULL) {
+ return NGHTTP3_ERR_NOMEM;
+ }
+
+ if (nghttp3_ringbuf_full(chunks)) {
+ size_t nlen = nghttp3_max(NGHTTP3_MIN_RBLEN, len * 2);
+ rv = nghttp3_ringbuf_reserve(chunks, nlen);
+ if (rv != 0) {
+ return rv;
+ }
+ }
+
+ chunk = nghttp3_ringbuf_push_back(chunks);
+ nghttp3_buf_wrap_init(chunk, p, n);
+
+ return 0;
+}
+
+nghttp3_buf *nghttp3_stream_get_chunk(nghttp3_stream *stream) {
+ nghttp3_ringbuf *chunks = &stream->chunks;
+ size_t len = nghttp3_ringbuf_len(chunks);
+
+ assert(len);
+
+ return nghttp3_ringbuf_get(chunks, len - 1);
+}
+
+int nghttp3_stream_is_blocked(nghttp3_stream *stream) {
+ return (stream->flags & NGHTTP3_STREAM_FLAG_FC_BLOCKED) ||
+ (stream->flags & NGHTTP3_STREAM_FLAG_SHUT_WR) ||
+ (stream->flags & NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED);
+}
+
+int nghttp3_stream_require_schedule(nghttp3_stream *stream) {
+ return (!nghttp3_stream_outq_write_done(stream) &&
+ !(stream->flags & NGHTTP3_STREAM_FLAG_FC_BLOCKED) &&
+ !(stream->flags & NGHTTP3_STREAM_FLAG_SHUT_WR)) ||
+ (nghttp3_ringbuf_len(&stream->frq) &&
+ !(stream->flags & NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED));
+}
+
+nghttp3_ssize nghttp3_stream_writev(nghttp3_stream *stream, int *pfin,
+ nghttp3_vec *vec, size_t veccnt) {
+ nghttp3_ringbuf *outq = &stream->outq;
+ size_t len = nghttp3_ringbuf_len(outq);
+ size_t i = stream->outq_idx;
+ uint64_t offset = stream->outq_offset;
+ size_t buflen;
+ nghttp3_vec *vbegin = vec, *vend = vec + veccnt;
+ nghttp3_typed_buf *tbuf;
+
+ assert(veccnt > 0);
+
+ if (i < len) {
+ tbuf = nghttp3_ringbuf_get(outq, i);
+ buflen = nghttp3_buf_len(&tbuf->buf);
+
+ if (offset < buflen) {
+ vec->base = tbuf->buf.pos + offset;
+ vec->len = (size_t)(buflen - offset);
+ ++vec;
+ } else {
+ /* This is the only case that satisfies offset >= buflen */
+ assert(0 == offset);
+ assert(0 == buflen);
+ }
+
+ ++i;
+
+ for (; i < len && vec != vend; ++i, ++vec) {
+ tbuf = nghttp3_ringbuf_get(outq, i);
+ vec->base = tbuf->buf.pos;
+ vec->len = nghttp3_buf_len(&tbuf->buf);
+ }
+ }
+
+ /* TODO Rework this if we have finished implementing HTTP
+ messaging */
+ *pfin = nghttp3_ringbuf_len(&stream->frq) == 0 && i == len &&
+ (stream->flags & NGHTTP3_STREAM_FLAG_WRITE_END_STREAM);
+
+ return vec - vbegin;
+}
+
+int nghttp3_stream_add_outq_offset(nghttp3_stream *stream, size_t n) {
+ nghttp3_ringbuf *outq = &stream->outq;
+ size_t i;
+ size_t len = nghttp3_ringbuf_len(outq);
+ uint64_t offset = stream->outq_offset + n;
+ size_t buflen;
+ nghttp3_typed_buf *tbuf;
+
+ for (i = stream->outq_idx; i < len; ++i) {
+ tbuf = nghttp3_ringbuf_get(outq, i);
+ buflen = nghttp3_buf_len(&tbuf->buf);
+ if (offset >= buflen) {
+ offset -= buflen;
+ continue;
+ }
+
+ break;
+ }
+
+ assert(i < len || offset == 0);
+
+ stream->unsent_bytes -= n;
+ stream->outq_idx = i;
+ stream->outq_offset = offset;
+
+ return 0;
+}
+
+int nghttp3_stream_outq_write_done(nghttp3_stream *stream) {
+ nghttp3_ringbuf *outq = &stream->outq;
+ size_t len = nghttp3_ringbuf_len(outq);
+
+ return len == 0 || stream->outq_idx >= len;
+}
+
+static int stream_pop_outq_entry(nghttp3_stream *stream,
+ nghttp3_typed_buf *tbuf) {
+ nghttp3_ringbuf *chunks = &stream->chunks;
+ nghttp3_buf *chunk;
+
+ switch (tbuf->type) {
+ case NGHTTP3_BUF_TYPE_PRIVATE:
+ nghttp3_buf_free(&tbuf->buf, stream->mem);
+ break;
+ case NGHTTP3_BUF_TYPE_ALIEN:
+ break;
+ case NGHTTP3_BUF_TYPE_SHARED:
+ assert(nghttp3_ringbuf_len(chunks));
+
+ chunk = nghttp3_ringbuf_get(chunks, 0);
+
+ assert(chunk->begin == tbuf->buf.begin);
+ assert(chunk->end == tbuf->buf.end);
+
+ if (chunk->last == tbuf->buf.last) {
+ if (nghttp3_buf_cap(chunk) == NGHTTP3_STREAM_MIN_CHUNK_SIZE) {
+ nghttp3_objalloc_chunk_release(stream->out_chunk_objalloc,
+ (nghttp3_chunk *)(void *)chunk->begin);
+ } else {
+ nghttp3_buf_free(chunk, stream->mem);
+ }
+ nghttp3_ringbuf_pop_front(chunks);
+ }
+ break;
+ default:
+ nghttp3_unreachable();
+ };
+
+ nghttp3_ringbuf_pop_front(&stream->outq);
+
+ return 0;
+}
+
+int nghttp3_stream_add_ack_offset(nghttp3_stream *stream, uint64_t n) {
+ nghttp3_ringbuf *outq = &stream->outq;
+ uint64_t offset = stream->ack_offset + n;
+ size_t buflen;
+ size_t npopped = 0;
+ uint64_t nack;
+ nghttp3_typed_buf *tbuf;
+ int rv;
+
+ for (; nghttp3_ringbuf_len(outq);) {
+ tbuf = nghttp3_ringbuf_get(outq, 0);
+ buflen = nghttp3_buf_len(&tbuf->buf);
+
+ if (tbuf->type == NGHTTP3_BUF_TYPE_ALIEN) {
+ nack = nghttp3_min(offset, (uint64_t)buflen) - stream->ack_done;
+ if (stream->callbacks.acked_data) {
+ rv = stream->callbacks.acked_data(stream, stream->node.id, nack,
+ stream->user_data);
+ if (rv != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+ }
+ stream->ack_done += nack;
+ }
+
+ if (offset >= buflen) {
+ rv = stream_pop_outq_entry(stream, tbuf);
+ if (rv != 0) {
+ return rv;
+ }
+
+ offset -= buflen;
+ ++npopped;
+ stream->ack_done = 0;
+
+ if (stream->outq_idx + 1 == npopped) {
+ stream->outq_offset = 0;
+ break;
+ }
+
+ continue;
+ }
+
+ break;
+ }
+
+ assert(stream->outq_idx + 1 >= npopped);
+ if (stream->outq_idx >= npopped) {
+ stream->outq_idx -= npopped;
+ } else {
+ stream->outq_idx = 0;
+ }
+
+ stream->ack_offset = offset;
+
+ return 0;
+}
+
+int nghttp3_stream_buffer_data(nghttp3_stream *stream, const uint8_t *data,
+ size_t datalen) {
+ nghttp3_ringbuf *inq = &stream->inq;
+ size_t len = nghttp3_ringbuf_len(inq);
+ nghttp3_buf *buf;
+ size_t nwrite;
+ uint8_t *rawbuf;
+ size_t bufleft;
+ int rv;
+
+ if (len) {
+ buf = nghttp3_ringbuf_get(inq, len - 1);
+ bufleft = nghttp3_buf_left(buf);
+ nwrite = nghttp3_min(datalen, bufleft);
+ buf->last = nghttp3_cpymem(buf->last, data, nwrite);
+ data += nwrite;
+ datalen -= nwrite;
+ }
+
+ for (; datalen;) {
+ if (nghttp3_ringbuf_full(inq)) {
+ size_t nlen =
+ nghttp3_max(NGHTTP3_MIN_RBLEN, nghttp3_ringbuf_len(inq) * 2);
+ rv = nghttp3_ringbuf_reserve(inq, nlen);
+ if (rv != 0) {
+ return rv;
+ }
+ }
+
+ rawbuf = nghttp3_mem_malloc(stream->mem, 16384);
+ if (rawbuf == NULL) {
+ return NGHTTP3_ERR_NOMEM;
+ }
+
+ buf = nghttp3_ringbuf_push_back(inq);
+ nghttp3_buf_wrap_init(buf, rawbuf, 16384);
+ bufleft = nghttp3_buf_left(buf);
+ nwrite = nghttp3_min(datalen, bufleft);
+ buf->last = nghttp3_cpymem(buf->last, data, nwrite);
+ data += nwrite;
+ datalen -= nwrite;
+ }
+
+ return 0;
+}
+
+size_t nghttp3_stream_get_buffered_datalen(nghttp3_stream *stream) {
+ nghttp3_ringbuf *inq = &stream->inq;
+ size_t len = nghttp3_ringbuf_len(inq);
+ size_t i, n = 0;
+ nghttp3_buf *buf;
+
+ for (i = 0; i < len; ++i) {
+ buf = nghttp3_ringbuf_get(inq, i);
+ n += nghttp3_buf_len(buf);
+ }
+
+ return n;
+}
+
+int nghttp3_stream_transit_rx_http_state(nghttp3_stream *stream,
+ nghttp3_stream_http_event event) {
+ int rv;
+
+ switch (stream->rx.hstate) {
+ case NGHTTP3_HTTP_STATE_NONE:
+ return NGHTTP3_ERR_H3_INTERNAL_ERROR;
+ case NGHTTP3_HTTP_STATE_REQ_INITIAL:
+ switch (event) {
+ case NGHTTP3_HTTP_EVENT_HEADERS_BEGIN:
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_HEADERS_BEGIN;
+ return 0;
+ default:
+ return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
+ }
+ case NGHTTP3_HTTP_STATE_REQ_HEADERS_BEGIN:
+ if (event != NGHTTP3_HTTP_EVENT_HEADERS_END) {
+ return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING;
+ }
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_HEADERS_END;
+ return 0;
+ case NGHTTP3_HTTP_STATE_REQ_HEADERS_END:
+ switch (event) {
+ case NGHTTP3_HTTP_EVENT_HEADERS_BEGIN:
+ /* TODO Better to check status code */
+ if (stream->rx.http.flags & NGHTTP3_HTTP_FLAG_METH_CONNECT) {
+ return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
+ }
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_TRAILERS_BEGIN;
+ return 0;
+ case NGHTTP3_HTTP_EVENT_DATA_BEGIN:
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_DATA_BEGIN;
+ return 0;
+ case NGHTTP3_HTTP_EVENT_MSG_END:
+ rv = nghttp3_http_on_remote_end_stream(stream);
+ if (rv != 0) {
+ return rv;
+ }
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_END;
+ return 0;
+ default:
+ return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
+ }
+ case NGHTTP3_HTTP_STATE_REQ_DATA_BEGIN:
+ if (event != NGHTTP3_HTTP_EVENT_DATA_END) {
+ return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING;
+ }
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_DATA_END;
+ return 0;
+ case NGHTTP3_HTTP_STATE_REQ_DATA_END:
+ switch (event) {
+ case NGHTTP3_HTTP_EVENT_DATA_BEGIN:
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_DATA_BEGIN;
+ return 0;
+ case NGHTTP3_HTTP_EVENT_HEADERS_BEGIN:
+ /* TODO Better to check status code */
+ if (stream->rx.http.flags & NGHTTP3_HTTP_FLAG_METH_CONNECT) {
+ return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
+ }
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_TRAILERS_BEGIN;
+ return 0;
+ case NGHTTP3_HTTP_EVENT_MSG_END:
+ rv = nghttp3_http_on_remote_end_stream(stream);
+ if (rv != 0) {
+ return rv;
+ }
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_END;
+ return 0;
+ default:
+ return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
+ }
+ case NGHTTP3_HTTP_STATE_REQ_TRAILERS_BEGIN:
+ if (event != NGHTTP3_HTTP_EVENT_HEADERS_END) {
+ return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING;
+ }
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_TRAILERS_END;
+ return 0;
+ case NGHTTP3_HTTP_STATE_REQ_TRAILERS_END:
+ if (event != NGHTTP3_HTTP_EVENT_MSG_END) {
+ /* TODO Should ignore unexpected frame in this state as per
+ spec. */
+ return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
+ }
+ rv = nghttp3_http_on_remote_end_stream(stream);
+ if (rv != 0) {
+ return rv;
+ }
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_END;
+ return 0;
+ case NGHTTP3_HTTP_STATE_REQ_END:
+ return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING;
+ case NGHTTP3_HTTP_STATE_RESP_INITIAL:
+ if (event != NGHTTP3_HTTP_EVENT_HEADERS_BEGIN) {
+ return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
+ }
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_HEADERS_BEGIN;
+ return 0;
+ case NGHTTP3_HTTP_STATE_RESP_HEADERS_BEGIN:
+ if (event != NGHTTP3_HTTP_EVENT_HEADERS_END) {
+ return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING;
+ }
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_HEADERS_END;
+ return 0;
+ case NGHTTP3_HTTP_STATE_RESP_HEADERS_END:
+ switch (event) {
+ case NGHTTP3_HTTP_EVENT_HEADERS_BEGIN:
+ if (stream->rx.http.status_code == -1) {
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_HEADERS_BEGIN;
+ return 0;
+ }
+ if ((stream->rx.http.flags & NGHTTP3_HTTP_FLAG_METH_CONNECT) &&
+ stream->rx.http.status_code / 100 == 2) {
+ return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
+ }
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_TRAILERS_BEGIN;
+ return 0;
+ case NGHTTP3_HTTP_EVENT_DATA_BEGIN:
+ if (stream->rx.http.flags & NGHTTP3_HTTP_FLAG_EXPECT_FINAL_RESPONSE) {
+ return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
+ }
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_DATA_BEGIN;
+ return 0;
+ case NGHTTP3_HTTP_EVENT_MSG_END:
+ rv = nghttp3_http_on_remote_end_stream(stream);
+ if (rv != 0) {
+ return rv;
+ }
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_END;
+ return 0;
+ default:
+ return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
+ }
+ case NGHTTP3_HTTP_STATE_RESP_DATA_BEGIN:
+ if (event != NGHTTP3_HTTP_EVENT_DATA_END) {
+ return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING;
+ }
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_DATA_END;
+ return 0;
+ case NGHTTP3_HTTP_STATE_RESP_DATA_END:
+ switch (event) {
+ case NGHTTP3_HTTP_EVENT_DATA_BEGIN:
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_DATA_BEGIN;
+ return 0;
+ case NGHTTP3_HTTP_EVENT_HEADERS_BEGIN:
+ if ((stream->rx.http.flags & NGHTTP3_HTTP_FLAG_METH_CONNECT) &&
+ stream->rx.http.status_code / 100 == 2) {
+ return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
+ }
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_TRAILERS_BEGIN;
+ return 0;
+ case NGHTTP3_HTTP_EVENT_MSG_END:
+ rv = nghttp3_http_on_remote_end_stream(stream);
+ if (rv != 0) {
+ return rv;
+ }
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_END;
+ return 0;
+ default:
+ return NGHTTP3_ERR_H3_FRAME_UNEXPECTED;
+ }
+ case NGHTTP3_HTTP_STATE_RESP_TRAILERS_BEGIN:
+ if (event != NGHTTP3_HTTP_EVENT_HEADERS_END) {
+ return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING;
+ }
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_TRAILERS_END;
+ return 0;
+ case NGHTTP3_HTTP_STATE_RESP_TRAILERS_END:
+ if (event != NGHTTP3_HTTP_EVENT_MSG_END) {
+ return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING;
+ }
+ rv = nghttp3_http_on_remote_end_stream(stream);
+ if (rv != 0) {
+ return rv;
+ }
+ stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_END;
+ return 0;
+ case NGHTTP3_HTTP_STATE_RESP_END:
+ return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING;
+ default:
+ nghttp3_unreachable();
+ }
+}
+
+int nghttp3_stream_empty_headers_allowed(nghttp3_stream *stream) {
+ switch (stream->rx.hstate) {
+ case NGHTTP3_HTTP_STATE_REQ_TRAILERS_BEGIN:
+ case NGHTTP3_HTTP_STATE_RESP_TRAILERS_BEGIN:
+ return 0;
+ default:
+ return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING;
+ }
+}
+
+int nghttp3_stream_uni(int64_t stream_id) { return (stream_id & 0x2) != 0; }
+
+int nghttp3_client_stream_bidi(int64_t stream_id) {
+ return (stream_id & 0x3) == 0;
+}
+
+int nghttp3_client_stream_uni(int64_t stream_id) {
+ return (stream_id & 0x3) == 0x2;
+}
+
+int nghttp3_server_stream_uni(int64_t stream_id) {
+ return (stream_id & 0x3) == 0x3;
+}