changeset 8224:ae35ccba7aa6 quic

Extracted transport part of the code into separate file. All code dealing with serializing/deserializing is moved int srv/event/ngx_event_quic_transport.c/h file. All macros for dealing with data are internal to source file. The header file exposes frame types and error codes. The exported functions are currently packet header parsers and writers and frames parser/writer. The ngx_quic_header_t structure is updated with 'log' member. This avoids passing extra argument to parsing functions that need to report errors.
author Vladimir Homutov <vl@nginx.com>
date Wed, 18 Mar 2020 12:58:27 +0300
parents 61f9b873e2e7
children 714a19dba6af
files auto/modules src/core/ngx_core.h src/event/ngx_event_quic.c src/event/ngx_event_quic.h src/event/ngx_event_quic_protection.c src/event/ngx_event_quic_protection.h src/event/ngx_event_quic_transport.c src/event/ngx_event_quic_transport.h
diffstat 8 files changed, 813 insertions(+), 747 deletions(-) [+]
line wrap: on
line diff
--- a/auto/modules
+++ b/auto/modules
@@ -1265,10 +1265,12 @@ if [ $USE_OPENSSL = YES ]; then
     ngx_module_incs=
     ngx_module_deps="src/event/ngx_event_openssl.h \
                      src/event/ngx_event_quic.h \
+                     src/event/ngx_event_quic_transport.h \
                      src/event/ngx_event_quic_protection.h"
-    ngx_module_srcs="src/event/ngx_event_openssl.c
-                     src/event/ngx_event_openssl_stapling.c
-                     src/event/ngx_event_quic.c
+    ngx_module_srcs="src/event/ngx_event_openssl.c \
+                     src/event/ngx_event_openssl_stapling.c \
+                     src/event/ngx_event_quic.c \
+                     src/event/ngx_event_quic_transport.c \
                      src/event/ngx_event_quic_protection.c"
     ngx_module_libs=
     ngx_module_link=YES
