changeset 8182:b28ea685a56e quic

Moved all QUIC code into ngx_event_quic.c Introduced ngx_quic_input() and ngx_quic_output() as interface between nginx and protocol. They are the only functions that are exported. While there, added copyrights.
author Vladimir Homutov <vl@nginx.com>
date Fri, 28 Feb 2020 16:23:25 +0300
parents 3cb4f16426a5
children 6091506af0f7
files src/event/ngx_event_openssl.c src/event/ngx_event_quic.c src/event/ngx_event_quic.h src/http/ngx_http_request.c
diffstat 4 files changed, 1053 insertions(+), 969 deletions(-) [+]
line wrap: on
line diff
--- a/src/event/ngx_event_openssl.c
+++ b/src/event/ngx_event_openssl.c
@@ -90,328 +90,6 @@ static char *ngx_openssl_engine(ngx_conf
 static void ngx_openssl_exit(ngx_cycle_t *cycle);
 
 
-#if NGX_OPENSSL_QUIC
-
-static int
-quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn,
-    enum ssl_encryption_level_t level, const uint8_t *read_secret,
-    const uint8_t *write_secret, size_t secret_len)
-{
-    u_char             *name;
-    ngx_uint_t          i;
-    const EVP_MD       *digest;
-    const EVP_CIPHER   *cipher;
-    ngx_connection_t   *c;
-    ngx_quic_secret_t  *client, *server;
-
-    c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
-
-    ngx_ssl_handshake_log(c);
-
-#if (NGX_DEBUG)
-    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
-        u_char  buf[64];
-        size_t  m;
-
-        m = ngx_hex_dump(buf, (u_char *) read_secret, secret_len) - buf;
-        ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                       "set_encryption_secrets: read %*s, len: %uz, level:%d",
-                       m, buf, secret_len, (int) level);
-
-        m = ngx_hex_dump(buf, (u_char *) write_secret, secret_len) - buf;
-        ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                       "set_encryption_secrets: write %*s, len: %uz, level:%d",
-                       m, buf, secret_len, (int) level);
-    }
-#endif
-
-    name = (u_char *) SSL_get_cipher(ssl_conn);
-
-    if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0
-        || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0)
-    {
-        cipher = EVP_aes_128_gcm();
-        digest = EVP_sha256();
-
-    } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) {
-        cipher = EVP_aes_256_gcm();
-        digest = EVP_sha384();
-
-    } else {
-        ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher");
-        return 0;
-    }
-
-    switch (level) {
-
-    case ssl_encryption_handshake:
-        client = &c->quic->client_hs;
-        server = &c->quic->server_hs;
-
-        break;
-
-    case ssl_encryption_application:
-        client = &c->quic->client_ad;
-        server = &c->quic->server_ad;
-
-        break;
-
-    default:
-        return 0;
-    }
-
-    client->key.len = EVP_CIPHER_key_length(cipher);
-    server->key.len = EVP_CIPHER_key_length(cipher);
-
-    client->iv.len = EVP_CIPHER_iv_length(cipher);
-    server->iv.len = EVP_CIPHER_iv_length(cipher);
-
-    client->hp.len = EVP_CIPHER_key_length(cipher);
-    server->hp.len = EVP_CIPHER_key_length(cipher);
-
-    struct {
-        ngx_str_t       label;
-        ngx_str_t      *key;
-        const uint8_t  *secret;
-    } seq[] = {
-        { ngx_string("tls13 quic key"), &client->key, read_secret  },
-        { ngx_string("tls13 quic iv"),  &client->iv,  read_secret  },
-        { ngx_string("tls13 quic hp"),  &client->hp,  read_secret  },
-        { ngx_string("tls13 quic key"), &server->key, write_secret },
-        { ngx_string("tls13 quic iv"),  &server->iv,  write_secret },
-        { ngx_string("tls13 quic hp"),  &server->hp,  write_secret },
-    };
-
-    for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) {
-
-        if (ngx_quic_hkdf_expand(c, digest, seq[i].key, &seq[i].label,
-                                 seq[i].secret, secret_len)
-            != NGX_OK)
-        {
-            return 0;
-        }
-    }
-
-    return 1;
-}
-
-
-static int
-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, *pnp, *name, *nonce, *sample;
-    ngx_int_t                 m;
-    ngx_str_t                 in, out, ad;
-    static int                pn;
-    const EVP_CIPHER         *cipher;
-    ngx_connection_t         *c;
-    ngx_quic_secret_t        *secret;
-    ngx_quic_connection_t    *qc;
-    u_char                    buf[2048], mask[16];
-
-    c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
-    qc = c->quic;
-
-    ngx_ssl_handshake_log(c);
-
-    switch (level) {
-
-    case ssl_encryption_initial:
-        secret = &qc->server_in;
-        break;
-
-    case ssl_encryption_handshake:
-        secret = &qc->server_hs;
-        break;
-
-    default:
-        return 0;
-    }
-
-    m = ngx_hex_dump(buf, (u_char *) data, ngx_min(len, 1024)) - buf;
-    ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic_add_handshake_data: %*s%s, len: %uz, level:%d",
-                   m, buf, len < 2048 ? "" : "...", len, (int) level);
-
-    in.data = ngx_alloc(4 + len + 5 /*minimal ACK*/, c->log);
-    if (in.data == 0) {
-        return 0;
-    }
-
-    p = in.data;
-    ngx_quic_build_int(&p, 6);	// crypto frame
-    ngx_quic_build_int(&p, 0);
-    ngx_quic_build_int(&p, len);
-    p = ngx_cpymem(p, data, len);
-
-    if (level == ssl_encryption_initial) {
-        ngx_quic_build_int(&p, 2);	// ack frame
-        ngx_quic_build_int(&p, 0);
-        ngx_quic_build_int(&p, 0);
-        ngx_quic_build_int(&p, 0);
-        ngx_quic_build_int(&p, 0);
-    }
-
-    in.len = p - in.data;
-    out.len = in.len + EVP_GCM_TLS_TAG_LEN;
-
-    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic_add_handshake_data: clear_len:%uz, ciphertext_len:%uz",
-                   in.len, out.len);
-
-    ad.data = ngx_alloc(346 /*max header*/, c->log);
-    if (ad.data == 0) {
-        return 0;
-    }
-
-    p = ad.data;
-    if (level == ssl_encryption_initial) {
-        *p++ = 0xc0;	// initial, packet number len
-    } else if (level == ssl_encryption_handshake) {
-        *p++ = 0xe0;	// handshake, packet number len
-    }
-    p = ngx_quic_write_uint32(p, quic_version);
-    *p++ = qc->scid.len;
-    p = ngx_cpymem(p, qc->scid.data, qc->scid.len);
-    *p++ = qc->dcid.len;
-    p = ngx_cpymem(p, qc->dcid.data, qc->dcid.len);
-    if (level == ssl_encryption_initial) {
-        ngx_quic_build_int(&p, 0);	// token length
-    }
-    ngx_quic_build_int(&p, out.len + 1); // length (inc. pnl)
-    pnp = p;
-
-    if (level == ssl_encryption_initial) {
-        *p++ = 0;	// packet number 0
-
-    } else if (level == ssl_encryption_handshake) {
-        *p++ = pn++;
-    }
-
-    ad.len = p - ad.data;
-
-    m = ngx_hex_dump(buf, ad.data, ad.len) - buf;
-    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic_add_handshake_data ad: %*s, len: %uz",
-                   m, buf, ad.len);
-
-
-    name = (u_char *) SSL_get_cipher(ssl_conn);
-
-    if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0
-        || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0)
-    {
-        cipher = EVP_aes_128_gcm();
-
-    } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) {
-        cipher = EVP_aes_256_gcm();
-
-    } else {
-        return 0;
-    }
-
-    nonce = ngx_pstrdup(c->pool, &secret->iv);
-    if (level == ssl_encryption_handshake) {
-        nonce[11] ^= (pn - 1);
-    }
-
-    m = ngx_hex_dump(buf, (u_char *) secret->iv.data, 12) - buf;
-    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic_add_handshake_data sample: server_iv %*s",
-                   m, buf);
-    m = ngx_hex_dump(buf, (u_char *) nonce, 12) - buf;
-    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic_add_handshake_data sample: n=%d nonce %*s",
-                   pn - 1, m, buf);
-
-    if (ngx_quic_tls_seal(c, cipher, secret, &out, nonce, &in, &ad) != NGX_OK)
-    {
-        return 0;
-    }
-
-    sample = &out.data[3]; // pnl=0
-    if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), secret, mask, sample) != NGX_OK) {
-        return 0;
-    }
-
-    m = ngx_hex_dump(buf, (u_char *) sample, 16) - buf;
-    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic_add_handshake_data sample: %*s, len: %uz",
-                   m, buf, 16);
-
-    m = ngx_hex_dump(buf, (u_char *) mask, 16) - buf;
-    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic_add_handshake_data mask: %*s, len: %uz",
-                   m, buf, 16);
-
-    m = ngx_hex_dump(buf, (u_char *) secret->hp.data, 16) - buf;
-    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic_add_handshake_data hp_key: %*s, len: %uz",
-                   m, buf, 16);
-
-    // header protection, pnl = 0
-    ad.data[0] ^= mask[0] & 0x0f;
-    *pnp ^= mask[1];
-
-    u_char *packet = ngx_alloc(ad.len + out.len, c->log);
-    if (packet == 0) {
-        return 0;
-    }
-
-    p = ngx_cpymem(packet, ad.data, ad.len);
-    p = ngx_cpymem(p, out.data, out.len);
-
-    m = ngx_hex_dump(buf, (u_char *) packet, ngx_min(1024, p - packet)) - buf;
-    ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic_add_handshake_data packet: %*s%s, len: %uz",
-                   m, buf, len < 2048 ? "" : "...", p - packet);
-
-    c->send(c, packet, p - packet);
-
-    return 1;
-}
-
-
-static int
-quic_flush_flight(ngx_ssl_conn_t *ssl_conn)
-{
-    ngx_connection_t  *c;
-
-    c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
-
-    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic_flush_flight()");
-
-    return 1;
-}
-
-
-static int
-quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level,
-    uint8_t alert)
-{
-    ngx_connection_t  *c;
-
-    c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
-
-    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic_send_alert(), lvl=%d, alert=%d",
-                   (int) level, (int) alert);
-
-    return 1;
-}
-
-
-static SSL_QUIC_METHOD quic_method = {
-    quic_set_encryption_secrets,
-    quic_add_handshake_data,
-    quic_flush_flight,
-    quic_send_alert,
-};
-
-#endif
-
-
 static ngx_command_t  ngx_openssl_commands[] = {
 
     { ngx_string("ssl_engine"),
@@ -1790,7 +1468,7 @@ ngx_ssl_quic(ngx_conf_t *cf, ngx_ssl_t *
 
 #if NGX_OPENSSL_QUIC
 
-    SSL_CTX_set_quic_method(ssl->ctx, &quic_method);
+    ngx_quic_init_ssl_methods(ssl->ctx);
     return NGX_OK;
 
 #else
--- a/src/event/ngx_event_quic.c
+++ b/src/event/ngx_event_quic.c
@@ -1,6 +1,1031 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ */
+
+
 #include <ngx_config.h>
 #include <ngx_core.h>
-#include <ngx_event.h>
+
+
+#define quic_version 0xff000018
+
+
+#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))
+
+
+/* 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_str_t          secret;
+    ngx_str_t          key;
+    ngx_str_t          iv;
+    ngx_str_t          hp;
+} ngx_quic_secret_t;
+
+
+struct ngx_quic_connection_s {
+
+    ngx_quic_state_t   state;
+    ngx_ssl_t         *ssl;
+
+    ngx_str_t          out; // stub for some kind of output queue
+
+    ngx_str_t          scid;
+    ngx_str_t          dcid;
+    ngx_str_t          token;
+
+    ngx_quic_secret_t  client_in;
+    ngx_quic_secret_t  client_hs;
+    ngx_quic_secret_t  client_ad;
+    ngx_quic_secret_t  server_in;
+    ngx_quic_secret_t  server_hs;
+    ngx_quic_secret_t  server_ad;
+};
+
+
+static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl,
+    ngx_buf_t *b);
+static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *b);
+
+static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn,
+    enum ssl_encryption_level_t level, const uint8_t *read_secret,
+    const uint8_t *write_secret, size_t secret_len);
+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);
+static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn,
+    enum ssl_encryption_level_t level, uint8_t alert);
+
+static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask);
+static uint64_t ngx_quic_parse_int(u_char **pos);
+static void ngx_quic_build_int(u_char **pos, uint64_t value);
+
+static ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len,
+    const EVP_MD *digest, const u_char *secret, size_t secret_len,
+    const u_char *salt, size_t salt_len);
+static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len,
+    const EVP_MD *digest, const u_char *prk, size_t prk_len,
+    const u_char *info, size_t info_len);
+
+static ngx_int_t ngx_quic_hkdf_expand(ngx_connection_t *c, const EVP_MD *digest,
+    ngx_str_t *out, ngx_str_t *label, const uint8_t *prk, size_t prk_len);
+
+static ngx_int_t ngx_quic_tls_open(ngx_connection_t *c,
+    const EVP_CIPHER *cipher, ngx_quic_secret_t *s, ngx_str_t *out,
+    u_char *nonce, ngx_str_t *in, ngx_str_t *ad);
+static ngx_int_t ngx_quic_tls_seal(ngx_connection_t *c,
+    const EVP_CIPHER *cipher, ngx_quic_secret_t *s, ngx_str_t *out,
+    u_char *nonce, ngx_str_t *in, ngx_str_t *ad);
+
+static ngx_int_t ngx_quic_tls_hp(ngx_connection_t *c, const EVP_CIPHER *cipher,
+    ngx_quic_secret_t *s, u_char *out, u_char *in);
+
+
+static SSL_QUIC_METHOD quic_method = {
+    ngx_quic_set_encryption_secrets,
+    ngx_quic_add_handshake_data,
+    ngx_quic_flush_flight,
+    ngx_quic_send_alert,
+};
+
+
+void
+ngx_quic_init_ssl_methods(SSL_CTX* ctx)
+{
+    SSL_CTX_set_quic_method(ctx, &quic_method);
+}
+
+
+ngx_int_t
+ngx_quic_input(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b)
+{
+    if (c->quic == NULL) {
+        return ngx_quic_new_connection(c, ssl, b); //TODO: change state by results
+    }
+
+    switch (c->quic->state) {
+    case NGX_QUIC_ST_INITIAL:
+    case NGX_QUIC_ST_HANDSHAKE:
+        return ngx_quic_handshake_input(c, b);
+    default:
+        /* application data */
+        break;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_quic_output(ngx_connection_t *c)
+{
+    /* stub for processing output queue */
+
+    if (c->quic->out.data) {
+        c->send(c, c->quic->out.data, c->quic->out.len);
+        c->quic->out.data = NULL;
+    }
+
+    return NGX_OK;
+}
+
+
+static int
+ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn,
+    enum ssl_encryption_level_t level, const uint8_t *read_secret,
+    const uint8_t *write_secret, size_t secret_len)
+{
+    u_char             *name;
+    ngx_uint_t          i;
+    const EVP_MD       *digest;
+    const EVP_CIPHER   *cipher;
+    ngx_connection_t   *c;
+    ngx_quic_secret_t  *client, *server;
+
+    c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
+
+    //ngx_ssl_handshake_log(c); // TODO: enable again
+
+#if (NGX_DEBUG)
+    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
+        u_char  buf[64];
+        size_t  m;
+
+        m = ngx_hex_dump(buf, (u_char *) read_secret, secret_len) - buf;
+        ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "set_encryption_secrets: read %*s, len: %uz, level:%d",
+                       m, buf, secret_len, (int) level);
+
+        m = ngx_hex_dump(buf, (u_char *) write_secret, secret_len) - buf;
+        ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "set_encryption_secrets: write %*s, len: %uz, level:%d",
+                       m, buf, secret_len, (int) level);
+    }
+#endif
+
+    name = (u_char *) SSL_get_cipher(ssl_conn);
+
+    if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0
+        || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0)
+    {
+        cipher = EVP_aes_128_gcm();
+        digest = EVP_sha256();
+
+    } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) {
+        cipher = EVP_aes_256_gcm();
+        digest = EVP_sha384();
+
+    } else {
+        ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher");
+        return 0;
+    }
+
+    switch (level) {
+
+    case ssl_encryption_handshake:
+        client = &c->quic->client_hs;
+        server = &c->quic->server_hs;
+
+        break;
+
+    case ssl_encryption_application:
+        client = &c->quic->client_ad;
+        server = &c->quic->server_ad;
+
+        break;
+
+    default:
+        return 0;
+    }
+
+    client->key.len = EVP_CIPHER_key_length(cipher);
+    server->key.len = EVP_CIPHER_key_length(cipher);
+
+    client->iv.len = EVP_CIPHER_iv_length(cipher);
+    server->iv.len = EVP_CIPHER_iv_length(cipher);
+
+    client->hp.len = EVP_CIPHER_key_length(cipher);
+    server->hp.len = EVP_CIPHER_key_length(cipher);
+
+    struct {
+        ngx_str_t       label;
+        ngx_str_t      *key;
+        const uint8_t  *secret;
+    } seq[] = {
+        { ngx_string("tls13 quic key"), &client->key, read_secret  },
+        { ngx_string("tls13 quic iv"),  &client->iv,  read_secret  },
+        { ngx_string("tls13 quic hp"),  &client->hp,  read_secret  },
+        { ngx_string("tls13 quic key"), &server->key, write_secret },
+        { ngx_string("tls13 quic iv"),  &server->iv,  write_secret },
+        { ngx_string("tls13 quic hp"),  &server->hp,  write_secret },
+    };
+
+    for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) {
+
+        if (ngx_quic_hkdf_expand(c, digest, seq[i].key, &seq[i].label,
+                                 seq[i].secret, secret_len)
+            != NGX_OK)
+        {
+            return 0;
+        }
+    }
+
+    return 1;
+}
+
+
+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, *pnp, *name, *nonce, *sample;
+    ngx_int_t                 m;
+    ngx_str_t                 in, out, ad;
+    static int                pn;
+    const EVP_CIPHER         *cipher;
+    ngx_connection_t         *c;
+    ngx_quic_secret_t        *secret;
+    ngx_quic_connection_t    *qc;
+    u_char                    buf[2048], mask[16];
+
+    c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
+    qc = c->quic;
+
+    //ngx_ssl_handshake_log(c); // TODO: enable again
+
+    switch (level) {
+
+    case ssl_encryption_initial:
+        secret = &qc->server_in;
+        break;
+
+    case ssl_encryption_handshake:
+        secret = &qc->server_hs;
+        break;
+
+    default:
+        return 0;
+    }
+
+    m = ngx_hex_dump(buf, (u_char *) data, ngx_min(len, 1024)) - buf;
+    ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "ngx_quic_add_handshake_data: %*s%s, len: %uz, level:%d",
+                   m, buf, len < 2048 ? "" : "...", len, (int) level);
+
+    in.data = ngx_alloc(4 + len + 5 /*minimal ACK*/, c->log);
+    if (in.data == 0) {
+        return 0;
+    }
+
+    p = in.data;
+    ngx_quic_build_int(&p, 6);	// crypto frame
+    ngx_quic_build_int(&p, 0);
+    ngx_quic_build_int(&p, len);
+    p = ngx_cpymem(p, data, len);
+
+    if (level == ssl_encryption_initial) {
+        ngx_quic_build_int(&p, 2);	// ack frame
+        ngx_quic_build_int(&p, 0);
+        ngx_quic_build_int(&p, 0);
+        ngx_quic_build_int(&p, 0);
+        ngx_quic_build_int(&p, 0);
+    }
+
+    in.len = p - in.data;
+    out.len = in.len + EVP_GCM_TLS_TAG_LEN;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "ngx_quic_add_handshake_data: clear_len:%uz, ciphertext_len:%uz",
+                   in.len, out.len);
+
+    ad.data = ngx_alloc(346 /*max header*/, c->log);
+    if (ad.data == 0) {
+        return 0;
+    }
+
+    p = ad.data;
+    if (level == ssl_encryption_initial) {
+        *p++ = 0xc0;	// initial, packet number len
+    } else if (level == ssl_encryption_handshake) {
+        *p++ = 0xe0;	// handshake, packet number len
+    }
+    p = ngx_quic_write_uint32(p, quic_version);
+    *p++ = qc->scid.len;
+    p = ngx_cpymem(p, qc->scid.data, qc->scid.len);
+    *p++ = qc->dcid.len;
+    p = ngx_cpymem(p, qc->dcid.data, qc->dcid.len);
+    if (level == ssl_encryption_initial) {
+        ngx_quic_build_int(&p, 0);	// token length
+    }
+    ngx_quic_build_int(&p, out.len + 1); // length (inc. pnl)
+    pnp = p;
+
+    if (level == ssl_encryption_initial) {
+        *p++ = 0;	// packet number 0
+
+    } else if (level == ssl_encryption_handshake) {
+        *p++ = pn++;
+    }
+
+    ad.len = p - ad.data;
+
+    m = ngx_hex_dump(buf, ad.data, ad.len) - buf;
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "ngx_quic_add_handshake_data ad: %*s, len: %uz",
+                   m, buf, ad.len);
+
+
+    name = (u_char *) SSL_get_cipher(ssl_conn);
+
+    if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0
+        || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0)
+    {
+        cipher = EVP_aes_128_gcm();
+
+    } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) {
+        cipher = EVP_aes_256_gcm();
+
+    } else {
+        return 0;
+    }
+
+    nonce = ngx_pstrdup(c->pool, &secret->iv);
+    if (level == ssl_encryption_handshake) {
+        nonce[11] ^= (pn - 1);
+    }
+
+    m = ngx_hex_dump(buf, (u_char *) secret->iv.data, 12) - buf;
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "ngx_quic_add_handshake_data sample: server_iv %*s",
+                   m, buf);
+    m = ngx_hex_dump(buf, (u_char *) nonce, 12) - buf;
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "ngx_quic_add_handshake_data sample: n=%d nonce %*s",
+                   pn - 1, m, buf);
+
+    if (ngx_quic_tls_seal(c, cipher, secret, &out, nonce, &in, &ad) != NGX_OK)
+    {
+        return 0;
+    }
+
+    sample = &out.data[3]; // pnl=0
+    if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), secret, mask, sample) != NGX_OK) {
+        return 0;
+    }
+
+    m = ngx_hex_dump(buf, (u_char *) sample, 16) - buf;
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "ngx_quic_add_handshake_data sample: %*s, len: %uz",
+                   m, buf, 16);
+
+    m = ngx_hex_dump(buf, (u_char *) mask, 16) - buf;
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "ngx_quic_add_handshake_data mask: %*s, len: %uz",
+                   m, buf, 16);
+
+    m = ngx_hex_dump(buf, (u_char *) secret->hp.data, 16) - buf;
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "ngx_quic_add_handshake_data hp_key: %*s, len: %uz",
+                   m, buf, 16);
+
+    // header protection, pnl = 0
+    ad.data[0] ^= mask[0] & 0x0f;
+    *pnp ^= mask[1];
+
+    u_char *packet = ngx_alloc(ad.len + out.len, c->log);
+    if (packet == 0) {
+        return 0;
+    }
+
+    p = ngx_cpymem(packet, ad.data, ad.len);
+    p = ngx_cpymem(p, out.data, out.len);
+
+    m = ngx_hex_dump(buf, (u_char *) packet, ngx_min(1024, p - packet)) - buf;
+    ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "ngx_quic_add_handshake_data packet: %*s%s, len: %uz",
+                   m, buf, len < 2048 ? "" : "...", p - packet);
+
+    // TODO: save state of data to send into qc (push into queue)
+
+    qc->out.data = packet;
+    qc->out.len = p - packet;
+
+    if (ngx_quic_output(c) != NGX_OK) {
+        return 0;
+    }
+
+    return 1;
+}
+
+
+static int
+ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn)
+{
+    ngx_connection_t  *c;
+
+    c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "ngx_quic_flush_flight()");
+
+    return 1;
+}
+
+
+static int
+ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level,
+    uint8_t alert)
+{
+    ngx_connection_t  *c;
+
+    c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "ngx_quic_send_alert(), lvl=%d, alert=%d",
+                   (int) level, (int) alert);
+
+    return 1;
+}
+
+
+static ngx_int_t
+ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b)
+{
+    int             n, sslerr;
+#if (NGX_DEBUG)
+    u_char          buf[512];
+    size_t          m;
+#endif
+
+    ngx_quic_connection_t    *qc;
+
+    if ((b->pos[0] & 0xf0) != 0xc0) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0, "invalid initial packet");
+        return NGX_ERROR;
+    }
+
+    if (ngx_buf_size(b) < 1200) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0, "too small UDP datagram");
+        return NGX_ERROR;
+    }
+
+    ngx_int_t flags = *b->pos++;
+    uint32_t version = ngx_quic_parse_uint32(b->pos);
+    b->pos += sizeof(uint32_t);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "quic flags:%xi version:%xD", flags, version);
+
+    if (version != quic_version) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0, "unsupported quic version");
+        return NGX_ERROR;
+    }
+
+    qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t));
+    if (qc == NULL) {
+        return NGX_ERROR;
+    }
+
+    c->quic = qc;
+
+    qc->dcid.len = *b->pos++;
+    qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len);
+    if (qc->dcid.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(qc->dcid.data, b->pos, qc->dcid.len);
+    b->pos += qc->dcid.len;
+
+    qc->scid.len = *b->pos++;
+    qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len);
+    if (qc->scid.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(qc->scid.data, b->pos, qc->scid.len);
+    b->pos += qc->scid.len;
+
+    qc->token.len = ngx_quic_parse_int(&b->pos);
+    qc->token.data = ngx_pnalloc(c->pool, qc->token.len);
+    if (qc->token.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(qc->token.data, b->pos, qc->token.len);
+    b->pos += qc->token.len;
+
+    ngx_int_t plen = ngx_quic_parse_int(&b->pos);
+
+    if (plen > b->last - b->pos) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated initial packet");
+        return NGX_ERROR;
+    }
+
+    /* draft-ietf-quic-tls-23#section-5.4.2:
+     * the Packet Number field is assumed to be 4 bytes long
+     * draft-ietf-quic-tls-23#section-5.4.[34]:
+     * AES-Based and ChaCha20-Based header protections sample 16 bytes
+     */
+    u_char *sample = b->pos + 4;
+
+#if (NGX_DEBUG)
+    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
+        m = ngx_hex_dump(buf, qc->dcid.data, qc->dcid.len) - buf;
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "quic DCID: %*s, len: %uz", m, buf, qc->dcid.len);
+
+        m = ngx_hex_dump(buf, qc->scid.data, qc->scid.len) - buf;
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "quic SCID: %*s, len: %uz", m, buf, qc->scid.len);
+
+        m = ngx_hex_dump(buf, qc->token.data, qc->token.len) - buf;
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "quic token: %*s, len: %uz", m, buf, qc->token.len);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "quic packet length: %d", plen);
+
+        m = ngx_hex_dump(buf, sample, 16) - buf;
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "quic sample: %*s", m, buf);
+    }
+#endif
+
+// initial secret
+
+    size_t                    is_len;
+    uint8_t                   is[SHA256_DIGEST_LENGTH];
+    ngx_uint_t                i;
+    const EVP_MD             *digest;
+    const EVP_CIPHER         *cipher;
+    static const uint8_t salt[20] =
+        "\xc3\xee\xf7\x12\xc7\x2e\xbb\x5a\x11\xa7"
+        "\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02";
+
+    /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */
+
+    cipher = EVP_aes_128_gcm();
+    digest = EVP_sha256();
+
+    if (ngx_hkdf_extract(is, &is_len, digest, qc->dcid.data, qc->dcid.len,
+                         salt, sizeof(salt))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    ngx_str_t iss = {
+        .data = is,
+        .len = is_len
+    };
+
+#if (NGX_DEBUG)
+    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
+        m = ngx_hex_dump(buf, (uint8_t *) salt, sizeof(salt)) - buf;
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "quic salt: %*s, len: %uz", m, buf, sizeof(salt));
+
+        m = ngx_hex_dump(buf, is, is_len) - buf;
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "quic initial secret: %*s, len: %uz", m, buf, is_len);
+    }
+#endif
+
+    /* draft-ietf-quic-tls-23#section-5.2 */
+    qc->client_in.secret.len = SHA256_DIGEST_LENGTH;
+    qc->server_in.secret.len = SHA256_DIGEST_LENGTH;
+
+    qc->client_in.key.len = EVP_CIPHER_key_length(cipher);
+    qc->server_in.key.len = EVP_CIPHER_key_length(cipher);
+
+    qc->client_in.hp.len = EVP_CIPHER_key_length(cipher);
+    qc->server_in.hp.len = EVP_CIPHER_key_length(cipher);
+
+    qc->client_in.iv.len = EVP_CIPHER_iv_length(cipher);
+    qc->server_in.iv.len = EVP_CIPHER_iv_length(cipher);
+
+    struct {
+        ngx_str_t   label;
+        ngx_str_t  *key;
+        ngx_str_t  *prk;
+    } seq[] = {
+
+        /* draft-ietf-quic-tls-23#section-5.2 */
+        { ngx_string("tls13 client in"), &qc->client_in.secret, &iss },
+        {
+            ngx_string("tls13 quic key"),
+            &qc->client_in.key,
+            &qc->client_in.secret,
+        },
+        {
+            ngx_string("tls13 quic iv"),
+            &qc->client_in.iv,
+            &qc->client_in.secret,
+        },
+        {
+            /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */
+            ngx_string("tls13 quic hp"),
+            &qc->client_in.hp,
+            &qc->client_in.secret,
+        },
+        { ngx_string("tls13 server in"), &qc->server_in.secret, &iss },
+        {
+            /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */
+            ngx_string("tls13 quic key"),
+            &qc->server_in.key,
+            &qc->server_in.secret,
+        },
+        {
+            ngx_string("tls13 quic iv"),
+            &qc->server_in.iv,
+            &qc->server_in.secret,
+        },
+        {
+           /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */
+            ngx_string("tls13 quic hp"),
+            &qc->server_in.hp,
+            &qc->server_in.secret,
+        },
+
+    };
+
+    for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) {
+
+        if (ngx_quic_hkdf_expand(c, digest, seq[i].key, &seq[i].label,
+                                 seq[i].prk->data, seq[i].prk->len)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+// header protection
+
+    uint8_t mask[16];
+    if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), &qc->client_in, mask, sample)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    u_char  clearflags = flags ^ (mask[0] & 0x0f);
+    ngx_int_t  pnl = (clearflags & 0x03) + 1;
+    uint64_t  pn = ngx_quic_parse_pn(&b->pos, pnl, &mask[1]);
+
+#if (NGX_DEBUG)
+    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
+        m = ngx_hex_dump(buf, sample, 16) - buf;
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "quic sample: %*s", m, buf);
+
+        m = ngx_hex_dump(buf, mask, 5) - buf;
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "quic mask: %*s", m, buf);
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "quic packet number: %uL, len: %xi", pn, pnl);
+    }
+#endif
+
+// packet protection
+
+    ngx_str_t in;
+    in.data = b->pos;
+    in.len = plen - pnl;
+
+    ngx_str_t ad;
+    ad.len = b->pos - b->start;
+    ad.data = ngx_pnalloc(c->pool, ad.len);
+    if (ad.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(ad.data, b->start, ad.len);
+    ad.data[0] = clearflags;
+    ad.data[ad.len - pnl] = (u_char)pn;
+
+    uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_in.iv);
+    nonce[11] ^= pn;
+
+#if (NGX_DEBUG)
+    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
+        m = ngx_hex_dump(buf, nonce, 12) - buf;
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "quic nonce: %*s, len: %uz", m, buf, 12);
+
+        m = ngx_hex_dump(buf, ad.data, ad.len) - buf;
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "quic ad: %*s, len: %uz", m, buf, ad.len);
+    }
+#endif
+
+    ngx_str_t  out;
+
+    if (ngx_quic_tls_open(c, EVP_aes_128_gcm(), &qc->client_in, &out, nonce,
+                          &in, &ad)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+#if (NGX_DEBUG)
+    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
+        m = ngx_hex_dump(buf, out.data, ngx_min(out.len, 256)) - buf;
+        ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "quic packet payload: %*s%s, len: %uz",
+                       m, buf, m < 512 ? "" : "...", out.len);
+    }
+#endif
+
+    if (out.data[0] != 0x06) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "unexpected frame in initial packet");
+        return NGX_ERROR;
+    }
+
+    if (out.data[1] != 0x00) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "unexpected CRYPTO offset in initial packet");
+        return NGX_ERROR;
+    }
+
+    uint8_t *crypto = &out.data[2];
+    uint64_t crypto_len = ngx_quic_parse_int(&crypto);
+
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "quic initial packet CRYPTO length: %uL pp:%p:%p",
+                   crypto_len, out.data, crypto);
+
+    if (ngx_ssl_create_connection(ssl, c, NGX_SSL_BUFFER) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    static const uint8_t params[12] = "\x00\x0a\x00\x3a\x00\x01\x00\x00\x09\x00\x01\x03";
+
+    if (SSL_set_quic_transport_params(c->ssl->connection, params,
+                                      sizeof(params)) == 0)
+    {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "SSL_set_quic_transport_params() failed");
+        return NGX_ERROR;
+    }
+
+    n = SSL_do_handshake(c->ssl->connection);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n);
+
+    if (n == -1) {
+        sslerr = SSL_get_error(c->ssl->connection, n);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d",
+                       sslerr);
+    }
+
+    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(c->ssl->connection),
+                   (int) SSL_quic_write_level(c->ssl->connection));
+
+    if (!SSL_provide_quic_data(c->ssl->connection,
+                               SSL_quic_read_level(c->ssl->connection),
+                               crypto, crypto_len))
+    {
+        ngx_ssl_error(NGX_LOG_INFO, c->log, 0,
+                      "SSL_provide_quic_data() failed");
+        return NGX_ERROR;
+    }
+
+    n = SSL_do_handshake(c->ssl->connection);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n);
+
+    if (n == -1) {
+        sslerr = SSL_get_error(c->ssl->connection, 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_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "SSL_quic_read_level: %d, SSL_quic_write_level: %d",
+                   (int) SSL_quic_read_level(c->ssl->connection),
+                   (int) SSL_quic_write_level(c->ssl->connection));
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb)
+{
+    size_t                  m;
+    ssize_t                 n;
+    ngx_str_t               out;
+    const EVP_CIPHER       *cipher;
+    ngx_quic_connection_t  *qc;
+    u_char                  buf[4096], *p, *b;
+
+    qc = c->quic;
+
+    n = bb->last - bb->pos;
+    p = bb->pos;
+    b = bb->start;
+
+    m = ngx_hex_dump(buf, b, n) - buf;
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "quic handshake handler: %*s, len: %uz", m, buf, n);
+
+    if ((p[0] & 0xf0) != 0xe0) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0, "invalid packet type");
+        return NGX_ERROR;
+    }
+
+    ngx_int_t flags = *p++;
+    uint32_t 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", flags, version);
+
+    if (version != quic_version) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0, "unsupported quic version");
+        return NGX_ERROR;
+    }
+
+    if (*p++ != qc->dcid.len) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcidl");
+        return NGX_ERROR;
+    }
+
+    if (ngx_memcmp(p, qc->dcid.data, qc->dcid.len) != 0) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcid");
+        return NGX_ERROR;
+    }
+
+    p += qc->dcid.len;
+
+    if (*p++ != qc->scid.len) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scidl");
+        return NGX_ERROR;
+    }
+
+    if (ngx_memcmp(p, qc->scid.data, qc->scid.len) != 0) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scid");
+        return NGX_ERROR;
+    }
+
+    p += qc->scid.len;
+
+    ngx_int_t plen = ngx_quic_parse_int(&p);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "quic packet length: %d", plen);
+
+    if (plen > b + n - p) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated handshake packet");
+        return NGX_ERROR;
+    }
+
+    u_char *sample = p + 4;
+
+    m = ngx_hex_dump(buf, sample, 16) - buf;
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic sample: %*s", m, buf);
+
+// header protection
+
+    uint8_t mask[16];
+    if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), &qc->client_hs, mask, sample)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    u_char  clearflags = flags ^ (mask[0] & 0x0f);
+    ngx_int_t  pnl = (clearflags & 0x03) + 1;
+    uint64_t  pn = ngx_quic_parse_pn(&p, pnl, &mask[1]);
+
+#if (NGX_DEBUG)
+    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
+        m = ngx_hex_dump(buf, mask, 5) - buf;
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "quic mask: %*s", m, buf);
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "quic clear flags: %xi", clearflags);
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "quic packet number: %uL, len: %xi", pn, pnl);
+    }
+#endif
+
+// packet protection
+
+    ngx_str_t in;
+    in.data = p;
+    in.len = plen - pnl;
+
+    ngx_str_t ad;
+    ad.len = p - b;
+    ad.data = ngx_pnalloc(c->pool, ad.len);
+    if (ad.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(ad.data, b, ad.len);
+    ad.data[0] = clearflags;
+    ad.data[ad.len - pnl] = (u_char)pn;
+
+    uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_hs.iv);
+    nonce[11] ^= pn;
+
+#if (NGX_DEBUG)
+    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
+        m = ngx_hex_dump(buf, nonce, 12) - buf;
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "quic nonce: %*s, len: %uz", m, buf, 12);
+
+        m = ngx_hex_dump(buf, ad.data, ad.len) - buf;
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "quic ad: %*s, len: %uz", m, buf, ad.len);
+    }
+#endif
+
+    u_char *name = (u_char *) SSL_get_cipher(c->ssl->connection);
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "quic ssl cipher: %s", name);
+
+    if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0
+        || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0)
+    {
+        cipher = EVP_aes_128_gcm();
+
+    } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) {
+        cipher = EVP_aes_256_gcm();
+
+    } else {
+        ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher");
+        return NGX_ERROR;
+    }
+
+    if (ngx_quic_tls_open(c, cipher, &qc->client_hs, &out, nonce, &in, &ad)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+#if (NGX_DEBUG)
+    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
+        m = ngx_hex_dump(buf, out.data, ngx_min(out.len, 256)) - buf;
+        ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "quic packet payload: %*s%s, len: %uz",
+                       m, buf, m < 512 ? "" : "...", out.len);
+    }
+#endif
+
+    return NGX_OK;
+}
 
 
 uint64_t
