# HG changeset patch # User Vladimir Homutov # Date 1584525739 -10800 # Node ID 714a19dba6af855f7752708034ad88a87011dc66 # Parent ae35ccba7aa65e88d867a69d1cc9ac4149fec1b3 Style and handlers. Cleanup in ngx_event_quic.c: + reorderded functions, structures + added missing prototypes + added separate handlers for each frame type + numerous indentation/comments/TODO fixes + removed non-implemented qc->state and corresponding enum; this requires deep thinking, stub was unused. + streams inside quic connection are now in own structure diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -9,67 +9,38 @@ #include -/* TODO: real states, these are stubs */ -typedef enum { - NGX_QUIC_ST_INITIAL, - NGX_QUIC_ST_HANDSHAKE, - NGX_QUIC_ST_APP_DATA -} ngx_quic_state_t; +typedef struct { + ngx_rbtree_node_t node; + ngx_buf_t *b; + ngx_connection_t *c; + ngx_quic_stream_t s; +} ngx_quic_stream_node_t; + + +typedef struct { + ngx_rbtree_t tree; + ngx_rbtree_node_t sentinel; + ngx_msec_t timeout; + ngx_connection_handler_pt handler; +} ngx_quic_streams_t; struct ngx_quic_connection_s { - - ngx_quic_state_t state; - ngx_ssl_t *ssl; - - ngx_quic_frame_t *frames; - - ngx_str_t scid; - ngx_str_t dcid; - ngx_str_t token; - - /* current packet numbers for each namespace */ - ngx_uint_t initial_pn; - ngx_uint_t handshake_pn; - ngx_uint_t appdata_pn; - - ngx_quic_secrets_t secrets; - - /* streams */ - ngx_rbtree_t stree; - ngx_rbtree_node_t stree_sentinel; - ngx_msec_t stream_timeout; - ngx_connection_handler_pt stream_handler; -}; - + ngx_str_t scid; + ngx_str_t dcid; + ngx_str_t token; -typedef struct { - ngx_rbtree_node_t node; - ngx_buf_t *b; - ngx_connection_t *c; - ngx_quic_stream_t s; -} ngx_quic_stream_node_t; - - -static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b); -static ngx_int_t ngx_quic_output(ngx_connection_t *c); + /* current packet numbers for each namespace */ + ngx_uint_t initial_pn; + ngx_uint_t handshake_pn; + ngx_uint_t appdata_pn; -static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, - ngx_quic_header_t *pkt); -static void ngx_quic_close_connection(ngx_connection_t *c); + ngx_quic_secrets_t secrets; + ngx_ssl_t *ssl; + ngx_quic_frame_t *frames; -static ngx_quic_stream_node_t *ngx_quic_stream_lookup(ngx_rbtree_t *rbtree, - ngx_uint_t key); -static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, - ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); - -static void ngx_quic_handshake_handler(ngx_event_t *rev); -static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, - ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_initial_input(ngx_connection_t *c, - ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, - ngx_quic_header_t *pkt); + ngx_quic_streams_t streams; +}; #if BORINGSSL_API_VERSION >= 10 @@ -84,6 +55,7 @@ static int ngx_quic_set_encryption_secre enum ssl_encryption_level_t level, const uint8_t *read_secret, const uint8_t *write_secret, size_t secret_len); #endif + static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *data, size_t len); static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn); @@ -91,6 +63,44 @@ static int ngx_quic_send_alert(ngx_ssl_c enum ssl_encryption_level_t level, uint8_t alert); +static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); +static void ngx_quic_handshake_handler(ngx_event_t *rev); +static void ngx_quic_close_connection(ngx_connection_t *c); + +static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b); +static ngx_int_t ngx_quic_initial_input(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, + ngx_quic_header_t *pkt); + +static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_ack_frame_t *f); +static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_crypto_frame_t *frame); +static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stream_frame_t *frame); + +static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, + ngx_quic_frame_t *frame); + +static ngx_int_t ngx_quic_output(ngx_connection_t *c); +ngx_int_t ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, + ngx_quic_frame_t *end, size_t total); +static ngx_int_t ngx_quic_send_packet(ngx_connection_t *c, + ngx_quic_connection_t *qc, enum ssl_encryption_level_t level, + ngx_str_t *payload); + + +static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); +static ngx_quic_stream_node_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree, + ngx_uint_t key); static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size); static ssize_t ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, @@ -98,6 +108,7 @@ static ssize_t ngx_quic_stream_send(ngx_ static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit); + static SSL_QUIC_METHOD quic_method = { #if BORINGSSL_API_VERSION >= 10 ngx_quic_set_read_secret, @@ -118,333 +129,6 @@ ngx_quic_init_ssl_methods(SSL_CTX* ctx) } -void -ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_msec_t timeout, - ngx_connection_handler_pt handler) -{ - ngx_buf_t *b; - ngx_quic_header_t pkt; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic handshake"); - - c->log->action = "QUIC handshaking"; - - ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); - - b = c->buffer; - - pkt.log = c->log; - pkt.raw = b; - pkt.data = b->start; - pkt.len = b->last - b->start; - - if (ngx_quic_new_connection(c, ssl, &pkt) != NGX_OK) { - ngx_quic_close_connection(c); - return; - } - - // we don't need stream handler for initial packet processing - c->quic->stream_handler = handler; - c->quic->stream_timeout = timeout; - - ngx_add_timer(c->read, timeout); - - c->read->handler = ngx_quic_handshake_handler; - - return; -} - - -static void -ngx_quic_handshake_handler(ngx_event_t *rev) -{ - ssize_t n; - ngx_connection_t *c; - u_char buf[512]; - ngx_buf_t b; - - b.start = buf; - b.end = buf + 512; - b.pos = b.last = b.start; - - c = rev->data; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic handshake handler"); - - if (rev->timedout) { - ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); - ngx_quic_close_connection(c); - return; - } - - if (c->close) { - ngx_quic_close_connection(c); - return; - } - - n = c->recv(c, b.start, b.end - b.start); - - if (n == NGX_AGAIN) { - return; - } - - if (n == NGX_ERROR) { - c->read->eof = 1; - ngx_quic_close_connection(c); - return; - } - - b.last += n; - - if (ngx_quic_input(c, &b) != NGX_OK) { - ngx_quic_close_connection(c); - return; - } -} - - -static void -ngx_quic_close_connection(ngx_connection_t *c) -{ - ngx_pool_t *pool; - - /* XXX wait for all streams to close */ - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "close quic connection: %d", c->fd); - - if (c->ssl) { - (void) ngx_ssl_shutdown(c); - } - -#if (NGX_STAT_STUB) - (void) ngx_atomic_fetch_add(ngx_stat_active, -1); -#endif - - c->destroyed = 1; - - pool = c->pool; - - ngx_close_connection(c); - - ngx_destroy_pool(pool); -} - - -ngx_connection_t * -ngx_quic_create_uni_stream(ngx_connection_t *c) -{ - /* XXX */ - return NULL; -} - - -static ngx_int_t -ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) -{ - u_char *p; - ngx_int_t rc; - ngx_quic_header_t pkt; - - if (c->quic == NULL) { - // XXX: possible? - ngx_log_error(NGX_LOG_INFO, c->log, 0, "BUG: no QUIC in connection"); - return NGX_ERROR; - } - - p = b->start; - - do { - ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); - pkt.raw = b; - pkt.data = p; - pkt.len = b->last - p; - pkt.log = c->log; - - if (p[0] == 0) { - /* XXX: no idea WTF is this, just ignore */ - ngx_log_error(NGX_LOG_ALERT, c->log, 0, "FIREFOX: ZEROES"); - break; - } - - // TODO: check current state - if (p[0] & NGX_QUIC_PKT_LONG) { - - if ((p[0] & 0xf0) == NGX_QUIC_PKT_INITIAL) { - rc = ngx_quic_initial_input(c, &pkt); - - } else if ((p[0] & 0xf0) == NGX_QUIC_PKT_HANDSHAKE) { - rc = ngx_quic_handshake_input(c, &pkt); - - } else { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "BUG: unknown quic state"); - return NGX_ERROR; - } - - } else { - rc = ngx_quic_app_input(c, &pkt); - } - - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - /* b->pos is at header end, adjust by actual packet length */ - p = b->pos + pkt.len; - b->pos = p; /* reset b->pos to the next packet start */ - - } while (p < b->last); - - return NGX_OK; -} - -static ngx_int_t -ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc, - enum ssl_encryption_level_t level, ngx_str_t *payload) -{ - ngx_str_t res; - ngx_quic_header_t pkt; - - pkt.log = c->log; - - static ngx_str_t initial_token = ngx_null_string; - - ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); - ngx_quic_hexdump0(c->log, "payload", payload->data, payload->len); - - pkt.level = level; - pkt.dcid = qc->dcid; - pkt.scid = qc->scid; - - if (level == ssl_encryption_initial) { - pkt.number = &qc->initial_pn; - pkt.flags = NGX_QUIC_PKT_INITIAL; - pkt.secret = &qc->secrets.server.in; - pkt.token = initial_token; - - } else if (level == ssl_encryption_handshake) { - pkt.number = &qc->handshake_pn; - pkt.flags = NGX_QUIC_PKT_HANDSHAKE; - pkt.secret = &qc->secrets.server.hs; - - } else { - pkt.number = &qc->appdata_pn; - pkt.secret = &qc->secrets.server.ad; - } - - if (ngx_quic_encrypt(c->pool, c->ssl->connection, &pkt, payload, &res) - != NGX_OK) - { - return NGX_ERROR; - } - - ngx_quic_hexdump0(c->log, "packet to send", res.data, res.len); - - c->send(c, res.data, res.len); // TODO: err handling - - (*pkt.number)++; - - return NGX_OK; -} - - -/* pack a group of frames [start; end) into memory p and send as single packet */ -ngx_int_t -ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, - ngx_quic_frame_t *end, size_t total) -{ - ssize_t len; - u_char *p; - ngx_str_t out; - ngx_quic_frame_t *f; - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "sending frames %p...%p", start, end); - - p = ngx_pnalloc(c->pool, total); - if (p == NULL) { - return NGX_ERROR; - } - - out.data = p; - - for (f = start; f != end; f = f->next) { - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "frame: %s", f->info); - - len = ngx_quic_create_frame(p, p + total, f); - if (len == -1) { - return NGX_ERROR; - } - - p += len; - } - - out.len = p - out.data; - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "packet ready: %ui bytes at level %d", - out.len, start->level); - - // IOVEC/sendmsg_chain ? - if (ngx_quic_send_packet(c, c->quic, start->level, &out) != NGX_OK) { - return NGX_ERROR; - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_output(ngx_connection_t *c) -{ - size_t len; - ngx_uint_t lvl; - ngx_quic_frame_t *f, *start; - ngx_quic_connection_t *qc; - - qc = c->quic; - - if (qc->frames == NULL) { - return NGX_OK; - } - - lvl = qc->frames->level; - start = qc->frames; - f = start; - - do { - len = 0; - - do { - /* process same-level group of frames */ - - len += ngx_quic_frame_len(f);// TODO: handle overflow, max size - - f = f->next; - } while (f && f->level == lvl); - - - if (ngx_quic_frames_send(c, start, f, len) != NGX_OK) { - return NGX_ERROR; - } - - if (f == NULL) { - break; - } - - lvl = f->level; // TODO: must not decrease (ever, also between calls) - start = f; - - } while (1); - - qc->frames = NULL; - - return NGX_OK; -} - - #if BORINGSSL_API_VERSION >= 10 static int @@ -512,35 +196,14 @@ ngx_quic_set_encryption_secrets(ngx_ssl_ #endif -static void -ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) -{ - ngx_quic_frame_t *f; - - if (qc->frames == NULL) { - qc->frames = frame; - return; - } - - for (f = qc->frames; f->next; f = f->next) { - if (f->next->level > frame->level) { - break; - } - } - - frame->next = f->next; - f->next = frame; -} - - static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *data, size_t len) { - u_char *p; - ngx_quic_frame_t *frame; - ngx_connection_t *c; - ngx_quic_connection_t *qc; + u_char *p; + ngx_quic_frame_t *frame; + ngx_connection_t *c; + ngx_quic_connection_t *qc; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); qc = c->quic; @@ -602,59 +265,122 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_ } +void +ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_msec_t timeout, + ngx_connection_handler_pt handler) +{ + ngx_buf_t *b; + ngx_quic_header_t pkt; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic handshake"); + + c->log->action = "QUIC handshaking"; + + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + + b = c->buffer; + + pkt.log = c->log; + pkt.raw = b; + pkt.data = b->start; + pkt.len = b->last - b->start; + + if (ngx_quic_new_connection(c, ssl, &pkt) != NGX_OK) { + ngx_quic_close_connection(c); + return; + } + + // we don't need stream handler for initial packet processing + c->quic->streams.handler = handler; + c->quic->streams.timeout = timeout; + + ngx_add_timer(c->read, timeout); + + c->read->handler = ngx_quic_handshake_handler; + + return; +} + static ngx_int_t -ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, - ngx_quic_frame_t *frame) +ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, + ngx_quic_header_t *pkt) { - int sslerr; - ssize_t n; - ngx_ssl_conn_t *ssl_conn; - - ssl_conn = c->ssl->connection; + ngx_quic_connection_t *qc; - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "SSL_quic_read_level: %d, SSL_quic_write_level: %d", - (int) SSL_quic_read_level(ssl_conn), - (int) SSL_quic_write_level(ssl_conn)); - + if (ngx_buf_size(pkt->raw) < 1200) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "too small UDP datagram"); + return NGX_ERROR; + } - if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn), - frame->u.crypto.data, frame->u.crypto.len)) - { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "SSL_provide_quic_data() failed"); + if (ngx_quic_parse_long_header(pkt) != NGX_OK) { return NGX_ERROR; } - n = SSL_do_handshake(ssl_conn); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); - - if (n == -1) { - sslerr = SSL_get_error(ssl_conn, n); + if ((pkt->flags & 0xf0) != NGX_QUIC_PKT_INITIAL) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "invalid initial packet: 0x%xi", pkt->flags); + return NGX_ERROR; + } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", - sslerr); + if (ngx_quic_parse_initial_header(pkt) != NGX_OK) { + return NGX_ERROR; + } - if (sslerr == SSL_ERROR_SSL) { - ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); - } + qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); + if (qc == NULL) { + return NGX_ERROR; } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ssl cipher: %s", SSL_get_cipher(ssl_conn)); + ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, + ngx_quic_rbtree_insert_stream); + + c->quic = qc; + qc->ssl = ssl; + + qc->dcid.len = pkt->dcid.len; + qc->dcid.data = ngx_pnalloc(c->pool, pkt->dcid.len); + if (qc->dcid.data == NULL) { + return NGX_ERROR; + } + ngx_memcpy(qc->dcid.data, pkt->dcid.data, qc->dcid.len); + + qc->scid.len = pkt->scid.len; + qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len); + if (qc->scid.data == NULL) { + return NGX_ERROR; + } + ngx_memcpy(qc->scid.data, pkt->scid.data, qc->scid.len); - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "SSL_quic_read_level: %d, SSL_quic_write_level: %d", - (int) SSL_quic_read_level(ssl_conn), - (int) SSL_quic_write_level(ssl_conn)); + qc->token.len = pkt->token.len; + qc->token.data = ngx_pnalloc(c->pool, qc->token.len); + if (qc->token.data == NULL) { + return NGX_ERROR; + } + ngx_memcpy(qc->token.data, pkt->token.data, qc->token.len); + + + if (ngx_quic_set_initial_secret(c->pool, &qc->secrets, &qc->dcid) + != NGX_OK) + { + return NGX_ERROR; + } - return NGX_OK; + pkt->secret = &qc->secrets.client.in; + pkt->level = ssl_encryption_initial; + + if (ngx_quic_decrypt(c->pool, NULL, pkt) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_quic_init_connection(c) != NGX_OK) { + return NGX_ERROR; + } + + return ngx_quic_payload_handler(c, pkt); } - static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c) { @@ -706,541 +432,141 @@ ngx_quic_init_connection(ngx_connection_ } -static ssize_t -ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) +static void +ngx_quic_handshake_handler(ngx_event_t *rev) { - ssize_t len; - ngx_buf_t *b; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; - ngx_quic_stream_node_t *sn; - - qs = c->qs; - qc = qs->parent->quic; - - // XXX: get direct pointer from stream structure? - sn = ngx_quic_stream_lookup(&qc->stree, qs->id); - - if (sn == NULL) { - return NGX_ERROR; - } - - // XXX: how to return EOF? - - b = sn->b; - - if (b->last - b->pos == 0) { - c->read->ready = 0; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic recv() not ready"); - return NGX_AGAIN; // ? - } - - len = ngx_min(b->last - b->pos, (ssize_t) size); + ssize_t n; + ngx_buf_t b; + ngx_connection_t *c; - ngx_memcpy(buf, b->pos, len); - - b->pos += len; - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic recv: %z of %uz", len, size); - - return len; -} - - -static ssize_t -ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) -{ - u_char *p; - ngx_connection_t *pc; - ngx_quic_frame_t *frame; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; - ngx_quic_stream_node_t *sn; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send: %uz", size); - - qs = c->qs; - pc = qs->parent; - qc = pc->quic; - - // XXX: get direct pointer from stream structure? - sn = ngx_quic_stream_lookup(&qc->stree, qs->id); - - if (sn == NULL) { - return NGX_ERROR; - } + u_char buf[512]; - frame = ngx_pcalloc(pc->pool, sizeof(ngx_quic_frame_t)); - if (frame == NULL) { - return 0; - } - - p = ngx_pnalloc(pc->pool, size); - if (p == NULL) { - return 0; - } - - ngx_memcpy(p, buf, size); + b.start = buf; + b.end = buf + 512; + b.pos = b.last = b.start; - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_STREAM6; /* OFF=1 LEN=1 FIN=0 */ - frame->u.stream.off = 1; - frame->u.stream.len = 1; - frame->u.stream.fin = 0; - - frame->u.stream.type = frame->type; - frame->u.stream.stream_id = qs->id; - frame->u.stream.offset = c->sent; - frame->u.stream.length = size; - frame->u.stream.data = p; - - c->sent += size; - - ngx_sprintf(frame->info, "stream %xi len=%ui level=%d", - qs->id, size, frame->level); - - ngx_quic_queue_frame(qc, frame); - - return size; -} - + c = rev->data; -static ngx_chain_t * -ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, - off_t limit) -{ - size_t len; - ssize_t n; - ngx_buf_t *b; - - for ( /* void */; in; in = in->next) { - b = in->buf; - - if (!ngx_buf_in_memory(b)) { - continue; - } + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic handshake handler"); - if (ngx_buf_size(b) == 0) { - continue; - } - - len = b->last - b->pos; - - n = ngx_quic_stream_send(c, b->pos, len); - - if (n == NGX_ERROR) { - return NGX_CHAIN_ERROR; - } - - if (n == NGX_AGAIN) { - return in; - } - - if (n != (ssize_t) len) { - b->pos += n; - return in; - } + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + ngx_quic_close_connection(c); + return; } - return NULL; -} - - -/* process all payload from the current packet and generate ack if required */ -static ngx_int_t -ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) -{ - u_char *end, *p; - ssize_t len; - ngx_buf_t *b; - ngx_log_t *log; - ngx_uint_t ack_this, do_close; - ngx_pool_t *pool; - ngx_event_t *rev, *wev; - ngx_quic_frame_t frame, *ack_frame; - ngx_quic_connection_t *qc; - ngx_quic_stream_node_t *sn; - - qc = c->quic; - - p = pkt->payload.data; - end = p + pkt->payload.len; - - ack_this = 0; - do_close = 0; - - while (p < end) { - - len = ngx_quic_parse_frame(p, end, &frame); - if (len < 0) { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "unknown frame type %xi", frame.type); - // XXX: log here - return NGX_ERROR; - } - - p += len; - - switch (frame.type) { - - case NGX_QUIC_FT_ACK: - - // TODO: handle ack - - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ACK: { largest=%ui delay=%ui first=%ui count=%ui}", - frame.u.ack.largest, - frame.u.ack.delay, - frame.u.ack.first_range, - frame.u.ack.range_count); - - break; - - case NGX_QUIC_FT_CRYPTO: - ngx_quic_hexdump0(c->log, "CRYPTO frame", - frame.u.crypto.data, frame.u.crypto.len); - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic CRYPTO frame length: %uL off:%uL pp:%p", - frame.u.crypto.len, frame.u.crypto.offset, - frame.u.crypto.data); - - if (frame.u.crypto.offset != 0x0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "crypto frame with non-zero offset"); - // TODO: support packet spanning with offsets - return NGX_ERROR; - } - - if (ngx_quic_handle_crypto_frame(c, pkt, &frame) != NGX_OK) { - return NGX_ERROR; - } - - ack_this = 1; - - continue; - - case NGX_QUIC_FT_PADDING: - continue; - - case NGX_QUIC_FT_PING: - ack_this = 1; - continue; - - case NGX_QUIC_FT_NEW_CONNECTION_ID: - ack_this = 1; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "NCID: { seq=%ui retire=%ui len=%ui}", - frame.u.ncid.seqnum, - frame.u.ncid.retire, - frame.u.ncid.len); - continue; - - case NGX_QUIC_FT_CONNECTION_CLOSE: - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "CONN.CLOSE: { %s (0x%xi) type=0x%xi reason='%V'}", - ngx_quic_error_text(frame.u.close.error_code), - frame.u.close.error_code, - frame.u.close.frame_type, - &frame.u.close.reason); - - do_close = 1; - break; + if (c->close) { + ngx_quic_close_connection(c); + return; + } - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: - - ack_this = 1; - - ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, - "STREAM frame 0x%xi id 0x%xi offset 0x%xi len 0x%xi bits:off=%d len=%d fin=%d", - frame.type, - frame.u.stream.stream_id, - frame.u.stream.offset, - frame.u.stream.length, - frame.u.stream.off, - frame.u.stream.len, - frame.u.stream.fin); - - sn = ngx_quic_stream_lookup(&qc->stree, frame.u.stream.stream_id); - if (sn == NULL) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "stream is new"); - - sn = ngx_pcalloc(c->pool, sizeof(ngx_quic_stream_node_t)); - if (sn == NULL) { - return NGX_ERROR; - } - - sn->c = ngx_get_connection(-1, c->log); // TODO: free on connection termination - if (sn->c == NULL) { - return NGX_ERROR; - } - - pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); - if (pool == NULL) { - /* XXX free connection */ - return NGX_ERROR; - } - - log = ngx_palloc(pool, sizeof(ngx_log_t)); - if (log == NULL) { - /* XXX free pool and connection */ - return NGX_ERROR; - } - - *log = *c->log; - pool->log = log; - - sn->c->log = log; - sn->c->pool = pool; - - sn->c->listening = c->listening; - sn->c->sockaddr = c->sockaddr; - sn->c->local_sockaddr = c->local_sockaddr; + n = c->recv(c, b.start, b.end - b.start); - rev = sn->c->read; - wev = sn->c->write; - - rev->ready = 1; - - rev->log = c->log; - wev->log = c->log; - - sn->c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); - - sn->node.key = frame.u.stream.stream_id; - sn->b = ngx_create_temp_buf(pool, 16 * 1024); // XXX enough for everyone - if (sn->b == NULL) { - return NGX_ERROR; - } - b = sn->b; - - ngx_memcpy(b->start, frame.u.stream.data, frame.u.stream.length); - b->last = b->start + frame.u.stream.length; - - ngx_rbtree_insert(&qc->stree, &sn->node); - - sn->s.id = frame.u.stream.stream_id; - sn->s.unidirectional = (sn->s.id & 0x02) ? 1 : 0; - sn->s.parent = c; - sn->c->qs = &sn->s; - - sn->c->recv = ngx_quic_stream_recv; - sn->c->send = ngx_quic_stream_send; - sn->c->send_chain = ngx_quic_stream_send_chain; - - qc->stream_handler(sn->c); - - } else { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "existing stream"); - b = sn->b; - - if ((size_t) (b->end - b->pos) < frame.u.stream.length) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "no space in stream buffer"); - return NGX_ERROR; - } - - ngx_memcpy(b->pos, frame.u.stream.data, frame.u.stream.length); - b->pos += frame.u.stream.length; - - // TODO: ngx_post_event(&c->read, &ngx_posted_events) ??? - } - - ngx_quic_hexdump0(c->log, "STREAM.data", - frame.u.stream.data, frame.u.stream.length); - break; - - default: - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "unexpected frame type 0x%xd in packet", frame.type); - return NGX_ERROR; - } + if (n == NGX_AGAIN) { + return; } - if (p != end) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "trailing garbage in payload: %ui bytes", end - p); - return NGX_ERROR; - } - - if (do_close) { - // TODO: handle stream close - } - - if (ack_this == 0) { - /* do not ack packets with ACKs and PADDING */ - return NGX_OK; + if (n == NGX_ERROR) { + c->read->eof = 1; + ngx_quic_close_connection(c); + return; } - // packet processed, ACK it now if required - // TODO: if (ack_required) ... - currently just ack each packet - - ack_frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); - if (ack_frame == NULL) { - return NGX_ERROR; - } + b.last += n; - ack_frame->level = pkt->level; - ack_frame->type = NGX_QUIC_FT_ACK; - ack_frame->u.ack.pn = pkt->pn; - - ngx_sprintf(ack_frame->info, "ACK for PN=%d from frame handler level=%d", pkt->pn, pkt->level); - ngx_quic_queue_frame(qc, ack_frame); - - return ngx_quic_output(c); + if (ngx_quic_input(c, &b) != NGX_OK) { + ngx_quic_close_connection(c); + return; + } } static void -ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, - ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) +ngx_quic_close_connection(ngx_connection_t *c) { - ngx_rbtree_node_t **p; - ngx_quic_stream_node_t *qn, *qnt; - - for ( ;; ) { + ngx_pool_t *pool; - if (node->key < temp->key) { - - p = &temp->left; - - } else if (node->key > temp->key) { - - p = &temp->right; + /* XXX wait for all streams to close */ - } else { /* node->key == temp->key */ - - qn = (ngx_quic_stream_node_t *) &node->color; - qnt = (ngx_quic_stream_node_t *) &temp->color; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "close quic connection: %d", c->fd); - if (qn->c < qnt->c) { - p = &temp->left; - } else { - p = &temp->right; - } - } - - if (*p == sentinel) { - break; - } - - temp = *p; + if (c->ssl) { + (void) ngx_ssl_shutdown(c); } - *p = node; - node->parent = temp; - node->left = sentinel; - node->right = sentinel; - ngx_rbt_red(node); -} +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, -1); +#endif - -static ngx_quic_stream_node_t * -ngx_quic_stream_lookup(ngx_rbtree_t *rbtree, ngx_uint_t key) -{ - ngx_rbtree_node_t *node, *sentinel; + c->destroyed = 1; - node = rbtree->root; - sentinel = rbtree->sentinel; - - while (node != sentinel) { + pool = c->pool; - if (key == node->key) { - return (ngx_quic_stream_node_t *) node; - } + ngx_close_connection(c); - node = (key < node->key) ? node->left : node->right; - } - - return NULL; + ngx_destroy_pool(pool); } static ngx_int_t -ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, - ngx_quic_header_t *pkt) +ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) { - ngx_quic_connection_t *qc; - - if (ngx_buf_size(pkt->raw) < 1200) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "too small UDP datagram"); - return NGX_ERROR; - } + u_char *p; + ngx_int_t rc; + ngx_quic_header_t pkt; - if (ngx_quic_parse_long_header(pkt) != NGX_OK) { - return NGX_ERROR; - } - - if ((pkt->flags & 0xf0) != NGX_QUIC_PKT_INITIAL) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "invalid initial packet: 0x%xi", pkt->flags); - return NGX_ERROR; - } - - if (ngx_quic_parse_initial_header(pkt) != NGX_OK) { - return NGX_ERROR; - } - - qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); - if (qc == NULL) { + if (c->quic == NULL) { + // XXX: possible? + ngx_log_error(NGX_LOG_INFO, c->log, 0, "BUG: no QUIC in connection"); return NGX_ERROR; } - ngx_rbtree_init(&qc->stree, &qc->stree_sentinel, - ngx_quic_rbtree_insert_stream); + p = b->start; - c->quic = qc; - qc->ssl = ssl; + do { + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + pkt.raw = b; + pkt.data = p; + pkt.len = b->last - p; + pkt.log = c->log; - qc->dcid.len = pkt->dcid.len; - qc->dcid.data = ngx_pnalloc(c->pool, pkt->dcid.len); - if (qc->dcid.data == NULL) { - return NGX_ERROR; - } - ngx_memcpy(qc->dcid.data, pkt->dcid.data, qc->dcid.len); + if (p[0] == 0) { + /* XXX: no idea WTF is this, just ignore */ + ngx_log_error(NGX_LOG_ALERT, c->log, 0, "FIREFOX: ZEROES"); + break; + } - qc->scid.len = pkt->scid.len; - qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len); - if (qc->scid.data == NULL) { - return NGX_ERROR; - } - ngx_memcpy(qc->scid.data, pkt->scid.data, qc->scid.len); + // TODO: check current state + if (p[0] & NGX_QUIC_PKT_LONG) { + + if ((p[0] & 0xf0) == NGX_QUIC_PKT_INITIAL) { + rc = ngx_quic_initial_input(c, &pkt); - qc->token.len = pkt->token.len; - qc->token.data = ngx_pnalloc(c->pool, qc->token.len); - if (qc->token.data == NULL) { - return NGX_ERROR; - } - ngx_memcpy(qc->token.data, pkt->token.data, qc->token.len); + } else if ((p[0] & 0xf0) == NGX_QUIC_PKT_HANDSHAKE) { + rc = ngx_quic_handshake_input(c, &pkt); + } else { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "BUG: unknown quic state"); + return NGX_ERROR; + } - if (ngx_quic_set_initial_secret(c->pool, &qc->secrets, &qc->dcid) - != NGX_OK) - { - return NGX_ERROR; - } + } else { + rc = ngx_quic_app_input(c, &pkt); + } - pkt->secret = &qc->secrets.client.in; - pkt->level = ssl_encryption_initial; + if (rc == NGX_ERROR) { + return NGX_ERROR; + } - if (ngx_quic_decrypt(c->pool, NULL, pkt) != NGX_OK) { - return NGX_ERROR; - } + /* b->pos is at header end, adjust by actual packet length */ + p = b->pos + pkt.len; + b->pos = p; /* reset b->pos to the next packet start */ - if (ngx_quic_init_connection(c) != NGX_OK) { - return NGX_ERROR; - } + } while (p < b->last); - return ngx_quic_payload_handler(c, pkt); + return NGX_OK; } @@ -1355,3 +681,716 @@ ngx_quic_app_input(ngx_connection_t *c, } +static ngx_int_t +ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + u_char *end, *p; + ssize_t len; + ngx_uint_t ack_this, do_close; + ngx_quic_frame_t frame, *ack_frame; + ngx_quic_connection_t *qc; + + qc = c->quic; + + p = pkt->payload.data; + end = p + pkt->payload.len; + + ack_this = 0; + do_close = 0; + + while (p < end) { + + len = ngx_quic_parse_frame(p, end, &frame); + if (len < 0) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "failed to parse frame type %xi", frame.type); + return NGX_ERROR; + } + + p += len; + + switch (frame.type) { + + case NGX_QUIC_FT_ACK: + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ACK: { largest=%ui delay=%ui first=%ui count=%ui}", + frame.u.ack.largest, + frame.u.ack.delay, + frame.u.ack.first_range, + frame.u.ack.range_count); + + if (ngx_quic_handle_ack_frame(c, pkt, &frame.u.ack) != NGX_OK) { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_CRYPTO: + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic CRYPTO frame length: %uL off:%uL pp:%p", + frame.u.crypto.len, frame.u.crypto.offset, + frame.u.crypto.data); + + ngx_quic_hexdump0(c->log, "CRYPTO frame contents", + frame.u.crypto.data, frame.u.crypto.len); + + + if (ngx_quic_handle_crypto_frame(c, pkt, &frame.u.crypto) + != NGX_OK) + { + return NGX_ERROR; + } + + ack_this = 1; + break; + + case NGX_QUIC_FT_PADDING: + break; + + case NGX_QUIC_FT_PING: + ack_this = 1; + break; + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + ack_this = 1; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "NCID: { seq=%ui retire=%ui len=%ui}", + frame.u.ncid.seqnum, + frame.u.ncid.retire, + frame.u.ncid.len); + break; + + case NGX_QUIC_FT_CONNECTION_CLOSE: + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "CONN.CLOSE: { %s (0x%xi) type=0x%xi reason='%V'}", + ngx_quic_error_text(frame.u.close.error_code), + frame.u.close.error_code, + frame.u.close.frame_type, + &frame.u.close.reason); + + do_close = 1; + break; + + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, + "STREAM frame { 0x%xi id 0x%xi offset 0x%xi len 0x%xi bits:off=%d len=%d fin=%d }", + frame.type, + frame.u.stream.stream_id, + frame.u.stream.offset, + frame.u.stream.length, + frame.u.stream.off, + frame.u.stream.len, + frame.u.stream.fin); + + ngx_quic_hexdump0(c->log, "STREAM frame contents", + frame.u.stream.data, frame.u.stream.length); + + if (ngx_quic_handle_stream_frame(c, pkt, &frame.u.stream) + != NGX_OK) + { + return NGX_ERROR; + } + + ack_this = 1; + break; + + default: + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "unsupported frame type 0x%xd in packet", frame.type); + return NGX_ERROR; + } + } + + if (p != end) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "trailing garbage in payload: %ui bytes", end - p); + return NGX_ERROR; + } + + if (do_close) { + // TODO: handle stream close + } + + if (ack_this == 0) { + /* do not ack packets with ACKs and PADDING */ + return NGX_OK; + } + + // packet processed, ACK it now if required + // TODO: if (ack_required) ... - currently just ack each packet + + ack_frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + if (ack_frame == NULL) { + return NGX_ERROR; + } + + ack_frame->level = pkt->level; + ack_frame->type = NGX_QUIC_FT_ACK; + ack_frame->u.ack.pn = pkt->pn; + + ngx_sprintf(ack_frame->info, "ACK for PN=%d from frame handler level=%d", pkt->pn, pkt->level); + ngx_quic_queue_frame(qc, ack_frame); + + return ngx_quic_output(c); +} + + +static ngx_int_t +ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_ack_frame_t *f) +{ + /* TODO: handle ACK here */ + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_crypto_frame_t *f) +{ + int sslerr; + ssize_t n; + ngx_ssl_conn_t *ssl_conn; + + if (f->offset != 0x0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "crypto frame with non-zero offset"); + // TODO: add support for crypto frames spanning packets + return NGX_ERROR; + } + + ssl_conn = c->ssl->connection; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_quic_read_level: %d, SSL_quic_write_level: %d", + (int) SSL_quic_read_level(ssl_conn), + (int) SSL_quic_write_level(ssl_conn)); + + if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn), + f->data, f->len)) + { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "SSL_provide_quic_data() failed"); + return NGX_ERROR; + } + + n = SSL_do_handshake(ssl_conn); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); + + if (n == -1) { + sslerr = SSL_get_error(ssl_conn, n); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", + sslerr); + + if (sslerr == SSL_ERROR_SSL) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ssl cipher: %s", SSL_get_cipher(ssl_conn)); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_quic_read_level: %d, SSL_quic_write_level: %d", + (int) SSL_quic_read_level(ssl_conn), + (int) SSL_quic_write_level(ssl_conn)); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_handle_stream_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stream_frame_t *f) +{ + ngx_buf_t *b; + ngx_log_t *log; + ngx_pool_t *pool; + ngx_event_t *rev, *wev; + ngx_quic_connection_t *qc; + ngx_quic_stream_node_t *sn; + + qc = c->quic; + + sn = ngx_quic_find_stream(&qc->streams.tree, f->stream_id); + + if (sn) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "existing stream"); + b = sn->b; + + if ((size_t) (b->end - b->pos) < f->length) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "no space in stream buffer"); + return NGX_ERROR; + } + + ngx_memcpy(b->pos, f->data, f->length); + b->pos += f->length; + + // TODO: notify + + return NGX_OK; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "stream is new"); + + sn = ngx_pcalloc(c->pool, sizeof(ngx_quic_stream_node_t)); + if (sn == NULL) { + return NGX_ERROR; + } + + sn->c = ngx_get_connection(-1, c->log); // TODO: free on connection termination + if (sn->c == NULL) { + return NGX_ERROR; + } + + pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); + if (pool == NULL) { + /* XXX free connection */ + return NGX_ERROR; + } + + log = ngx_palloc(pool, sizeof(ngx_log_t)); + if (log == NULL) { + /* XXX free pool and connection */ + return NGX_ERROR; + } + + *log = *c->log; + pool->log = log; + + sn->c->log = log; + sn->c->pool = pool; + + sn->c->listening = c->listening; + sn->c->sockaddr = c->sockaddr; + sn->c->local_sockaddr = c->local_sockaddr; + + rev = sn->c->read; + wev = sn->c->write; + + rev->ready = 1; + + rev->log = c->log; + wev->log = c->log; + + sn->c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + + sn->node.key = f->stream_id; + sn->b = ngx_create_temp_buf(pool, 16 * 1024); // XXX enough for everyone + if (sn->b == NULL) { + return NGX_ERROR; + } + b = sn->b; + + ngx_memcpy(b->start, f->data, f->length); + b->last = b->start + f->length; + + ngx_rbtree_insert(&qc->streams.tree, &sn->node); + + sn->s.id = f->stream_id; + sn->s.unidirectional = (sn->s.id & 0x02) ? 1 : 0; + sn->s.parent = c; + sn->c->qs = &sn->s; + + sn->c->recv = ngx_quic_stream_recv; + sn->c->send = ngx_quic_stream_send; + sn->c->send_chain = ngx_quic_stream_send_chain; + + qc->streams.handler(sn->c); + + return NGX_OK; +} + + +static void +ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) +{ + ngx_quic_frame_t *f; + + if (qc->frames == NULL) { + qc->frames = frame; + return; + } + + for (f = qc->frames; f->next; f = f->next) { + if (f->next->level > frame->level) { + break; + } + } + + frame->next = f->next; + f->next = frame; +} + + +static ngx_int_t +ngx_quic_output(ngx_connection_t *c) +{ + size_t len; + ngx_uint_t lvl; + ngx_quic_frame_t *f, *start; + ngx_quic_connection_t *qc; + + qc = c->quic; + + if (qc->frames == NULL) { + return NGX_OK; + } + + lvl = qc->frames->level; + start = qc->frames; + f = start; + + do { + len = 0; + + do { + /* process same-level group of frames */ + + len += ngx_quic_frame_len(f);// TODO: handle overflow, max size + + f = f->next; + } while (f && f->level == lvl); + + + if (ngx_quic_frames_send(c, start, f, len) != NGX_OK) { + return NGX_ERROR; + } + + if (f == NULL) { + break; + } + + lvl = f->level; // TODO: must not decrease (ever, also between calls) + start = f; + + } while (1); + + qc->frames = NULL; + + return NGX_OK; +} + + +/* pack a group of frames [start; end) into memory p and send as single packet */ +ngx_int_t +ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, + ngx_quic_frame_t *end, size_t total) +{ + ssize_t len; + u_char *p; + ngx_str_t out; + ngx_quic_frame_t *f; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "sending frames %p...%p", start, end); + + p = ngx_pnalloc(c->pool, total); + if (p == NULL) { + return NGX_ERROR; + } + + out.data = p; + + for (f = start; f != end; f = f->next) { + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "frame: %s", f->info); + + len = ngx_quic_create_frame(p, p + total, f); + if (len == -1) { + return NGX_ERROR; + } + + p += len; + } + + out.len = p - out.data; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "packet ready: %ui bytes at level %d", + out.len, start->level); + + // IOVEC/sendmsg_chain ? + if (ngx_quic_send_packet(c, c->quic, start->level, &out) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc, + enum ssl_encryption_level_t level, ngx_str_t *payload) +{ + ngx_str_t res; + ngx_quic_header_t pkt; + + pkt.log = c->log; + + static ngx_str_t initial_token = ngx_null_string; + + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + ngx_quic_hexdump0(c->log, "payload", payload->data, payload->len); + + pkt.level = level; + pkt.dcid = qc->dcid; + pkt.scid = qc->scid; + + if (level == ssl_encryption_initial) { + pkt.number = &qc->initial_pn; + pkt.flags = NGX_QUIC_PKT_INITIAL; + pkt.secret = &qc->secrets.server.in; + pkt.token = initial_token; + + } else if (level == ssl_encryption_handshake) { + pkt.number = &qc->handshake_pn; + pkt.flags = NGX_QUIC_PKT_HANDSHAKE; + pkt.secret = &qc->secrets.server.hs; + + } else { + pkt.number = &qc->appdata_pn; + pkt.secret = &qc->secrets.server.ad; + } + + if (ngx_quic_encrypt(c->pool, c->ssl->connection, &pkt, payload, &res) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_quic_hexdump0(c->log, "packet to send", res.data, res.len); + + c->send(c, res.data, res.len); // TODO: err handling + + (*pkt.number)++; + + return NGX_OK; +} + + +ngx_connection_t * +ngx_quic_create_uni_stream(ngx_connection_t *c) +{ + /* XXX */ + return NULL; +} + + +static void +ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) +{ + ngx_rbtree_node_t **p; + ngx_quic_stream_node_t *qn, *qnt; + + for ( ;; ) { + + if (node->key < temp->key) { + + p = &temp->left; + + } else if (node->key > temp->key) { + + p = &temp->right; + + } else { /* node->key == temp->key */ + + qn = (ngx_quic_stream_node_t *) &node->color; + qnt = (ngx_quic_stream_node_t *) &temp->color; + + if (qn->c < qnt->c) { + p = &temp->left; + } else { + p = &temp->right; + } + } + + if (*p == sentinel) { + break; + } + + temp = *p; + } + + *p = node; + node->parent = temp; + node->left = sentinel; + node->right = sentinel; + ngx_rbt_red(node); +} + + +static ngx_quic_stream_node_t * +ngx_quic_find_stream(ngx_rbtree_t *rbtree, ngx_uint_t key) +{ + ngx_rbtree_node_t *node, *sentinel; + + node = rbtree->root; + sentinel = rbtree->sentinel; + + while (node != sentinel) { + + if (key == node->key) { + return (ngx_quic_stream_node_t *) node; + } + + node = (key < node->key) ? node->left : node->right; + } + + return NULL; +} + + +static ssize_t +ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) +{ + ssize_t len; + ngx_buf_t *b; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + ngx_quic_stream_node_t *sn; + + qs = c->qs; + qc = qs->parent->quic; + + // XXX: get direct pointer from stream structure? + sn = ngx_quic_find_stream(&qc->streams.tree, qs->id); + + if (sn == NULL) { + return NGX_ERROR; + } + + // XXX: how to return EOF? + + b = sn->b; + + if (b->last - b->pos == 0) { + c->read->ready = 0; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic recv() not ready"); + return NGX_AGAIN; // ? + } + + len = ngx_min(b->last - b->pos, (ssize_t) size); + + ngx_memcpy(buf, b->pos, len); + + b->pos += len; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic recv: %z of %uz", len, size); + + return len; +} + + +static ssize_t +ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) +{ + u_char *p; + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + ngx_quic_stream_node_t *sn; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send: %uz", size); + + qs = c->qs; + pc = qs->parent; + qc = pc->quic; + + // XXX: get direct pointer from stream structure? + sn = ngx_quic_find_stream(&qc->streams.tree, qs->id); + + if (sn == NULL) { + return NGX_ERROR; + } + + frame = ngx_pcalloc(pc->pool, sizeof(ngx_quic_frame_t)); + if (frame == NULL) { + return 0; + } + + p = ngx_pnalloc(pc->pool, size); + if (p == NULL) { + return 0; + } + + ngx_memcpy(p, buf, size); + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STREAM6; /* OFF=1 LEN=1 FIN=0 */ + frame->u.stream.off = 1; + frame->u.stream.len = 1; + frame->u.stream.fin = 0; + + frame->u.stream.type = frame->type; + frame->u.stream.stream_id = qs->id; + frame->u.stream.offset = c->sent; + frame->u.stream.length = size; + frame->u.stream.data = p; + + c->sent += size; + + ngx_sprintf(frame->info, "stream %xi len=%ui level=%d", + qs->id, size, frame->level); + + ngx_quic_queue_frame(qc, frame); + + return size; +} + + +static ngx_chain_t * +ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, + off_t limit) +{ + size_t len; + ssize_t n; + ngx_buf_t *b; + + for ( /* void */; in; in = in->next) { + b = in->buf; + + if (!ngx_buf_in_memory(b)) { + continue; + } + + if (ngx_buf_size(b) == 0) { + continue; + } + + len = b->last - b->pos; + + n = ngx_quic_stream_send(c, b->pos, len); + + if (n == NGX_ERROR) { + return NGX_CHAIN_ERROR; + } + + if (n == NGX_AGAIN) { + return in; + } + + if (n != (ssize_t) len) { + b->pos += n; + return in; + } + } + + return NULL; +}