--- a/src/core/ngx_core.h
+++ b/src/core/ngx_core.h
@@ -85,6 +85,7 @@ typedef void (*ngx_connection_handler_pt
 #if (NGX_OPENSSL)
 #include <ngx_event_openssl.h>
 #include <ngx_event_quic.h>
+#include <ngx_event_quic_transport.h>
 #include <ngx_event_quic_protection.h>
 #endif
 #include <ngx_process_cycle.h>
--- a/src/event/ngx_event_quic.c
+++ b/src/event/ngx_event_quic.c
@@ -9,82 +9,6 @@
 #include <ngx_event.h>
 
 
-/* 12.4.  Frames and Frame Types */
-#define NGX_QUIC_FT_PADDING                0x00
-#define NGX_QUIC_FT_PING                   0x01
-#define NGX_QUIC_FT_ACK                    0x02
-#define NGX_QUIC_FT_ACK_ECN                0x03
-#define NGX_QUIC_FT_RESET_STREAM           0x04
-#define NGX_QUIC_FT_STOP_SENDING           0x05
-#define NGX_QUIC_FT_CRYPTO                 0x06
-#define NGX_QUIC_FT_NEW_TOKEN              0x07
-#define NGX_QUIC_FT_STREAM0                0x08
-#define NGX_QUIC_FT_STREAM1                0x09
-#define NGX_QUIC_FT_STREAM2                0x0A
-#define NGX_QUIC_FT_STREAM3                0x0B
-#define NGX_QUIC_FT_STREAM4                0x0C
-#define NGX_QUIC_FT_STREAM5                0x0D
-#define NGX_QUIC_FT_STREAM6                0x0E
-#define NGX_QUIC_FT_STREAM7                0x0F
-#define NGX_QUIC_FT_MAX_DATA               0x10
-#define NGX_QUIC_FT_MAX_STREAM_DATA        0x11
-#define NGX_QUIC_FT_MAX_STREAMS            0x12
-#define NGX_QUIC_FT_MAX_STREAMS2           0x13 // XXX
-#define NGX_QUIC_FT_DATA_BLOCKED           0x14
-#define NGX_QUIC_FT_STREAM_DATA_BLOCKED    0x15
-#define NGX_QUIC_FT_STREAMS_BLOCKED        0x16
-#define NGX_QUIC_FT_STREAMS_BLOCKED2       0x17 // XXX
-#define NGX_QUIC_FT_NEW_CONNECTION_ID      0x18
-#define NGX_QUIC_FT_RETIRE_CONNECTION_ID   0x19
-#define NGX_QUIC_FT_PATH_CHALLENGE         0x1a
-#define NGX_QUIC_FT_PATH_RESPONSE          0x1b
-#define NGX_QUIC_FT_CONNECTION_CLOSE       0x1c
-#define NGX_QUIC_FT_CONNECTION_CLOSE2      0x1d // XXX
-#define NGX_QUIC_FT_HANDSHAKE_DONE         0x1e
-
-#define ngx_quic_stream_bit_off(val)  (((val) & 0x04) ? 1 : 0)
-#define ngx_quic_stream_bit_len(val)  (((val) & 0x02) ? 1 : 0)
-#define ngx_quic_stream_bit_fin(val)  (((val) & 0x01) ? 1 : 0)
-
-
-#define NGX_QUIC_ERR_NO_ERROR                   0x0
-#define NGX_QUIC_ERR_INTERNAL_ERROR             0x1
-#define NGX_QUIC_ERR_SERVER_BUSY                0x2
-#define NGX_QUIC_ERR_FLOW_CONTROL_ERROR         0x3
-#define NGX_QUIC_ERR_STREAM_LIMIT_ERROR         0x4
-#define NGX_QUIC_ERR_STREAM_STATE_ERROR         0x5
-#define NGX_QUIC_ERR_FINAL_SIZE_ERROR           0x6
-#define NGX_QUIC_ERR_FRAME_ENCODING_ERROR       0x7
-#define NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR  0x8
-#define NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR  0x9
-#define NGX_QUIC_ERR_PROTOCOL_VIOLATION         0xA
-#define NGX_QUIC_ERR_INVALID_TOKEN              0xB
-/* 0xC is not defined */
-#define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED     0xD
-#define NGX_QUIC_ERR_CRYPTO_ERROR               0x10
-
-#define NGX_QUIC_ERR_LAST  NGX_QUIC_ERR_CRYPTO_ERROR
-
-/* literal errors indexed by corresponding value */
-static char *ngx_quic_errors[] = {
-    "NO_ERROR",
-    "INTERNAL_ERROR",
-    "SERVER_BUSY",
-    "FLOW_CONTROL_ERROR",
-    "STREAM_LIMIT_ERROR",
-    "STREAM_STATE_ERROR",
-    "FINAL_SIZE_ERROR",
-    "FRAME_ENCODING_ERROR",
-    "TRANSPORT_PARAMETER_ERROR",
-    "CONNECTION_ID_LIMIT_ERROR",
-    "PROTOCOL_VIOLATION",
-    "INVALID_TOKEN",
-    "",
-    "CRYPTO_BUFFER_EXCEEDED",
-    "CRYPTO_ERROR",
-};
-
-
 /* TODO: real states, these are stubs */
 typedef enum  {
     NGX_QUIC_ST_INITIAL,
@@ -93,69 +17,6 @@ typedef enum  {
 } ngx_quic_state_t;
 
 
-typedef struct ngx_quic_frame_s  ngx_quic_frame_t;
-
-typedef struct {
-    ngx_uint_t                  pn;
-
-    // input
-    uint64_t                    largest;
-    uint64_t                    delay;
-    uint64_t                    range_count;
-    uint64_t                    first_range;
-    uint64_t                    ranges[20];
-    /* ecn counts */
-} ngx_quic_ack_frame_t;
-
-typedef struct {
-    size_t                      offset;
-    size_t                      len;
-    u_char                     *data;
-} ngx_quic_crypto_frame_t;
-
-
-typedef struct {
-    uint64_t                     seqnum;
-    uint64_t                     retire;
-    uint64_t                     len;
-    u_char                       cid[20];
-    u_char                       srt[16];
-} ngx_quic_ncid_t;
-
-
-typedef struct {
-    uint8_t                      type;
-    uint64_t                     stream_id;
-    uint64_t                     offset;
-    uint64_t                     length;
-    u_char                      *data;
-} ngx_quic_stream_frame_t;
-
-
-typedef struct {
-    uint64_t                     error_code;
-    uint64_t                     frame_type;
-    ngx_str_t                    reason;
-} ngx_quic_close_frame_t;
-
-
-struct ngx_quic_frame_s {
-    ngx_uint_t                  type;
-    ngx_quic_level_t            level;
-    ngx_quic_frame_t           *next;
-    union {
-        ngx_quic_crypto_frame_t crypto;
-        ngx_quic_ack_frame_t    ack;
-        ngx_quic_ncid_t         ncid;
-        ngx_quic_stream_frame_t stream;
-        ngx_quic_close_frame_t  close;
-        // more frames
-    } u;
-
-    u_char                      info[128]; // for debug purposes
-};
-
-
 struct ngx_quic_connection_s {
 
     ngx_quic_state_t   state;
@@ -229,16 +90,6 @@ static int ngx_quic_flush_flight(ngx_ssl
 static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn,
     enum ssl_encryption_level_t level, uint8_t alert);
 
-static ngx_int_t ngx_quic_process_long_header(ngx_connection_t *c,
-    ngx_quic_header_t *pkt);
-static ngx_int_t ngx_quic_process_short_header(ngx_connection_t *c,
-    ngx_quic_header_t *pkt);
-static ngx_int_t ngx_quic_process_initial_header(ngx_connection_t *c,
-    ngx_quic_header_t *pkt);
-static ngx_int_t ngx_quic_process_handshake_header(ngx_connection_t *c,
-    ngx_quic_header_t *pkt);
-
-static uint64_t ngx_quic_parse_int(u_char **pos);
 
 static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf,
     size_t size);
@@ -282,6 +133,7 @@ ngx_quic_run(ngx_connection_t *c, ngx_ss
 
     b = c->buffer;
 
+    pkt.log = c->log;
     pkt.raw = b;
     pkt.data = b->start;
     pkt.len = b->last - b->start;
@@ -407,6 +259,7 @@ ngx_quic_input(ngx_connection_t *c, ngx_
         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 */
@@ -448,11 +301,13 @@ ngx_quic_input(ngx_connection_t *c, ngx_
 
 static ngx_int_t
 ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc,
-    ngx_quic_level_t level, ngx_str_t *payload)
+    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));
@@ -494,139 +349,12 @@ ngx_quic_send_packet(ngx_connection_t *c
 }
 
 
-static size_t
-ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack)
-{
-    size_t  len;
-
-    /* minimal ACK packet */
-
-    if (p == NULL) {
-        len = ngx_quic_varint_len(NGX_QUIC_FT_ACK);
-        len += ngx_quic_varint_len(ack->pn);
-        len += ngx_quic_varint_len(0);
-        len += ngx_quic_varint_len(0);
-        len += ngx_quic_varint_len(ack->pn);
-
-        return len;
-    }
-
-    ngx_quic_build_int(&p, NGX_QUIC_FT_ACK);
-    ngx_quic_build_int(&p, ack->pn);
-    ngx_quic_build_int(&p, 0);
-    ngx_quic_build_int(&p, 0);
-    ngx_quic_build_int(&p, ack->pn);
-
-    return 5;
-}
-
-
-static size_t
-ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto)
-{
-    size_t   len;
-    u_char  *start;
-
-    if (p == NULL) {
-        len = ngx_quic_varint_len(NGX_QUIC_FT_CRYPTO);
-        len += ngx_quic_varint_len(crypto->offset);
-        len += ngx_quic_varint_len(crypto->len);
-        len += crypto->len;
-
-        return len;
-    }
-
-    start = p;
-
-    ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO);
-    ngx_quic_build_int(&p, crypto->offset);
-    ngx_quic_build_int(&p, crypto->len);
-    p = ngx_cpymem(p, crypto->data, crypto->len);
-
-    return p - start;
-}
-
-
-static size_t
-ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf)
-{
-    size_t   len;
-    u_char  *start;
-
-    if (!ngx_quic_stream_bit_len(sf->type)) {
-#if 0
-        ngx_log_error(NGX_LOG_INFO, c->log, 0,
-                      "attempt to generate a stream frame without length");
-#endif
-        // XXX: handle error in caller
-        return NGX_ERROR;
-    }
-
-    if (p == NULL) {
-        len = ngx_quic_varint_len(sf->type);
-
-        if (ngx_quic_stream_bit_off(sf->type)) {
-            len += ngx_quic_varint_len(sf->offset);
-        }
-
-        len += ngx_quic_varint_len(sf->stream_id);
-
-        /* length is always present in generated frames */
-        len += ngx_quic_varint_len(sf->length);
-
-        len += sf->length;
-
-        return len;
-    }
-
-    start = p;
-
-    ngx_quic_build_int(&p, sf->type);
-    ngx_quic_build_int(&p, sf->stream_id);
-
-    if (ngx_quic_stream_bit_off(sf->type)) {
-        ngx_quic_build_int(&p, sf->offset);
-    }
-
-    /* length is always present in generated frames */
-    ngx_quic_build_int(&p, sf->length);
-
-    p = ngx_cpymem(p, sf->data, sf->length);
-
-    return p - start;
-}
-
-
-size_t
-ngx_quic_frame_len(ngx_quic_frame_t *frame)
-{
-     switch (frame->type) {
-        case NGX_QUIC_FT_ACK:
-            return ngx_quic_create_ack(NULL, &frame->u.ack);
-        case NGX_QUIC_FT_CRYPTO:
-            return ngx_quic_create_crypto(NULL, &frame->u.crypto);
-
-        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:
-            return ngx_quic_create_stream(NULL, &frame->u.stream);
-        default:
-            /* BUG: unsupported frame type generated */
-            return 0;
-     }
-}
-
-
 /* 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;
@@ -645,30 +373,12 @@ ngx_quic_frames_send(ngx_connection_t *c
 
         ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "frame: %s", f->info);
 
-        switch (f->type) {
-        case NGX_QUIC_FT_ACK:
-            p += ngx_quic_create_ack(p, &f->u.ack);
-            break;
-
-        case NGX_QUIC_FT_CRYPTO:
-            p += ngx_quic_create_crypto(p, &f->u.crypto);
-            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:
-            p += ngx_quic_create_stream(p, &f->u.stream);
-            break;
-
-        default:
-            /* BUG: unsupported frame type generated */
+        len = ngx_quic_create_frame(p, p + total, f);
+        if (len == -1) {
             return NGX_ERROR;
         }