@@ -53,7 +1078,7 @@ ngx_quic_build_int(u_char **pos, uint64_
 }
 
 
-uint64_t
+static uint64_t
 ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask)
 {
     u_char      *p;
@@ -71,7 +1096,7 @@ ngx_quic_parse_pn(u_char **pos, ngx_int_
 }
 
 
-ngx_int_t
+static ngx_int_t
 ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest,
     const u_char *secret, size_t secret_len, const u_char *salt,
     size_t salt_len)
@@ -119,7 +1144,7 @@ ngx_hkdf_extract(u_char *out_key, size_t
 }
 
 
-ngx_int_t
+static ngx_int_t
 ngx_quic_hkdf_expand(ngx_connection_t *c, const EVP_MD *digest, ngx_str_t *out,
     ngx_str_t *label, const uint8_t *prk, size_t prk_len)
 {
@@ -168,7 +1193,7 @@ ngx_quic_hkdf_expand(ngx_connection_t *c
 }
 
 
-ngx_int_t
+static ngx_int_t
 ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest,
     const uint8_t *prk, size_t prk_len, const u_char *info, size_t info_len)
 {
@@ -214,7 +1239,7 @@ ngx_hkdf_expand(u_char *out_key, size_t 
 }
 
 
-ngx_int_t
+static ngx_int_t
 ngx_quic_tls_open(ngx_connection_t *c, const EVP_CIPHER *cipher,
     ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in,
     ngx_str_t *ad)
@@ -319,7 +1344,7 @@ ngx_quic_tls_open(ngx_connection_t *c, c
 }
 
 
-ngx_int_t
+static ngx_int_t
 ngx_quic_tls_seal(ngx_connection_t *c, const EVP_CIPHER *cipher,
     ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in,
     ngx_str_t *ad)
@@ -422,7 +1447,7 @@ ngx_quic_tls_seal(ngx_connection_t *c, c
 }
 
 
-ngx_int_t
+static ngx_int_t
 ngx_quic_tls_hp(ngx_connection_t *c, const EVP_CIPHER *cipher,
     ngx_quic_secret_t *s, u_char *out, u_char *in)
 {
--- a/src/event/ngx_event_quic.h
+++ b/src/event/ngx_event_quic.h
@@ -1,6 +1,6 @@
 
 /*
- *
+ * Copyright (C) Nginx, Inc.
  */
 
 
@@ -10,95 +10,11 @@
 
 #include <ngx_event_openssl.h>
 
-#define quic_version 0xff000018
-
-
-typedef struct {
-    ngx_str_t          secret;
-    ngx_str_t          key;
-    ngx_str_t          iv;
-    ngx_str_t          hp;
-} ngx_quic_secret_t;
-
+/* TODO: get rid somehow of ssl argument? */
+ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b);
+ngx_int_t ngx_quic_output(ngx_connection_t *c);
 
-struct ngx_quic_connection_s {
-    ngx_str_t          scid;
-    ngx_str_t          dcid;
-    ngx_str_t          token;
-
-    ngx_quic_secret_t  client_in;
-    ngx_quic_secret_t  client_hs;
-    ngx_quic_secret_t  client_ad;
-    ngx_quic_secret_t  server_in;
-    ngx_quic_secret_t  server_hs;
-    ngx_quic_secret_t  server_ad;
-};
+void ngx_quic_init_ssl_methods(SSL_CTX* ctx);
 
 
-uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask);
-uint64_t ngx_quic_parse_int(u_char **pos);
-void ngx_quic_build_int(u_char **pos, uint64_t value);
-
-ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len,
-    const EVP_MD *digest, const u_char *secret, size_t secret_len,
-    const u_char *salt, size_t salt_len);
-ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len,
-    const EVP_MD *digest, const u_char *prk, size_t prk_len,
-    const u_char *info, size_t info_len);
-
-ngx_int_t ngx_quic_hkdf_expand(ngx_connection_t *c, const EVP_MD *digest,
-    ngx_str_t *out, ngx_str_t *label, const uint8_t *prk, size_t prk_len);
-
-ngx_int_t ngx_quic_tls_open(ngx_connection_t *c,
-    const EVP_CIPHER *cipher, ngx_quic_secret_t *s, ngx_str_t *out,
-    u_char *nonce, ngx_str_t *in, ngx_str_t *ad);
-ngx_int_t ngx_quic_tls_seal(ngx_connection_t *c,
-    const EVP_CIPHER *cipher, ngx_quic_secret_t *s, ngx_str_t *out,
-    u_char *nonce, ngx_str_t *in, ngx_str_t *ad);
-
-ngx_int_t
-ngx_quic_tls_hp(ngx_connection_t *c, const EVP_CIPHER *cipher,
-    ngx_quic_secret_t *s, u_char *out, u_char *in);
-
-
-#if (NGX_HAVE_NONALIGNED)
-
-#define ngx_quic_parse_uint16(p)  ntohs(*(uint16_t *) (p))
-#define ngx_quic_parse_uint32(p)  ntohl(*(uint32_t *) (p))
-
-#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])
-
-#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))
-
-#if (NGX_HAVE_NONALIGNED)
-
-#define ngx_quic_write_uint16  ngx_quic_write_uint16_aligned
-#define ngx_quic_write_uint32  ngx_quic_write_uint32_aligned
-
-#else
-
-#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
-
 #endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */
--- a/src/http/ngx_http_request.c
+++ b/src/http/ngx_http_request.c
@@ -661,402 +661,22 @@ ngx_http_alloc_request(ngx_connection_t 
 static void
 ngx_http_quic_handshake(ngx_event_t *rev)
 {
-    int                       n, sslerr;
-#if (NGX_DEBUG)
-    u_char                    buf[512];
-    size_t                    m;
-#endif
-    ngx_buf_t                *b;
     ngx_connection_t         *c;
     ngx_http_connection_t    *hc;
-    ngx_quic_connection_t    *qc;
     ngx_http_ssl_srv_conf_t  *sscf;
 
     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic handshake");
 
     c = rev->data;
     hc = c->data;
-    b = c->buffer;
-
-    if ((b->pos[0] & 0xf0) != 0xc0) {
-        ngx_log_error(NGX_LOG_INFO, rev->log, 0, "invalid initial packet");
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    if (ngx_buf_size(b) < 1200) {
-        ngx_log_error(NGX_LOG_INFO, rev->log, 0, "too small UDP datagram");
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    ngx_int_t flags = *b->pos++;
-    uint32_t version = ngx_quic_parse_uint32(b->pos);
-    b->pos += sizeof(uint32_t);
-
-    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                   "quic flags:%xi version:%xD", flags, version);
-
-    if (version != quic_version) {
-        ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unsupported quic version");
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t));
-    if (qc == NULL) {
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    c->quic = qc;
-
-    qc->dcid.len = *b->pos++;
-    qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len);
-    if (qc->dcid.data == NULL) {
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    ngx_memcpy(qc->dcid.data, b->pos, qc->dcid.len);
-    b->pos += qc->dcid.len;
-
-    qc->scid.len = *b->pos++;
-    qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len);
-    if (qc->scid.data == NULL) {
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    ngx_memcpy(qc->scid.data, b->pos, qc->scid.len);
-    b->pos += qc->scid.len;
-
-    qc->token.len = ngx_quic_parse_int(&b->pos);
-    qc->token.data = ngx_pnalloc(c->pool, qc->token.len);
-    if (qc->token.data == NULL) {
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    ngx_memcpy(qc->token.data, b->pos, qc->token.len);
-    b->pos += qc->token.len;
-
-    ngx_int_t plen = ngx_quic_parse_int(&b->pos);
-
-    if (plen > b->last - b->pos) {
-        ngx_log_error(NGX_LOG_INFO, rev->log, 0, "truncated initial packet");
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    /* draft-ietf-quic-tls-23#section-5.4.2:
-     * the Packet Number field is assumed to be 4 bytes long
-     * draft-ietf-quic-tls-23#section-5.4.[34]:
-     * AES-Based and ChaCha20-Based header protections sample 16 bytes
-     */
-    u_char *sample = b->pos + 4;
-
-#if (NGX_DEBUG)
-    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
-        m = ngx_hex_dump(buf, qc->dcid.data, qc->dcid.len) - buf;
-        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                       "quic DCID: %*s, len: %uz", m, buf, qc->dcid.len);
-
-        m = ngx_hex_dump(buf, qc->scid.data, qc->scid.len) - buf;
-        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                       "quic SCID: %*s, len: %uz", m, buf, qc->scid.len);
-
-        m = ngx_hex_dump(buf, qc->token.data, qc->token.len) - buf;
-        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                       "quic token: %*s, len: %uz", m, buf, qc->token.len);
-
-        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                       "quic packet length: %d", plen);
-
-        m = ngx_hex_dump(buf, sample, 16) - buf;
-        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                       "quic sample: %*s", m, buf);
-    }
-#endif
-
-// initial secret
-
-    size_t                    is_len;
-    uint8_t                   is[SHA256_DIGEST_LENGTH];
-    ngx_uint_t                i;
-    const EVP_MD             *digest;
-    const EVP_CIPHER         *cipher;
-    static const uint8_t salt[20] =
-        "\xc3\xee\xf7\x12\xc7\x2e\xbb\x5a\x11\xa7"
-        "\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02";
-
-    /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */
-
-    cipher = EVP_aes_128_gcm();
-    digest = EVP_sha256();
-
-    if (ngx_hkdf_extract(is, &is_len, digest, qc->dcid.data, qc->dcid.len,
-                         salt, sizeof(salt))
-        != NGX_OK)
-    {
+
+    sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module);
+
+    if (ngx_quic_input(c, &sscf->ssl, c->buffer) != NGX_OK) {
         ngx_http_close_connection(c);
         return;
     }
 