+
+        p += len;
     }
 
     out.len = p - out.data;
@@ -892,279 +602,6 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_
 }
 
 
-static ngx_int_t
-ngx_quic_process_short_header(ngx_connection_t *c, ngx_quic_header_t *pkt)
-{
-    u_char  *p;
-
-    p = pkt->data;
-
-    ngx_quic_hexdump0(c->log, "short input", pkt->data, pkt->len);
-
-    if ((p[0] & NGX_QUIC_PKT_LONG)) {
-        ngx_log_error(NGX_LOG_INFO, c->log, 0, "not a short packet");
-        return NGX_ERROR;
-    }
-
-    pkt->flags = *p++;
-
-    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic flags:%xi", pkt->flags);
-
-    if (ngx_memcmp(p, c->quic->dcid.data, c->quic->dcid.len) != 0) {
-        ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcid");
-        return NGX_ERROR;
-    }
-
-    pkt->dcid.len = c->quic->dcid.len;
-    pkt->dcid.data = p;
-    p += pkt->dcid.len;
-
-    pkt->raw->pos = p;
-
-    return NGX_OK;
-}
-
-
-static ngx_int_t
-ngx_quic_process_long_header(ngx_connection_t *c, ngx_quic_header_t *pkt)
-{
-    u_char  *p;
-
-    p = pkt->data;
-
-    ngx_quic_hexdump0(c->log, "long input", pkt->data, pkt->len);
-
-    if (!(p[0] & NGX_QUIC_PKT_LONG)) {
-        ngx_log_error(NGX_LOG_INFO, c->log, 0, "not a long packet");
-        return NGX_ERROR;
-    }
-
-    pkt->flags = *p++;
-
-    pkt->version = ngx_quic_parse_uint32(p);
-    p += sizeof(uint32_t);
-
-    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic flags:%xi version:%xD", pkt->flags, pkt->version);
-
-    if (pkt->version != quic_version) {
-        ngx_log_error(NGX_LOG_INFO, c->log, 0, "unsupported quic version");
-        return NGX_ERROR;
-    }
-
-    pkt->dcid.len = *p++;
-    pkt->dcid.data = p;
-    p += pkt->dcid.len;
-
-    pkt->scid.len = *p++;
-    pkt->scid.data = p;
-    p += pkt->scid.len;
-
-    pkt->raw->pos = p;
-
-    return NGX_OK;
-}
-
-
-static ngx_int_t
-ngx_quic_process_initial_header(ngx_connection_t *c, ngx_quic_header_t *pkt)
-{
-    u_char     *p;
-    ngx_int_t   plen;
-
-    p = pkt->raw->pos;
-
-    pkt->token.len = ngx_quic_parse_int(&p);
-    pkt->token.data = p;
-
-    p += pkt->token.len;
-
-    plen = ngx_quic_parse_int(&p);
-
-    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic packet length: %d", plen);
-
-    if (plen > pkt->data + pkt->len - p) {
-        ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated initial packet");
-        return NGX_ERROR;
-    }
-
-    pkt->raw->pos = p;
-    pkt->len = plen;
-
-    ngx_quic_hexdump0(c->log, "DCID", pkt->dcid.data, pkt->dcid.len);
-    ngx_quic_hexdump0(c->log, "SCID", pkt->scid.data, pkt->scid.len);
-    ngx_quic_hexdump0(c->log, "token", pkt->token.data, pkt->token.len);
-
-    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic packet length: %d", plen);
-
-    return NGX_OK;
-}
-
-static ngx_int_t
-ngx_quic_process_handshake_header(ngx_connection_t *c, ngx_quic_header_t *pkt)
-{
-    u_char     *p;
-    ngx_int_t   plen;
-
-    p = pkt->raw->pos;
-
-    plen = ngx_quic_parse_int(&p);
-
-    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic packet length: %d", plen);
-
-    if (plen > pkt->data + pkt->len - p) {
-        ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated handshake packet");
-        return NGX_ERROR;
-    }
-
-    pkt->raw->pos = p;
-    pkt->len = plen;
-
-    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic packet length: %d", plen);
-
-    return NGX_OK;
-}
-
-
-ssize_t
-ngx_quic_read_frame(ngx_connection_t *c, u_char *start, u_char *end,
-    ngx_quic_frame_t *frame)
-{
-    u_char *p;
-
-    size_t npad;
-
-    p = start;
-
-    frame->type = *p++;  // TODO: check overflow (p < end)
-
-    switch (frame->type) {
-
-    case NGX_QUIC_FT_CRYPTO:
-        frame->u.crypto.offset = *p++;
-        frame->u.crypto.len = ngx_quic_parse_int(&p);
-        frame->u.crypto.data = p;
-        p += frame->u.crypto.len;
-
-        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);
-        break;
-
-    case NGX_QUIC_FT_PADDING:
-        npad = 0;
-        while (p < end && *p == NGX_QUIC_FT_PADDING) { // XXX
-            p++; npad++;
-        }
-
-        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                       "PADDING frame length %uL", npad);
-
-        break;
-
-    case NGX_QUIC_FT_ACK:
-    case NGX_QUIC_FT_ACK_ECN:
-
-        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "ACK frame");
-
-        frame->u.ack.largest = ngx_quic_parse_int(&p);
-        frame->u.ack.delay = ngx_quic_parse_int(&p);
-        frame->u.ack.range_count =ngx_quic_parse_int(&p);
-        frame->u.ack.first_range =ngx_quic_parse_int(&p);
-
-        if (frame->u.ack.range_count) {
-            frame->u.ack.ranges[0] = ngx_quic_parse_int(&p);
-        }
-
-        if (frame->type ==NGX_QUIC_FT_ACK_ECN) {
-            return NGX_ERROR;
-        }
-
-        break;
-
-    case NGX_QUIC_FT_PING:
-        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "PING frame");
-        break;
-
-    case NGX_QUIC_FT_NEW_CONNECTION_ID:
-        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "NCID frame");
-
-        frame->u.ncid.seqnum = ngx_quic_parse_int(&p);
-        frame->u.ncid.retire = ngx_quic_parse_int(&p);
-        frame->u.ncid.len = *p++;
-        ngx_memcpy(frame->u.ncid.cid, p, frame->u.ncid.len);
-        p += frame->u.ncid.len;
-
-        ngx_memcpy(frame->u.ncid.srt, p, 16);
-        p += 16;
-
-        break;
-
-    case NGX_QUIC_FT_CONNECTION_CLOSE:
-        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "connection close frame");
-
-        frame->u.close.error_code = ngx_quic_parse_int(&p);
-        frame->u.close.frame_type = ngx_quic_parse_int(&p); // not in 0x1d CC
-        frame->u.close.reason.len = ngx_quic_parse_int(&p);
-        frame->u.close.reason.data = p;
-        p += frame->u.close.reason.len;
-
-        if (frame->u.close.error_code > NGX_QUIC_ERR_LAST) {
-            frame->u.close.error_code = NGX_QUIC_ERR_LAST;
-        }
-        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_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                       "STREAM frame, type: 0x%xi", frame->type);
-
-        frame->u.stream.type = frame->type;
-
-        frame->u.stream.stream_id = ngx_quic_parse_int(&p);
-        if (frame->type & 0x04) {
-            frame->u.stream.offset = ngx_quic_parse_int(&p);
-        } else {
-            frame->u.stream.offset = 0;
-        }
-
-        if (frame->type & 0x02) {
-            frame->u.stream.length = ngx_quic_parse_int(&p);
-        } else {
-            frame->u.stream.length = end - p; /* up to packet end */
-        }
-
-        frame->u.stream.data = p;
-
-        p += frame->u.stream.length;
-
-        break;
-
-    default:
-        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                       "unknown frame type %xi", frame->type);
-        return NGX_ERROR;
-    }
-
-    return p - start;
-}
-
 
 static ngx_int_t
 ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt,