-    ngx_str_t iss = {
-        .data = is,
-        .len = is_len
-    };
-
-#if (NGX_DEBUG)
-    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
-        m = ngx_hex_dump(buf, (uint8_t *) salt, sizeof(salt)) - buf;
-        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                       "quic salt: %*s, len: %uz", m, buf, sizeof(salt));
-
-        m = ngx_hex_dump(buf, is, is_len) - buf;
-        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                       "quic initial secret: %*s, len: %uz", m, buf, is_len);
-    }
-#endif
-
-    /* draft-ietf-quic-tls-23#section-5.2 */
-    qc->client_in.secret.len = SHA256_DIGEST_LENGTH;
-    qc->server_in.secret.len = SHA256_DIGEST_LENGTH;
-
-    qc->client_in.key.len = EVP_CIPHER_key_length(cipher);
-    qc->server_in.key.len = EVP_CIPHER_key_length(cipher);
-
-    qc->client_in.hp.len = EVP_CIPHER_key_length(cipher);
-    qc->server_in.hp.len = EVP_CIPHER_key_length(cipher);
-
-    qc->client_in.iv.len = EVP_CIPHER_iv_length(cipher);
-    qc->server_in.iv.len = EVP_CIPHER_iv_length(cipher);
-
-    struct {
-        ngx_str_t   label;
-        ngx_str_t  *key;
-        ngx_str_t  *prk;
-    } seq[] = {
-
-        /* draft-ietf-quic-tls-23#section-5.2 */
-        { ngx_string("tls13 client in"), &qc->client_in.secret, &iss },
-        {
-            ngx_string("tls13 quic key"),
-            &qc->client_in.key,
-            &qc->client_in.secret,
-        },
-        {
-            ngx_string("tls13 quic iv"),
-            &qc->client_in.iv,
-            &qc->client_in.secret,
-        },
-        {
-            /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */
-            ngx_string("tls13 quic hp"),
-            &qc->client_in.hp,
-            &qc->client_in.secret,
-        },
-        { ngx_string("tls13 server in"), &qc->server_in.secret, &iss },
-        {
-            /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */
-            ngx_string("tls13 quic key"),
-            &qc->server_in.key,
-            &qc->server_in.secret,
-        },
-        {
-            ngx_string("tls13 quic iv"),
-            &qc->server_in.iv,
-            &qc->server_in.secret,
-        },
-        {
-           /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */
-            ngx_string("tls13 quic hp"),
-            &qc->server_in.hp,
-            &qc->server_in.secret,
-        },
-
-    };
-
-    for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) {
-
-        if (ngx_quic_hkdf_expand(c, digest, seq[i].key, &seq[i].label,
-                                 seq[i].prk->data, seq[i].prk->len)
-            != NGX_OK)
-        {
-            ngx_http_close_connection(c);
-            return;
-        }
-    }
-
-// header protection
-
-    uint8_t mask[16];
-    if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), &qc->client_in, mask, sample)
-        != NGX_OK)
-    {
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    u_char  clearflags = flags ^ (mask[0] & 0x0f);
-    ngx_int_t  pnl = (clearflags & 0x03) + 1;
-    uint64_t  pn = ngx_quic_parse_pn(&b->pos, pnl, &mask[1]);
-
-#if (NGX_DEBUG)
-    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
-        m = ngx_hex_dump(buf, sample, 16) - buf;
-        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                       "quic sample: %*s", m, buf);
-
-        m = ngx_hex_dump(buf, mask, 5) - buf;
-        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                       "quic mask: %*s", m, buf);
-
-        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                       "quic packet number: %uL, len: %xi", pn, pnl);
-    }
-#endif
-
-// packet protection
-
-    ngx_str_t in;
-    in.data = b->pos;
-    in.len = plen - pnl;
-
-    ngx_str_t ad;
-    ad.len = b->pos - b->start;
-    ad.data = ngx_pnalloc(c->pool, ad.len);
-    if (ad.data == NULL) {
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    ngx_memcpy(ad.data, b->start, ad.len);
-    ad.data[0] = clearflags;
-    ad.data[ad.len - pnl] = (u_char)pn;
-
-    uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_in.iv);
-    nonce[11] ^= pn;
-
-#if (NGX_DEBUG)
-    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
-        m = ngx_hex_dump(buf, nonce, 12) - buf;
-        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                       "quic nonce: %*s, len: %uz", m, buf, 12);
-
-        m = ngx_hex_dump(buf, ad.data, ad.len) - buf;
-        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                       "quic ad: %*s, len: %uz", m, buf, ad.len);
-    }
-#endif
-
-    ngx_str_t  out;
-
-    if (ngx_quic_tls_open(c, EVP_aes_128_gcm(), &qc->client_in, &out, nonce,
-                          &in, &ad)
-        != NGX_OK)
-    {
-        ngx_http_close_connection(c);
-        return;
-    }
-
-#if (NGX_DEBUG)
-    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
-        m = ngx_hex_dump(buf, out.data, ngx_min(out.len, 256)) - buf;
-        ngx_log_debug4(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                       "quic packet payload: %*s%s, len: %uz",
-                       m, buf, m < 512 ? "" : "...", out.len);
-    }
-#endif
-
-    if (out.data[0] != 0x06) {
-        ngx_log_error(NGX_LOG_INFO, rev->log, 0,
-                      "unexpected frame in initial packet");
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    if (out.data[1] != 0x00) {
-        ngx_log_error(NGX_LOG_INFO, rev->log, 0,
-                      "unexpected CRYPTO offset in initial packet");
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    uint8_t *crypto = &out.data[2];
-    uint64_t crypto_len = ngx_quic_parse_int(&crypto);
-
-    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                   "quic initial packet CRYPTO length: %uL pp:%p:%p",
-                   crypto_len, out.data, crypto);
-
-    sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module);
-
-    if (ngx_ssl_create_connection(&sscf->ssl, c, NGX_SSL_BUFFER)
-        != NGX_OK)
-    {
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    static const uint8_t params[12] = "\x00\x0a\x00\x3a\x00\x01\x00\x00\x09\x00\x01\x03";
-
-    if (SSL_set_quic_transport_params(c->ssl->connection, params,
-                                      sizeof(params)) == 0)
-    {
-        ngx_log_error(NGX_LOG_INFO, rev->log, 0,
-                      "SSL_set_quic_transport_params() failed");
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    n = SSL_do_handshake(c->ssl->connection);
-
-    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n);
-
-    if (n == -1) {
-        sslerr = SSL_get_error(c->ssl->connection, n);
-
-        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d",
-                       sslerr);
-    }
-
-    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(c->ssl->connection),
-                   (int) SSL_quic_write_level(c->ssl->connection));
-
-    if (!SSL_provide_quic_data(c->ssl->connection,
-                               SSL_quic_read_level(c->ssl->connection),
-                               crypto, crypto_len))
-    {
-        ngx_ssl_error(NGX_LOG_INFO, rev->log, 0,
-                      "SSL_provide_quic_data() failed");
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    n = SSL_do_handshake(c->ssl->connection);
-
-    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n);
-
-    if (n == -1) {
-        sslerr = SSL_get_error(c->ssl->connection, 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_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "SSL_quic_read_level: %d, SSL_quic_write_level: %d",
-                   (int) SSL_quic_read_level(c->ssl->connection),
-                   (int) SSL_quic_write_level(c->ssl->connection));
-
     if (!rev->timer_set) {
         ngx_add_timer(rev, c->listening->post_accept_timeout);
     }
@@ -1069,17 +689,16 @@ ngx_http_quic_handshake(ngx_event_t *rev
 static void
 ngx_http_quic_handshake_handler(ngx_event_t *rev)
 {
-    size_t                  m;
     ssize_t                 n;
-    ngx_str_t               out;
     ngx_connection_t       *c;
-    const EVP_CIPHER       *cipher;
-    ngx_quic_connection_t  *qc;
-    u_char                  buf[4096], b[512], *p;
+    u_char                  buf[512];
+    ngx_buf_t               b;
+
+    b.start = buf;
+    b.end = buf + 512;
+    b.pos = b.last = b.start;
 
     c = rev->data;
-    qc = c->quic;
-    p = b;
 
     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic handshake handler");
 
@@ -1094,7 +713,7 @@ ngx_http_quic_handshake_handler(ngx_even
         return;
     }
 
-    n = c->recv(c, b, sizeof(b));
+    n = c->recv(c, b.start, b.end - b.start);
 
     if (n == NGX_AGAIN) {
         return;
@@ -1106,166 +725,12 @@ ngx_http_quic_handshake_handler(ngx_even
         return;
     }
 
-    m = ngx_hex_dump(buf, b, n) - buf;
-    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                   "quic handshake handler: %*s, len: %uz", m, buf, n);
-
-    if ((p[0] & 0xf0) != 0xe0) {
-        ngx_log_error(NGX_LOG_INFO, rev->log, 0, "invalid packet type");
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    ngx_int_t flags = *p++;
-    uint32_t version = ngx_quic_parse_uint32(p);
-    p += sizeof(uint32_t);
-
-    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                   "quic flags:%xi version:%xD", flags, version);
-
-    if (version != quic_version) {
-        ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unsupported quic version");
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    if (*p++ != qc->dcid.len) {
-        ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unexpected quic dcidl");
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    if (ngx_memcmp(p, qc->dcid.data, qc->dcid.len) != 0) {
-        ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unexpected quic dcid");
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    p += qc->dcid.len;
-
-    if (*p++ != qc->scid.len) {
-        ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unexpected quic scidl");
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    if (ngx_memcmp(p, qc->scid.data, qc->scid.len) != 0) {
-        ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unexpected quic scid");
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    p += qc->scid.len;
-
-    ngx_int_t plen = ngx_quic_parse_int(&p);
-
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                   "quic packet length: %d", plen);
-
-    if (plen > b + n - p) {
-        ngx_log_error(NGX_LOG_INFO, rev->log, 0, "truncated handshake packet");
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    u_char *sample = p + 4;
-
-    m = ngx_hex_dump(buf, sample, 16) - buf;
-    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic sample: %*s", m, buf);
-
-// header protection
-
-    uint8_t mask[16];
-    if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), &qc->client_hs, mask, sample)
-        != NGX_OK)
-    {
+    b.last += n;
+
+    if (ngx_quic_input(c, NULL, &b) != NGX_OK) {
         ngx_http_close_connection(c);
         return;
     }
-
-    u_char  clearflags = flags ^ (mask[0] & 0x0f);
-    ngx_int_t  pnl = (clearflags & 0x03) + 1;
-    uint64_t  pn = ngx_quic_parse_pn(&p, pnl, &mask[1]);
-
-#if (NGX_DEBUG)
-    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
-        m = ngx_hex_dump(buf, mask, 5) - buf;
-        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                       "quic mask: %*s", m, buf);
-        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                       "quic clear flags: %xi", clearflags);
-        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                       "quic packet number: %uL, len: %xi", pn, pnl);
-    }
-#endif
-
-// packet protection
-
-    ngx_str_t in;
-    in.data = p;
-    in.len = plen - pnl;
-
-    ngx_str_t ad;
-    ad.len = p - b;
-    ad.data = ngx_pnalloc(c->pool, ad.len);
-    if (ad.data == NULL) {
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    ngx_memcpy(ad.data, b, ad.len);
-    ad.data[0] = clearflags;
-    ad.data[ad.len - pnl] = (u_char)pn;
-
-    uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_hs.iv);
-    nonce[11] ^= pn;
-
-#if (NGX_DEBUG)
-    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
-        m = ngx_hex_dump(buf, nonce, 12) - buf;
-        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                       "quic nonce: %*s, len: %uz", m, buf, 12);
-
-        m = ngx_hex_dump(buf, ad.data, ad.len) - buf;
-        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                       "quic ad: %*s, len: %uz", m, buf, ad.len);
-    }
-#endif
-
-    u_char *name = (u_char *) SSL_get_cipher(c->ssl->connection);
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                   "quic ssl cipher: %s", name);
-
-    if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0
-        || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0)
-    {
-        cipher = EVP_aes_128_gcm();
-
-    } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) {
-        cipher = EVP_aes_256_gcm();
-
-    } else {
-        ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "unexpected cipher");
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    if (ngx_quic_tls_open(c, cipher, &qc->client_hs, &out, nonce, &in, &ad)
-        != NGX_OK)
-    {
-        ngx_http_close_connection(c);
-        return;
-    }
-
-#if (NGX_DEBUG)
-    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
-        m = ngx_hex_dump(buf, out.data, ngx_min(out.len, 256)) - buf;
-        ngx_log_debug4(NGX_LOG_DEBUG_HTTP, rev->log, 0,
-                       "quic packet payload: %*s%s, len: %uz",
-                       m, buf, m < 512 ? "" : "...", out.len);
-    }
-#endif
-
 }