@@ -1349,6 +786,9 @@ ngx_quic_stream_send(ngx_connection_t *c
 
     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;
@@ -1433,8 +873,11 @@ ngx_quic_payload_handler(ngx_connection_
 
     while (p < end) {
 
-        len = ngx_quic_read_frame(c, p, end, &frame);
+        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;
         }
 
@@ -1456,6 +899,13 @@ ngx_quic_payload_handler(ngx_connection_
             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,
@@ -1491,7 +941,7 @@ ngx_quic_payload_handler(ngx_connection_
         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_errors[frame.u.close.error_code],
+                           ngx_quic_error_text(frame.u.close.error_code),
                            frame.u.close.error_code,
                            frame.u.close.frame_type,
                            &frame.u.close.reason);
@@ -1516,10 +966,9 @@ ngx_quic_payload_handler(ngx_connection_
                            frame.u.stream.stream_id,
                            frame.u.stream.offset,
                            frame.u.stream.length,
-                           ngx_quic_stream_bit_off(frame.u.stream.type),
-                           ngx_quic_stream_bit_len(frame.u.stream.type),
-                           ngx_quic_stream_bit_fin(frame.u.stream.type));
-
+                           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) {
@@ -1727,7 +1176,7 @@ ngx_quic_new_connection(ngx_connection_t
         return NGX_ERROR;
     }
 
-    if (ngx_quic_process_long_header(c, pkt) != NGX_OK) {
+    if (ngx_quic_parse_long_header(pkt) != NGX_OK) {
         return NGX_ERROR;
     }
 
@@ -1737,7 +1186,7 @@ ngx_quic_new_connection(ngx_connection_t
         return NGX_ERROR;
     }
 
-    if (ngx_quic_process_initial_header(c, pkt) != NGX_OK) {
+    if (ngx_quic_parse_initial_header(pkt) != NGX_OK) {
         return NGX_ERROR;
     }
 
@@ -1804,11 +1253,11 @@ ngx_quic_initial_input(ngx_connection_t 
     qc = c->quic;
     ssl_conn = c->ssl->connection;
 
-    if (ngx_quic_process_long_header(c, pkt) != NGX_OK) {
+    if (ngx_quic_parse_long_header(pkt) != NGX_OK) {
         return NGX_ERROR;
     }
 
-    if (ngx_quic_process_initial_header(c, pkt) != NGX_OK) {
+    if (ngx_quic_parse_initial_header(pkt) != NGX_OK) {
         return NGX_ERROR;
     }
 
@@ -1833,7 +1282,7 @@ ngx_quic_handshake_input(ngx_connection_
     ssl_conn = c->ssl->connection;
 
     /* extract cleartext data into pkt */
-    if (ngx_quic_process_long_header(c, pkt) != NGX_OK) {
+    if (ngx_quic_parse_long_header(pkt) != NGX_OK) {
         return NGX_ERROR;
     }
 
@@ -1863,7 +1312,7 @@ ngx_quic_handshake_input(ngx_connection_
         return NGX_ERROR;
     }
 
-    if (ngx_quic_process_handshake_header(c, pkt) != NGX_OK) {
+    if (ngx_quic_parse_handshake_header(pkt) != NGX_OK) {
         return NGX_ERROR;
     }
 
@@ -1891,7 +1340,7 @@ ngx_quic_app_input(ngx_connection_t *c, 
         return NGX_DECLINED;
     }
 
-    if (ngx_quic_process_short_header(c, pkt) != NGX_OK) {
+    if (ngx_quic_parse_short_header(pkt, &qc->dcid) != NGX_OK) {
         return NGX_ERROR;
     }
 
@@ -1906,52 +1355,3 @@ ngx_quic_app_input(ngx_connection_t *c, 
 }
 
 
-uint64_t
-ngx_quic_parse_int(u_char **pos)
-{
-    u_char      *p;
-    uint64_t     value;
-    ngx_uint_t   len;
-
-    p = *pos;
-    len = 1 << ((*p & 0xc0) >> 6);
-    value = *p++ & 0x3f;
-
-    while (--len) {
-        value = (value << 8) + *p++;
-    }
-
-    *pos = p;
-    return value;
-}
-
-
-void
-ngx_quic_build_int(u_char **pos, uint64_t value)
-{
-    u_char      *p;
-    ngx_uint_t   len;//, len2;
-
-    p = *pos;
-    len = 0;
-
-    while (value >> ((1 << len) * 8 - 2)) {
-        len++;
-    }
-
-    *p = len << 6;
-
-//    len2 =
-    len = (1 << len);
-    len--;
-    *p |= value >> (len * 8);
-    p++;
-
-    while (len) {
-        *p++ = value >> ((len-- - 1) * 8);
-    }
-
-    *pos = p;
-//    return len2;
-}
-
--- a/src/event/ngx_event_quic.h
+++ b/src/event/ngx_event_quic.h
@@ -11,57 +11,8 @@
 #include <ngx_event_openssl.h>
 
 
-#define quic_version                       0xff000018  /* draft-24 (ngtcp2) */
-//#define quic_version                       0xff00001b  /* draft-27 (FFN 76) */
-
-/* 17.2.  Long Header Packets */
-
-#define NGX_QUIC_PKT_LONG                  0x80
-
-#define NGX_QUIC_PKT_INITIAL               0xc0
-#define NGX_QUIC_PKT_HANDSHAKE             0xe0
-
-
-#if (NGX_HAVE_NONALIGNED)
-
-#define ngx_quic_parse_uint16(p)  ntohs(*(uint16_t *) (p))
-#define ngx_quic_parse_uint32(p)  ntohl(*(uint32_t *) (p))
-
-#define ngx_quic_write_uint16  ngx_quic_write_uint16_aligned
-#define ngx_quic_write_uint32  ngx_quic_write_uint32_aligned
-
-#else
-
-#define ngx_quic_parse_uint16(p)  ((p)[0] << 8 | (p)[1])
-#define ngx_quic_parse_uint32(p)                                              \
-    ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3])
-
-#define ngx_quic_write_uint16(p, s)                                           \
-    ((p)[0] = (u_char) ((s) >> 8),                                            \
-     (p)[1] = (u_char)  (s),                                                  \
-     (p) + sizeof(uint16_t))
-
-#define ngx_quic_write_uint32(p, s)                                           \
-    ((p)[0] = (u_char) ((s) >> 24),                                           \
-     (p)[1] = (u_char) ((s) >> 16),                                           \
-     (p)[2] = (u_char) ((s) >> 8),                                            \
-     (p)[3] = (u_char)  (s),                                                  \
-     (p) + sizeof(uint32_t))
-
-#endif
-
-
-#define ngx_quic_write_uint16_aligned(p, s)                                   \
-    (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t))
-
-#define ngx_quic_write_uint32_aligned(p, s)                                   \
-    (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t))
-
-#define ngx_quic_varint_len(value)                                            \
-     ((value) <= 63 ? 1                                                       \
-     : ((uint32_t) value) <= 16383 ? 2                                        \
-     : ((uint64_t) value) <= 1073741823 ?  4                                  \
-     : 8)
+#define quic_version        0xff000018  /* draft-24 (ngtcp2) */
+//#define quic_version      0xff00001b  /* draft-27 (FFN 76) */
 
 
 struct ngx_quic_stream_s {
@@ -71,35 +22,6 @@ struct ngx_quic_stream_s {
     void               *data;
 };
 
-typedef struct ngx_quic_secret_s ngx_quic_secret_t;
-typedef enum ssl_encryption_level_t  ngx_quic_level_t;
-
-typedef struct {
-    ngx_quic_secret_t  *secret;
-    ngx_uint_t          type;
-    ngx_uint_t          *number;
-    ngx_uint_t          flags;
-    uint32_t            version;
-    ngx_str_t           token;
-    ngx_quic_level_t    level;
-
-    /* filled in by parser */
-    ngx_buf_t          *raw;        /* udp datagram from wire */
-
-    u_char             *data;       /* quic packet */
-    size_t              len;
-
-    /* cleartext fields */
-    ngx_str_t           dcid;
-    ngx_str_t           scid;
-
-    uint64_t            pn;
-
-    ngx_str_t           payload;  /* decrypted payload */
-
-} ngx_quic_header_t;
-
-void ngx_quic_build_int(u_char **pos, uint64_t value);
 
 void ngx_quic_init_ssl_methods(SSL_CTX* ctx);
 
--- a/src/event/ngx_event_quic_protection.c
+++ b/src/event/ngx_event_quic_protection.c
@@ -702,30 +702,7 @@ ngx_quic_create_long_packet(ngx_pool_t *
         return NGX_ERROR;
     }
 
-    p = ad.data;
-
-    *p++ = pkt->flags;
-
-    p = ngx_quic_write_uint32(p, quic_version);
-
-    *p++ = pkt->scid.len;
-    p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len);
-
-    *p++ = pkt->dcid.len;
-    p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len);
-
-    if (pkt->level == ssl_encryption_initial) {
-        ngx_quic_build_int(&p, pkt->token.len);
-    }
-
-    ngx_quic_build_int(&p, out.len + 1); // length (inc. pnl)
-    pnp = p;
-
-    pn = *pkt->number;
-
-    *p++ = pn;
-
-    ad.len = p - ad.data;
+    ad.len = ngx_quic_create_long_header(pkt, &ad, out.len, &pnp);
 
     ngx_quic_hexdump0(log, "ad", ad.data, ad.len);
 
@@ -734,9 +711,8 @@ ngx_quic_create_long_packet(ngx_pool_t *
     }
 
     nonce = ngx_pstrdup(pool, &pkt->secret->iv);
-    if (pkt->level == ssl_encryption_handshake) {
-        nonce[11] ^= pn;
-    }
+    pn = *pkt->number;
+    nonce[11] ^= pn;
 
     ngx_quic_hexdump0(log, "server_iv", pkt->secret->iv.data, 12);
     ngx_quic_hexdump0(log, "nonce", nonce, 12);
--- a/src/event/ngx_event_quic_protection.h
+++ b/src/event/ngx_event_quic_protection.h
@@ -8,12 +8,12 @@
 #define _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_
 
 
-struct ngx_quic_secret_s {
+typedef struct ngx_quic_secret_s {
     ngx_str_t                 secret;
     ngx_str_t                 key;
     ngx_str_t                 iv;
     ngx_str_t                 hp;
-};
+} ngx_quic_secret_t;
 
 
 typedef struct {
new file mode 100644
--- /dev/null
+++ b/src/event/ngx_event_quic_transport.c
@@ -0,0 +1,588 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if (NGX_HAVE_NONALIGNED)
+
+#define ngx_quic_parse_uint16(p)  ntohs(*(uint16_t *) (p))
+#define ngx_quic_parse_uint32(p)  ntohl(*(uint32_t *) (p))
+
+#define ngx_quic_write_uint16  ngx_quic_write_uint16_aligned
+#define ngx_quic_write_uint32  ngx_quic_write_uint32_aligned
+
+#else
+
+#define ngx_quic_parse_uint16(p)  ((p)[0] << 8 | (p)[1])
+#define ngx_quic_parse_uint32(p)                                              \
+    ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3])
+
+#define ngx_quic_write_uint16(p, s)                                           \
+    ((p)[0] = (u_char) ((s) >> 8),                                            \
+     (p)[1] = (u_char)  (s),                                                  \
+     (p) + sizeof(uint16_t))
+
+#define ngx_quic_write_uint32(p, s)                                           \
+    ((p)[0] = (u_char) ((s) >> 24),                                           \
+     (p)[1] = (u_char) ((s) >> 16),                                           \
+     (p)[2] = (u_char) ((s) >> 8),                                            \
+     (p)[3] = (u_char)  (s),                                                  \
+     (p) + sizeof(uint32_t))
+
+#endif
+
+
+#define ngx_quic_write_uint16_aligned(p, s)                                   \
+    (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t))
+
+#define ngx_quic_write_uint32_aligned(p, s)                                   \
+    (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t))
+
+#define ngx_quic_varint_len(value)                                            \
+     ((value) <= 63 ? 1                                                       \
+     : ((uint32_t) value) <= 16383 ? 2                                        \
+     : ((uint64_t) value) <= 1073741823 ?  4                                  \
+     : 8)
+
+
+static uint64_t ngx_quic_parse_int(u_char **pos);
+static void ngx_quic_build_int(u_char **pos, uint64_t value);
+
+static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack);
+static size_t ngx_quic_create_crypto(u_char *p,
+    ngx_quic_crypto_frame_t *crypto);
+static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf);
+
+
+/* literal errors indexed by corresponding value */
+static char *ngx_quic_errors[] = {
+    "NO_ERROR",
+    "INTERNAL_ERROR",
+    "SERVER_BUSY",
+    "FLOW_CONTROL_ERROR",
+    "STREAM_LIMIT_ERROR",
+    "STREAM_STATE_ERROR",
+    "FINAL_SIZE_ERROR",
+    "FRAME_ENCODING_ERROR",
+    "TRANSPORT_PARAMETER_ERROR",
+    "CONNECTION_ID_LIMIT_ERROR",
+    "PROTOCOL_VIOLATION",
+    "INVALID_TOKEN",
+    "",
+    "CRYPTO_BUFFER_EXCEEDED",
+    "CRYPTO_ERROR",
+};
+
+
+static uint64_t
+ngx_quic_parse_int(u_char **pos)
+{
+    u_char      *p;
+    uint64_t     value;
+    ngx_uint_t   len;
+
+    p = *pos;
+    len = 1 << ((*p & 0xc0) >> 6);
+    value = *p++ & 0x3f;
+
+    while (--len) {
+        value = (value << 8) + *p++;
+    }
+
+    *pos = p;
+    return value;
+}
+
+
+static void
+ngx_quic_build_int(u_char **pos, uint64_t value)
+{
+    u_char      *p;
+    ngx_uint_t   len;//, len2;
+
+    p = *pos;
+    len = 0;
+
+    while (value >> ((1 << len) * 8 - 2)) {
+        len++;
+    }
+
+    *p = len << 6;
+
+//    len2 =
+    len = (1 << len);
+    len--;
+    *p |= value >> (len * 8);
+    p++;
+
+    while (len) {
+        *p++ = value >> ((len-- - 1) * 8);
+    }
+
+    *pos = p;
+//    return len2;
+}
+
+
+u_char *
+ngx_quic_error_text(uint64_t error_code)
+{
+    return (u_char *) ngx_quic_errors[error_code];
+}
+
+
+ngx_int_t
+ngx_quic_parse_long_header(ngx_quic_header_t *pkt)
+{
+    u_char  *p;
+
+    p = pkt->data;
+
+    ngx_quic_hexdump0(pkt->log, "long input", pkt->data, pkt->len);
+
+    if (!(p[0] & NGX_QUIC_PKT_LONG)) {
+        ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "not a long packet");
+        return NGX_ERROR;
+    }
+
+    pkt->flags = *p++;
+
+    pkt->version = ngx_quic_parse_uint32(p);
+    p += sizeof(uint32_t);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+                   "quic flags:%xi version:%xD", pkt->flags, pkt->version);
+
+    if (pkt->version != quic_version) {
+        ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "unsupported quic version");
+        return NGX_ERROR;
+    }
+
+    pkt->dcid.len = *p++;
+    pkt->dcid.data = p;
+    p += pkt->dcid.len;
+
+    pkt->scid.len = *p++;
+    pkt->scid.data = p;
+    p += pkt->scid.len;
+
+    pkt->raw->pos = p;
+
+    return NGX_OK;
+}
+
+
+size_t
+ngx_quic_create_long_header(ngx_quic_header_t *pkt, ngx_str_t *out,
+    size_t pkt_len, u_char **pnp)
+{
+    u_char    *p, *start;
+
+    p = start = out->data;
+
+    *p++ = pkt->flags;
+
+    p = ngx_quic_write_uint32(p, quic_version);
+
+    *p++ = pkt->scid.len;
+    p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len);
+
+    *p++ = pkt->dcid.len;
+    p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len);
+
+    if (pkt->level == ssl_encryption_initial) {
+        ngx_quic_build_int(&p, pkt->token.len);
+    }
+
+    ngx_quic_build_int(&p, pkt_len + 1); // length (inc. pnl)
+
+    *pnp = p;
+
+    *p++ = (uint64_t) (*pkt->number);
+
+    return p - start;
+}
+
+
+ngx_int_t
+ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid)
+{
+    u_char  *p;
+
+    p = pkt->data;
+
+    ngx_quic_hexdump0(pkt->log, "short input", pkt->data, pkt->len);
+
+    if ((p[0] & NGX_QUIC_PKT_LONG)) {
+        ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "not a short packet");
+        return NGX_ERROR;
+    }
+
+    pkt->flags = *p++;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+                   "quic flags:%xi", pkt->flags);
+
+    if (ngx_memcmp(p, dcid->data, dcid->len) != 0) {
+        ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "unexpected quic dcid");
+        return NGX_ERROR;
+    }
+
+    pkt->dcid.len = dcid->len;
+    pkt->dcid.data = p;
+    p += pkt->dcid.len;
+
+    pkt->raw->pos = p;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_quic_parse_initial_header(ngx_quic_header_t *pkt)
+{
+    u_char     *p;
+    ngx_int_t   plen;
+
+    p = pkt->raw->pos;
+
+    pkt->token.len = ngx_quic_parse_int(&p);
+    pkt->token.data = p;
+
+    p += pkt->token.len;
+
+    plen = ngx_quic_parse_int(&p);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+                   "quic packet length: %d", plen);
+
+    if (plen > pkt->data + pkt->len - p) {
+        ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "truncated initial packet");
+        return NGX_ERROR;
+    }
+
+    pkt->raw->pos = p;
+    pkt->len = plen;
+
+    ngx_quic_hexdump0(pkt->log, "DCID", pkt->dcid.data, pkt->dcid.len);
+    ngx_quic_hexdump0(pkt->log, "SCID", pkt->scid.data, pkt->scid.len);
+    ngx_quic_hexdump0(pkt->log, "token", pkt->token.data, pkt->token.len);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+                   "quic packet length: %d", plen);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt)
+{
+    u_char     *p;
+    ngx_int_t   plen;
+
+    p = pkt->raw->pos;
+
+    plen = ngx_quic_parse_int(&p);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+                   "quic packet length: %d", plen);
+
+    if (plen > pkt->data + pkt->len - p) {
+        ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "truncated handshake packet");
+        return NGX_ERROR;
+    }
+
+    pkt->raw->pos = p;
+    pkt->len = plen;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+                   "quic packet length: %d", plen);
+
+    return NGX_OK;
+}
+
+
+#define ngx_quic_stream_bit_off(val)  (((val) & 0x04) ? 1 : 0)
+#define ngx_quic_stream_bit_len(val)  (((val) & 0x02) ? 1 : 0)
+#define ngx_quic_stream_bit_fin(val)  (((val) & 0x01) ? 1 : 0)
+
+ssize_t
+ngx_quic_parse_frame(u_char *start, u_char *end, ngx_quic_frame_t *frame)
+{
+    u_char *p;
+
+    size_t npad;
+
+    p = start;
+
+    frame->type = *p++;  // TODO: check overflow (p < end)
+
+    switch (frame->type) {
+
+    case NGX_QUIC_FT_CRYPTO:
+        frame->u.crypto.offset = *p++;
+        frame->u.crypto.len = ngx_quic_parse_int(&p);
+        frame->u.crypto.data = p;
+        p += frame->u.crypto.len;
+
+        break;
+
+    case NGX_QUIC_FT_PADDING:
+        npad = 0;
+        while (p < end && *p == NGX_QUIC_FT_PADDING) { // XXX
+            p++; npad++;
+        }
+
+        break;
+
+    case NGX_QUIC_FT_ACK:
+    case NGX_QUIC_FT_ACK_ECN:
+
+        frame->u.ack.largest = ngx_quic_parse_int(&p);
+        frame->u.ack.delay = ngx_quic_parse_int(&p);
+        frame->u.ack.range_count =ngx_quic_parse_int(&p);
+        frame->u.ack.first_range =ngx_quic_parse_int(&p);
+
+        if (frame->u.ack.range_count) {
+            frame->u.ack.ranges[0] = ngx_quic_parse_int(&p);
+        }
+
+        if (frame->type ==NGX_QUIC_FT_ACK_ECN) {
+            return NGX_ERROR;
+        }
+
+        break;
+
+    case NGX_QUIC_FT_PING:
+        break;
+
+    case NGX_QUIC_FT_NEW_CONNECTION_ID:
+
+        frame->u.ncid.seqnum = ngx_quic_parse_int(&p);
+        frame->u.ncid.retire = ngx_quic_parse_int(&p);
+        frame->u.ncid.len = *p++;
+        ngx_memcpy(frame->u.ncid.cid, p, frame->u.ncid.len);
+        p += frame->u.ncid.len;
+
+        ngx_memcpy(frame->u.ncid.srt, p, 16);
+        p += 16;
+
+        break;
+
+    case NGX_QUIC_FT_CONNECTION_CLOSE:
+
+        frame->u.close.error_code = ngx_quic_parse_int(&p);
+        frame->u.close.frame_type = ngx_quic_parse_int(&p); // not in 0x1d CC
+        frame->u.close.reason.len = ngx_quic_parse_int(&p);
+        frame->u.close.reason.data = p;
+        p += frame->u.close.reason.len;
+
+        if (frame->u.close.error_code > NGX_QUIC_ERR_LAST) {
+            frame->u.close.error_code = NGX_QUIC_ERR_LAST;
+        }
+        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:
+
+        frame->u.stream.type = frame->type;
+
+        frame->u.stream.off = ngx_quic_stream_bit_off(frame->type);
+        frame->u.stream.len = ngx_quic_stream_bit_len(frame->type);
+        frame->u.stream.fin = ngx_quic_stream_bit_fin(frame->type);
+
+        frame->u.stream.stream_id = ngx_quic_parse_int(&p);
+        if (frame->type & 0x04) {
+            frame->u.stream.offset = ngx_quic_parse_int(&p);
+        } else {
+            frame->u.stream.offset = 0;
+        }
+
+        if (frame->type & 0x02) {
+            frame->u.stream.length = ngx_quic_parse_int(&p);
+        } else {
+            frame->u.stream.length = end - p; /* up to packet end */
+        }
+
+        frame->u.stream.data = p;
+
+        p += frame->u.stream.length;
+
+        break;
+
+    default:
+        return NGX_ERROR;
+    }
+
+    return p - start;
+}
+
+
+ssize_t
+ngx_quic_create_frame(u_char *p, u_char *end, ngx_quic_frame_t *f)
+{
+    // TODO: handle end arg
+
+    switch (f->type) {
+    case NGX_QUIC_FT_ACK:
+        return ngx_quic_create_ack(p, &f->u.ack);
+
+    case NGX_QUIC_FT_CRYPTO:
+        return ngx_quic_create_crypto(p, &f->u.crypto);
+
+    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:
+        return ngx_quic_create_stream(p, &f->u.stream);
+
+    default:
+        /* BUG: unsupported frame type generated */
+        return NGX_ERROR;
+    }
+}
+
+
+size_t
+ngx_quic_frame_len(ngx_quic_frame_t *frame)
+{
+     switch (frame->type) {
+        case NGX_QUIC_FT_ACK:
+            return ngx_quic_create_ack(NULL, &frame->u.ack);
+        case NGX_QUIC_FT_CRYPTO:
+            return ngx_quic_create_crypto(NULL, &frame->u.crypto);
+
+        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:
+            return ngx_quic_create_stream(NULL, &frame->u.stream);
+        default:
+            /* BUG: unsupported frame type generated */
+            return 0;
+     }
+}
+
+
+static size_t
+ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack)
+{
+    size_t  len;
+
+    /* minimal ACK packet */
+
+    if (p == NULL) {
+        len = ngx_quic_varint_len(NGX_QUIC_FT_ACK);
+        len += ngx_quic_varint_len(ack->pn);
+        len += ngx_quic_varint_len(0);
+        len += ngx_quic_varint_len(0);
+        len += ngx_quic_varint_len(ack->pn);
+
+        return len;
+    }
+
+    ngx_quic_build_int(&p, NGX_QUIC_FT_ACK);
+    ngx_quic_build_int(&p, ack->pn);
+    ngx_quic_build_int(&p, 0);
+    ngx_quic_build_int(&p, 0);
+    ngx_quic_build_int(&p, ack->pn);
+
+    return 5;
+}
+
+
+static size_t
+ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto)
+{
+    size_t   len;
+    u_char  *start;
+
+    if (p == NULL) {
+        len = ngx_quic_varint_len(NGX_QUIC_FT_CRYPTO);
+        len += ngx_quic_varint_len(crypto->offset);
+        len += ngx_quic_varint_len(crypto->len);
+        len += crypto->len;
+
+        return len;
+    }
+
+    start = p;
+
+    ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO);
+    ngx_quic_build_int(&p, crypto->offset);
+    ngx_quic_build_int(&p, crypto->len);
+    p = ngx_cpymem(p, crypto->data, crypto->len);
+
+    return p - start;
+}
+
+
+static size_t
+ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf)
+{
+    size_t   len;
+    u_char  *start;
+
+    if (!sf->len) {
+#if 0
+        ngx_log_error(NGX_LOG_INFO, log, 0,
+                      "attempt to generate a stream frame without length");
+#endif
+        // XXX: handle error in caller
+        return NGX_ERROR;
+    }
+
+    if (p == NULL) {
+        len = ngx_quic_varint_len(sf->type);
+
+        if (sf->off) {
+            len += ngx_quic_varint_len(sf->offset);
+        }
+
+        len += ngx_quic_varint_len(sf->stream_id);
+
+        /* length is always present in generated frames */
+        len += ngx_quic_varint_len(sf->length);
+
+        len += sf->length;
+
+        return len;
+    }
+
+    start = p;
+
+    ngx_quic_build_int(&p, sf->type);
+    ngx_quic_build_int(&p, sf->stream_id);
+
+    if (sf->off) {
+        ngx_quic_build_int(&p, sf->offset);
+    }
+
+    /* length is always present in generated frames */
+    ngx_quic_build_int(&p, sf->length);
+
+    p = ngx_cpymem(p, sf->data, sf->length);
+
+    return p - start;
+}
new file mode 100644
--- /dev/null
+++ b/src/event/ngx_event_quic_transport.h
@@ -0,0 +1,177 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_EVENT_QUIC_WIRE_H_INCLUDED_
+#define _NGX_EVENT_QUIC_WIRE_H_INCLUDED_
+
+
+#include <ngx_event_openssl.h>
+
+
+/* 17.2.  Long Header Packets */
+#define NGX_QUIC_PKT_LONG                       0x80
+
+#define NGX_QUIC_PKT_INITIAL                    0xC0
+#define NGX_QUIC_PKT_HANDSHAKE                  0xE0
+
+/* 12.4.  Frames and Frame Types */
+#define NGX_QUIC_FT_PADDING                     0x00
+#define NGX_QUIC_FT_PING                        0x01
+#define NGX_QUIC_FT_ACK                         0x02
+#define NGX_QUIC_FT_ACK_ECN                     0x03
+#define NGX_QUIC_FT_RESET_STREAM                0x04
+#define NGX_QUIC_FT_STOP_SENDING                0x05
+#define NGX_QUIC_FT_CRYPTO                      0x06
+#define NGX_QUIC_FT_NEW_TOKEN                   0x07
+#define NGX_QUIC_FT_STREAM0                     0x08
+#define NGX_QUIC_FT_STREAM1                     0x09
+#define NGX_QUIC_FT_STREAM2                     0x0A
+#define NGX_QUIC_FT_STREAM3                     0x0B
+#define NGX_QUIC_FT_STREAM4                     0x0C
+#define NGX_QUIC_FT_STREAM5                     0x0D
+#define NGX_QUIC_FT_STREAM6                     0x0E
+#define NGX_QUIC_FT_STREAM7                     0x0F
+#define NGX_QUIC_FT_MAX_DATA                    0x10
+#define NGX_QUIC_FT_MAX_STREAM_DATA             0x11
+#define NGX_QUIC_FT_MAX_STREAMS                 0x12
+#define NGX_QUIC_FT_MAX_STREAMS2                0x13 // XXX
+#define NGX_QUIC_FT_DATA_BLOCKED                0x14
+#define NGX_QUIC_FT_STREAM_DATA_BLOCKED         0x15
+#define NGX_QUIC_FT_STREAMS_BLOCKED             0x16
+#define NGX_QUIC_FT_STREAMS_BLOCKED2            0x17 // XXX
+#define NGX_QUIC_FT_NEW_CONNECTION_ID           0x18
+#define NGX_QUIC_FT_RETIRE_CONNECTION_ID        0x19
+#define NGX_QUIC_FT_PATH_CHALLENGE              0x1A
+#define NGX_QUIC_FT_PATH_RESPONSE               0x1B
+#define NGX_QUIC_FT_CONNECTION_CLOSE            0x1C
+#define NGX_QUIC_FT_CONNECTION_CLOSE2           0x1D // XXX
+#define NGX_QUIC_FT_HANDSHAKE_DONE              0x1E
+
+/* 22.4.  QUIC Transport Error Codes Registry */
+#define NGX_QUIC_ERR_NO_ERROR                   0x00
+#define NGX_QUIC_ERR_INTERNAL_ERROR             0x01
+#define NGX_QUIC_ERR_SERVER_BUSY                0x02
+#define NGX_QUIC_ERR_FLOW_CONTROL_ERROR         0x03
+#define NGX_QUIC_ERR_STREAM_LIMIT_ERROR         0x04
+#define NGX_QUIC_ERR_STREAM_STATE_ERROR         0x05
+#define NGX_QUIC_ERR_FINAL_SIZE_ERROR           0x06
+#define NGX_QUIC_ERR_FRAME_ENCODING_ERROR       0x07
+#define NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR  0x08
+#define NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR  0x09
+#define NGX_QUIC_ERR_PROTOCOL_VIOLATION         0x0A
+#define NGX_QUIC_ERR_INVALID_TOKEN              0x0B
+/* 0xC is not defined */
+#define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED     0x0D
+#define NGX_QUIC_ERR_CRYPTO_ERROR               0x10
+
+#define NGX_QUIC_ERR_LAST  NGX_QUIC_ERR_CRYPTO_ERROR
+
+
+typedef struct {
+    ngx_uint_t                                  pn;
+    uint64_t                                    largest;
+    uint64_t                                    delay;
+    uint64_t                                    range_count;
+    uint64_t                                    first_range;
+    uint64_t                                    ranges[20];
+    /* TODO: ecn counts */
+} ngx_quic_ack_frame_t;
+
+
+typedef struct {
+    size_t                                      offset;
+    size_t                                      len;
+    u_char                                     *data;
+} ngx_quic_crypto_frame_t;
+
+
+typedef struct {
+    uint64_t                                    seqnum;
+    uint64_t                                    retire;
+    uint64_t                                    len;
+    u_char                                      cid[20];
+    u_char                                      srt[16];
+} ngx_quic_new_conn_id_frame_t;
+
+
+typedef struct {
+    uint8_t                                     type;
+    uint64_t                                    stream_id;
+    uint64_t                                    offset;
+    uint64_t                                    length;
+    unsigned                                    off:1;
+    unsigned                                    len:1;
+    unsigned                                    fin:1;
+    u_char                                     *data;
+} ngx_quic_stream_frame_t;
+
+
+typedef struct {
+    uint64_t                                    error_code;
+    uint64_t                                    frame_type;
+    ngx_str_t                                   reason;
+} ngx_quic_close_frame_t;
+
+
+typedef struct ngx_quic_frame_s                 ngx_quic_frame_t;
+
+struct ngx_quic_frame_s {
+    ngx_uint_t                                  type;
+    enum ssl_encryption_level_t                 level;
+    ngx_quic_frame_t                           *next;
+    union {
+        ngx_quic_ack_frame_t                    ack;
+        ngx_quic_crypto_frame_t                 crypto;
+        ngx_quic_new_conn_id_frame_t            ncid;
+        ngx_quic_stream_frame_t                 stream;
+        ngx_quic_close_frame_t                  close;
+    } u;
+    u_char                                      info[128]; // for debug
+};
+
+
+typedef struct {
+    ngx_log_t                                  *log;
+
+    struct ngx_quic_secret_s                   *secret;
+    ngx_uint_t                                  type;
+    ngx_uint_t                                  *number;
+    ngx_uint_t                                  flags;
+    uint32_t                                    version;
+    ngx_str_t                                   token;
+    enum ssl_encryption_level_t                 level;
+
+    /* filled in by parser */
+    ngx_buf_t                                  *raw;   /* udp datagram */
+
+    u_char                                     *data;  /* quic packet */
+    size_t                                      len;
+
+    /* cleartext fields */
+    ngx_str_t                                   dcid;
+    ngx_str_t                                   scid;
+    uint64_t                                    pn;
+    ngx_str_t                                   payload;  /* decrypted */
+} ngx_quic_header_t;
+
+
+u_char *ngx_quic_error_text(uint64_t error_code);
+
+ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt);
+size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, ngx_str_t *out,
+    size_t pkt_len, u_char **pnp);
+
+ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt,
+    ngx_str_t *dcid);
+ngx_int_t ngx_quic_parse_initial_header(ngx_quic_header_t *pkt);
+ngx_int_t ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt);
+
+ssize_t ngx_quic_parse_frame(u_char *start, u_char *end,
+    ngx_quic_frame_t *frame);
+ssize_t ngx_quic_create_frame(u_char *p, u_char *end, ngx_quic_frame_t *f);
+size_t ngx_quic_frame_len(ngx_quic_frame_t *frame);
+
+#endif /* _NGX_EVENT_QUIC_WIRE_H_INCLUDED_ */