From 43327f1aa33d7b4a072445bbf9e4603173fbde5e Mon Sep 17 00:00:00 2001
From: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
Date: Thu, 24 May 2018 18:11:27 +0900
Subject: [PATCH] Packet number encryption

---
 examples/client.cc           | 125 ++++++++++--
 examples/client.h            |   7 +
 examples/crypto.cc           |  21 ++
 examples/crypto.h            |  21 ++
 examples/crypto_openssl.cc   |  44 ++++-
 examples/debug.cc            |   8 +
 examples/debug.h             |   3 +
 examples/server.cc           | 125 ++++++++++--
 examples/server.h            |   7 +
 lib/includes/ngtcp2/ngtcp2.h |  79 ++++----
 lib/ngtcp2_conn.c            | 366 +++++++++++++++++++++--------------
 lib/ngtcp2_conn.h            |   3 -
 lib/ngtcp2_conv.c            |  61 ++++++
 lib/ngtcp2_conv.h            |  19 ++
 lib/ngtcp2_crypto.c          |   9 +-
 lib/ngtcp2_crypto.h          |   9 +-
 lib/ngtcp2_log.c             |  22 +--
 lib/ngtcp2_pkt.c             | 117 ++++-------
 lib/ngtcp2_pkt.h             |  17 +-
 lib/ngtcp2_ppe.c             |  51 +++--
 lib/ngtcp2_ppe.h             |  12 +-
 tests/ngtcp2_conn_test.c     | 157 +++++++++------
 tests/ngtcp2_pkt_test.c      | 103 +++++-----
 tests/ngtcp2_rtb_test.c      |  32 +--
 tests/ngtcp2_test_helper.c   |  91 ++++++++-
 tests/ngtcp2_test_helper.h   |  24 +++
 26 files changed, 1065 insertions(+), 468 deletions(-)

diff --git a/examples/client.cc b/examples/client.cc
index 5a071e61..ecf7b66a 100644
--- a/examples/client.cc
+++ b/examples/client.cc
@@ -335,21 +335,16 @@ void Client::close() {
 
 namespace {
 ssize_t send_client_initial(ngtcp2_conn *conn, uint32_t flags,
-                            uint64_t *ppkt_num, const uint8_t **pdest,
+                            const uint8_t **pdest, int initial,
                             void *user_data) {
   auto c = static_cast<Client *>(user_data);
 
-  if (c->tls_handshake(ppkt_num) != 0) {
+  if (c->tls_handshake(initial) != 0) {
     return NGTCP2_ERR_CALLBACK_FAILURE;
   }
 
   c->handle_early_data();
 
-  if (ppkt_num) {
-    *ppkt_num = std::uniform_int_distribution<uint64_t>(
-        0, NGTCP2_MAX_INITIAL_PKT_NUM)(randgen);
-  }
-
   auto len = c->read_client_handshake(pdest);
 
   return len;
@@ -539,6 +534,41 @@ ssize_t do_decrypt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
 }
 } // namespace
 
+namespace {
+ssize_t do_hs_encrypt_pn(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
+                         const uint8_t *plaintext, size_t plaintextlen,
+                         const uint8_t *key, size_t keylen,
+                         const uint8_t *nonce, size_t noncelen,
+                         void *user_data) {
+  auto c = static_cast<Client *>(user_data);
+
+  auto nwrite = c->hs_encrypt_pn(dest, destlen, plaintext, plaintextlen, key,
+                                 keylen, nonce, noncelen);
+  if (nwrite < 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return nwrite;
+}
+} // namespace
+
+namespace {
+ssize_t do_encrypt_pn(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
+                      const uint8_t *plaintext, size_t plaintextlen,
+                      const uint8_t *key, size_t keylen, const uint8_t *nonce,
+                      size_t noncelen, void *user_data) {
+  auto c = static_cast<Client *>(user_data);
+
+  auto nwrite = c->encrypt_pn(dest, destlen, plaintext, plaintextlen, key,
+                              keylen, nonce, noncelen);
+  if (nwrite < 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return nwrite;
+}
+} // namespace
+
 int Client::init(int fd, const Address &remote_addr, const char *addr,
                  int datafd, uint32_t version) {
   int rv;
@@ -626,6 +656,8 @@ int Client::init(int fd, const Address &remote_addr, const char *addr,
       do_hs_decrypt,
       do_encrypt,
       do_decrypt,
+      do_hs_encrypt_pn,
+      do_encrypt_pn,
       recv_stream_data,
       acked_stream_data_offset,
       stream_close,
@@ -709,7 +741,7 @@ int Client::setup_handshake_crypto_context() {
     return -1;
   }
 
-  std::array<uint8_t, 16> key, iv;
+  std::array<uint8_t, 16> key, iv, pn;
 
   auto keylen = crypto::derive_packet_protection_key(
       key.data(), key.size(), secret.data(), secret.size(), hs_crypto_ctx_);
@@ -723,14 +755,21 @@ int Client::setup_handshake_crypto_context() {
     return -1;
   }
 
+  auto pnlen = crypto::derive_pkt_num_protection_key(
+      pn.data(), pn.size(), secret.data(), secret.size(), hs_crypto_ctx_);
+  if (pnlen < 0) {
+    return -1;
+  }
+
   if (!config.quiet && config.show_secret) {
     debug::print_client_hs_secret(secret.data(), secret.size());
     debug::print_client_pp_key(key.data(), keylen);
     debug::print_client_pp_iv(iv.data(), ivlen);
+    debug::print_client_pp_pn(pn.data(), pnlen);
   }
 
-  ngtcp2_conn_set_handshake_tx_keys(conn_, key.data(), keylen, iv.data(),
-                                    ivlen);
+  ngtcp2_conn_set_handshake_tx_keys(conn_, key.data(), keylen, iv.data(), ivlen,
+                                    pn.data(), pnlen);
 
   rv = crypto::derive_server_handshake_secret(secret.data(), secret.size(),
                                               handshake_secret.data(),
@@ -752,14 +791,21 @@ int Client::setup_handshake_crypto_context() {
     return -1;
   }
 
+  pnlen = crypto::derive_pkt_num_protection_key(
+      pn.data(), pn.size(), secret.data(), secret.size(), hs_crypto_ctx_);
+  if (pnlen < 0) {
+    return -1;
+  }
+
   if (!config.quiet && config.show_secret) {
     debug::print_server_hs_secret(secret.data(), secret.size());
     debug::print_server_pp_key(key.data(), keylen);
     debug::print_server_pp_iv(iv.data(), ivlen);
+    debug::print_server_pp_pn(pn.data(), pnlen);
   }
 
-  ngtcp2_conn_set_handshake_rx_keys(conn_, key.data(), keylen, iv.data(),
-                                    ivlen);
+  ngtcp2_conn_set_handshake_rx_keys(conn_, key.data(), keylen, iv.data(), ivlen,
+                                    pn.data(), pnlen);
 
   return 0;
 }
@@ -893,6 +939,8 @@ ssize_t Client::do_handshake_once(const uint8_t *data, size_t datalen) {
   if (nwrite < 0) {
     switch (nwrite) {
     case NGTCP2_ERR_TLS_DECRYPT:
+      std::cerr << "ngtcp2_conn_handshake: " << ngtcp2_strerror(nwrite)
+                << std::endl;
     case NGTCP2_ERR_NOBUF:
       return 0;
     }
@@ -1255,7 +1303,7 @@ int Client::setup_early_crypto_context() {
     return -1;
   }
 
-  std::array<uint8_t, 64> key, iv;
+  std::array<uint8_t, 64> key, iv, pn;
 
   auto keylen = crypto::derive_packet_protection_key(
       key.data(), key.size(), crypto_ctx_.tx_secret.data(),
@@ -1271,14 +1319,23 @@ int Client::setup_early_crypto_context() {
     return -1;
   }
 
+  auto pnlen = crypto::derive_pkt_num_protection_key(
+      pn.data(), pn.size(), crypto_ctx_.tx_secret.data(), crypto_ctx_.secretlen,
+      crypto_ctx_);
+  if (pnlen < 0) {
+    return -1;
+  }
+
   if (!config.quiet && config.show_secret) {
     debug::print_client_0rtt_secret(crypto_ctx_.tx_secret.data(),
                                     crypto_ctx_.secretlen);
     debug::print_client_pp_key(key.data(), keylen);
     debug::print_client_pp_iv(iv.data(), ivlen);
+    debug::print_client_pp_pn(pn.data(), pnlen);
   }
 
-  ngtcp2_conn_update_early_keys(conn_, key.data(), keylen, iv.data(), ivlen);
+  ngtcp2_conn_update_early_keys(conn_, key.data(), keylen, iv.data(), ivlen,
+                                pn.data(), pnlen);
 
   ngtcp2_conn_set_aead_overhead(conn_, crypto::aead_max_overhead(crypto_ctx_));
 
@@ -1307,7 +1364,7 @@ int Client::setup_crypto_context() {
     return -1;
   }
 
-  std::array<uint8_t, 64> key{}, iv{};
+  std::array<uint8_t, 64> key, iv, pn;
 
   auto keylen = crypto::derive_packet_protection_key(
       key.data(), key.size(), crypto_ctx_.tx_secret.data(),
@@ -1323,14 +1380,23 @@ int Client::setup_crypto_context() {
     return -1;
   }
 
+  auto pnlen = crypto::derive_pkt_num_protection_key(
+      pn.data(), pn.size(), crypto_ctx_.tx_secret.data(), crypto_ctx_.secretlen,
+      crypto_ctx_);
+  if (pnlen < 0) {
+    return -1;
+  }
+
   if (!config.quiet && config.show_secret) {
     debug::print_client_1rtt_secret(crypto_ctx_.tx_secret.data(),
                                     crypto_ctx_.secretlen);
     debug::print_client_pp_key(key.data(), keylen);
     debug::print_client_pp_iv(iv.data(), ivlen);
+    debug::print_client_pp_pn(pn.data(), pnlen);
   }
 
-  ngtcp2_conn_update_tx_keys(conn_, key.data(), keylen, iv.data(), ivlen);
+  ngtcp2_conn_update_tx_keys(conn_, key.data(), keylen, iv.data(), ivlen,
+                             pn.data(), pnlen);
 
   rv = crypto::export_server_secret(crypto_ctx_.rx_secret.data(),
                                     crypto_ctx_.secretlen, ssl_);
@@ -1352,14 +1418,23 @@ int Client::setup_crypto_context() {
     return -1;
   }
 
+  pnlen = crypto::derive_pkt_num_protection_key(
+      pn.data(), pn.size(), crypto_ctx_.rx_secret.data(), crypto_ctx_.secretlen,
+      crypto_ctx_);
+  if (pnlen < 0) {
+    return -1;
+  }
+
   if (!config.quiet && config.show_secret) {
     debug::print_server_1rtt_secret(crypto_ctx_.rx_secret.data(),
                                     crypto_ctx_.secretlen);
     debug::print_server_pp_key(key.data(), keylen);
     debug::print_server_pp_iv(iv.data(), ivlen);
+    debug::print_server_pp_pn(pn.data(), pnlen);
   }
 
-  ngtcp2_conn_update_rx_keys(conn_, key.data(), keylen, iv.data(), ivlen);
+  ngtcp2_conn_update_rx_keys(conn_, key.data(), keylen, iv.data(), ivlen,
+                             pn.data(), pnlen);
 
   ngtcp2_conn_set_aead_overhead(conn_, crypto::aead_max_overhead(crypto_ctx_));
 
@@ -1403,6 +1478,22 @@ ssize_t Client::decrypt_data(uint8_t *dest, size_t destlen,
                          key, keylen, nonce, noncelen, ad, adlen);
 }
 
+ssize_t Client::hs_encrypt_pn(uint8_t *dest, size_t destlen,
+                              const uint8_t *ciphertext, size_t ciphertextlen,
+                              const uint8_t *key, size_t keylen,
+                              const uint8_t *nonce, size_t noncelen) {
+  return crypto::encrypt_pn(dest, destlen, ciphertext, ciphertextlen,
+                            hs_crypto_ctx_, key, keylen, nonce, noncelen);
+}
+
+ssize_t Client::encrypt_pn(uint8_t *dest, size_t destlen,
+                           const uint8_t *ciphertext, size_t ciphertextlen,
+                           const uint8_t *key, size_t keylen,
+                           const uint8_t *nonce, size_t noncelen) {
+  return crypto::encrypt_pn(dest, destlen, ciphertext, ciphertextlen,
+                            crypto_ctx_, key, keylen, nonce, noncelen);
+}
+
 ngtcp2_conn *Client::conn() const { return conn_; }
 
 int Client::send_packet() {
diff --git a/examples/client.h b/examples/client.h
index a69dd783..23bf63d6 100644
--- a/examples/client.h
+++ b/examples/client.h
@@ -182,6 +182,13 @@ public:
                        size_t ciphertextlen, const uint8_t *key, size_t keylen,
                        const uint8_t *nonce, size_t noncelen, const uint8_t *ad,
                        size_t adlen);
+  ssize_t hs_encrypt_pn(uint8_t *data, size_t destlen,
+                        const uint8_t *ciphertext, size_t ciphertextlen,
+                        const uint8_t *key, size_t keylen, const uint8_t *nonce,
+                        size_t noncelen);
+  ssize_t encrypt_pn(uint8_t *data, size_t destlen, const uint8_t *ciphertext,
+                     size_t ciphertextlen, const uint8_t *key, size_t keylen,
+                     const uint8_t *nonce, size_t noncelen);
   ngtcp2_conn *conn() const;
   int send_packet();
   int start_interactive_input();
diff --git a/examples/crypto.cc b/examples/crypto.cc
index c961d99c..7ad7d2aa 100644
--- a/examples/crypto.cc
+++ b/examples/crypto.cc
@@ -148,6 +148,27 @@ ssize_t derive_packet_protection_iv(uint8_t *dest, size_t destlen,
   return ivlen;
 }
 
+ssize_t derive_pkt_num_protection_key(uint8_t *dest, size_t destlen,
+                                      const uint8_t *secret, size_t secretlen,
+                                      const Context &ctx) {
+  int rv;
+  static constexpr uint8_t LABEL_PKNKEY[] = "pn";
+
+  auto keylen = aead_key_length(ctx);
+  if (keylen > destlen) {
+    return -1;
+  }
+
+  rv = crypto::qhkdf_expand(dest, keylen, secret, secretlen, LABEL_PKNKEY,
+                            str_size(LABEL_PKNKEY), ctx);
+
+  if (rv != 0) {
+    return -1;
+  }
+
+  return keylen;
+}
+
 int qhkdf_expand(uint8_t *dest, size_t destlen, const uint8_t *secret,
                  size_t secretlen, const uint8_t *qlabel, size_t qlabellen,
                  const Context &ctx) {
diff --git a/examples/crypto.h b/examples/crypto.h
index ddf36b18..615074e6 100644
--- a/examples/crypto.h
+++ b/examples/crypto.h
@@ -45,6 +45,7 @@ struct Context {
 #else  // !OPENSSL_IS_BORINGSSL
   const EVP_CIPHER *aead;
 #endif // !OPENSSL_IS_BORINGSSL
+  const EVP_CIPHER *pn;
   const EVP_MD *prf;
   std::array<uint8_t, 64> tx_secret, rx_secret;
   size_t secretlen;
@@ -110,6 +111,14 @@ ssize_t derive_packet_protection_iv(uint8_t *dest, size_t destlen,
                                     const uint8_t *secret, size_t secretlen,
                                     const Context &ctx);
 
+// derive_pkt_num_protection_key derives and stores the packet number
+// protection key in the buffer pointed by |dest| of length |destlen|,
+// and the key size is returned.  This function returns the key length
+// if it succeeds, or -1.
+ssize_t derive_pkt_num_protection_key(uint8_t *dest, size_t destlen,
+                                      const uint8_t *secret, size_t secretlen,
+                                      const Context &ctx);
+
 // encrypt encrypts |plaintext| of length |plaintextlen| and writes
 // the encrypted data in the buffer pointed by |dest| of length
 // |destlen|.  This function can encrypt data in-place.  In other
@@ -139,6 +148,18 @@ size_t aead_key_length(const Context &ctx);
 // aead_nonce_length returns the nonce size of ctx.aead.
 size_t aead_nonce_length(const Context &ctx);
 
+// encrypt_pn encrypts |plaintext| of length |plaintextlen| and writes
+// the encrypted data in the buffer pointed by |dest| of length
+// |destlen|.  This function can encrypt data in-place.  In other
+// words, |dest| == |plaintext| is allowed.  This function returns the
+// number of bytes written if it succeeds, or -1.
+//
+// Use this function to decrypt ciphertext.  Just pass ciphertext as
+// |plaintext|.
+ssize_t encrypt_pn(uint8_t *dest, size_t destlen, const uint8_t *plaintext,
+                   size_t plaintextlen, const Context &ctx, const uint8_t *key,
+                   size_t keylen, const uint8_t *nonce, size_t noncelen);
+
 // hkdf_expand performs HKDF-expand.  This function returns 0 if it
 // succeeds, or -1.
 int hkdf_expand(uint8_t *dest, size_t destlen, const uint8_t *secret,
diff --git a/examples/crypto_openssl.cc b/examples/crypto_openssl.cc
index 7050cad5..1495c1a2 100644
--- a/examples/crypto_openssl.cc
+++ b/examples/crypto_openssl.cc
@@ -55,12 +55,15 @@ int negotiated_aead(Context &ctx, SSL *ssl) {
   switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) {
   case 0x03001301u: // TLS_AES_128_GCM_SHA256
     ctx.aead = EVP_aes_128_gcm();
+    ctx.pn = EVP_aes_128_ctr();
     return 0;
   case 0x03001302u: // TLS_AES_256_GCM_SHA384
     ctx.aead = EVP_aes_256_gcm();
+    ctx.pn = EVP_aes_256_ctr();
     return 0;
   case 0x03001303u: // TLS_CHACHA20_POLY1305_SHA256
     ctx.aead = EVP_chacha20_poly1305();
+    ctx.pn = EVP_chacha20();
     return 0;
   default:
     return -1;
@@ -208,6 +211,42 @@ size_t aead_nonce_length(const Context &ctx) {
   return EVP_CIPHER_iv_length(ctx.aead);
 }
 
+ssize_t encrypt_pn(uint8_t *dest, size_t destlen, const uint8_t *plaintext,
+                   size_t plaintextlen, const Context &ctx, const uint8_t *key,
+                   size_t keylen, const uint8_t *nonce, size_t noncelen) {
+  auto actx = EVP_CIPHER_CTX_new();
+  if (actx == nullptr) {
+    return -1;
+  }
+
+  auto actx_d = defer(EVP_CIPHER_CTX_free, actx);
+
+  if (EVP_EncryptInit_ex(actx, ctx.pn, nullptr, key, nonce) != 1) {
+    return -1;
+  }
+
+  size_t outlen = 0;
+  int len;
+
+  if (EVP_EncryptUpdate(actx, dest, &len, plaintext, plaintextlen) != 1) {
+    return -1;
+  }
+
+  assert(len > 0);
+
+  outlen = len;
+
+  if (EVP_EncryptFinal_ex(actx, dest + outlen, &len) != 1) {
+    return -1;
+  }
+
+  assert(len == 0);
+
+  /* outlen += len; */
+
+  return outlen;
+}
+
 int hkdf_expand(uint8_t *dest, size_t destlen, const uint8_t *secret,
                 size_t secretlen, const uint8_t *info, size_t infolen,
                 const Context &ctx) {
@@ -288,7 +327,10 @@ int hkdf_extract(uint8_t *dest, size_t destlen, const uint8_t *secret,
 
 void prf_sha256(Context &ctx) { ctx.prf = EVP_sha256(); }
 
-void aead_aes_128_gcm(Context &ctx) { ctx.aead = EVP_aes_128_gcm(); }
+void aead_aes_128_gcm(Context &ctx) {
+  ctx.aead = EVP_aes_128_gcm();
+  ctx.pn = EVP_aes_128_ctr();
+}
 
 } // namespace crypto
 
diff --git a/examples/debug.cc b/examples/debug.cc
index 3fc7b634..c1b73e1b 100644
--- a/examples/debug.cc
+++ b/examples/debug.cc
@@ -108,6 +108,14 @@ void print_server_pp_iv(const uint8_t *data, size_t len) {
   fprintf(outfile, "+ server_pp_iv=%s\n", util::format_hex(data, len).c_str());
 }
 
+void print_client_pp_pn(const uint8_t *data, size_t len) {
+  fprintf(outfile, "+ client_pp_pn=%s\n", util::format_hex(data, len).c_str());
+}
+
+void print_server_pp_pn(const uint8_t *data, size_t len) {
+  fprintf(outfile, "+ server_pp_pn=%s\n", util::format_hex(data, len).c_str());
+}
+
 void log_printf(void *user_data, const char *fmt, ...) {
   va_list ap;
 
diff --git a/examples/debug.h b/examples/debug.h
index 3483722f..dd22a62d 100644
--- a/examples/debug.h
+++ b/examples/debug.h
@@ -65,6 +65,9 @@ void print_server_pp_key(const uint8_t *data, size_t len);
 void print_client_pp_iv(const uint8_t *data, size_t len);
 void print_server_pp_iv(const uint8_t *data, size_t len);
 
+void print_client_pp_pn(const uint8_t *data, size_t len);
+void print_server_pp_pn(const uint8_t *data, size_t len);
+
 void log_printf(void *user_data, const char *fmt, ...);
 
 } // namespace debug
diff --git a/examples/server.cc b/examples/server.cc
index f2b9d9e7..14f9556b 100644
--- a/examples/server.cc
+++ b/examples/server.cc
@@ -595,20 +595,15 @@ int recv_client_initial(ngtcp2_conn *conn, const ngtcp2_cid *dcid,
 
 namespace {
 ssize_t send_server_handshake(ngtcp2_conn *conn, uint32_t flags,
-                              uint64_t *ppkt_num, const uint8_t **pdest,
+                              const uint8_t **pdest, int initial,
                               void *user_data) {
   auto h = static_cast<Handler *>(user_data);
 
-  if (ppkt_num) {
-    *ppkt_num = std::uniform_int_distribution<uint64_t>(
-        0, NGTCP2_MAX_INITIAL_PKT_NUM)(randgen);
-  }
-
   auto len = h->read_server_handshake(pdest);
 
   // If Initial packet does not have complete ClientHello, then drop
   // connection.
-  if (ppkt_num && len == 0) {
+  if (initial && len == 0) {
     return NGTCP2_ERR_CALLBACK_FAILURE;
   }
 
@@ -702,6 +697,41 @@ ssize_t do_decrypt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
 }
 } // namespace
 
+namespace {
+ssize_t do_hs_encrypt_pn(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
+                         const uint8_t *plaintext, size_t plaintextlen,
+                         const uint8_t *key, size_t keylen,
+                         const uint8_t *nonce, size_t noncelen,
+                         void *user_data) {
+  auto h = static_cast<Handler *>(user_data);
+
+  auto nwrite = h->hs_encrypt_pn(dest, destlen, plaintext, plaintextlen, key,
+                                 keylen, nonce, noncelen);
+  if (nwrite < 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return nwrite;
+}
+} // namespace
+
+namespace {
+ssize_t do_encrypt_pn(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
+                      const uint8_t *plaintext, size_t plaintextlen,
+                      const uint8_t *key, size_t keylen, const uint8_t *nonce,
+                      size_t noncelen, void *user_data) {
+  auto h = static_cast<Handler *>(user_data);
+
+  auto nwrite = h->encrypt_pn(dest, destlen, plaintext, plaintextlen, key,
+                              keylen, nonce, noncelen);
+  if (nwrite < 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return nwrite;
+}
+} // namespace
+
 namespace {
 int recv_stream0_data(ngtcp2_conn *conn, uint64_t offset, const uint8_t *data,
                       size_t datalen, void *user_data) {
@@ -810,6 +840,8 @@ int Handler::init(int fd, const sockaddr *sa, socklen_t salen,
       do_hs_decrypt,
       do_encrypt,
       do_decrypt,
+      do_hs_encrypt_pn,
+      do_encrypt_pn,
       ::recv_stream_data,
       acked_stream_data_offset,
       stream_close,
@@ -1045,7 +1077,7 @@ int Handler::recv_client_initial(const ngtcp2_cid *dcid) {
     return -1;
   }
 
-  std::array<uint8_t, 16> key, iv;
+  std::array<uint8_t, 16> key, iv, pn;
   auto keylen = crypto::derive_packet_protection_key(
       key.data(), key.size(), secret.data(), secret.size(), hs_crypto_ctx_);
   if (keylen < 0) {
@@ -1058,14 +1090,21 @@ int Handler::recv_client_initial(const ngtcp2_cid *dcid) {
     return -1;
   }
 
+  auto pnlen = crypto::derive_pkt_num_protection_key(
+      pn.data(), pn.size(), secret.data(), secret.size(), hs_crypto_ctx_);
+  if (pnlen < 0) {
+    return -1;
+  }
+
   if (!config.quiet && config.show_secret) {
     debug::print_server_hs_secret(secret.data(), secret.size());
     debug::print_server_pp_key(key.data(), keylen);
     debug::print_server_pp_iv(iv.data(), ivlen);
+    debug::print_server_pp_pn(pn.data(), pnlen);
   }
 
-  ngtcp2_conn_set_handshake_tx_keys(conn_, key.data(), keylen, iv.data(),
-                                    ivlen);
+  ngtcp2_conn_set_handshake_tx_keys(conn_, key.data(), keylen, iv.data(), ivlen,
+                                    pn.data(), pnlen);
 
   rv = crypto::derive_client_handshake_secret(secret.data(), secret.size(),
                                               handshake_secret.data(),
@@ -1087,14 +1126,21 @@ int Handler::recv_client_initial(const ngtcp2_cid *dcid) {
     return -1;
   }
 
+  pnlen = crypto::derive_pkt_num_protection_key(
+      pn.data(), pn.size(), secret.data(), secret.size(), hs_crypto_ctx_);
+  if (pnlen < 0) {
+    return -1;
+  }
+
   if (!config.quiet && config.show_secret) {
     debug::print_client_hs_secret(secret.data(), secret.size());
     debug::print_client_pp_key(key.data(), keylen);
     debug::print_client_pp_iv(iv.data(), ivlen);
+    debug::print_client_pp_pn(pn.data(), pnlen);
   }
 
-  ngtcp2_conn_set_handshake_rx_keys(conn_, key.data(), keylen, iv.data(),
-                                    ivlen);
+  ngtcp2_conn_set_handshake_rx_keys(conn_, key.data(), keylen, iv.data(), ivlen,
+                                    pn.data(), pnlen);
 
   return 0;
 }
@@ -1121,7 +1167,7 @@ int Handler::setup_early_crypto_context() {
     return -1;
   }
 
-  std::array<uint8_t, 64> key, iv;
+  std::array<uint8_t, 64> key, iv, pn;
 
   auto keylen = crypto::derive_packet_protection_key(
       key.data(), key.size(), crypto_ctx_.rx_secret.data(),
@@ -1137,14 +1183,23 @@ int Handler::setup_early_crypto_context() {
     return -1;
   }
 
+  auto pnlen = crypto::derive_pkt_num_protection_key(
+      pn.data(), pn.size(), crypto_ctx_.rx_secret.data(), crypto_ctx_.secretlen,
+      crypto_ctx_);
+  if (pnlen < 0) {
+    return -1;
+  }
+
   if (!config.quiet && config.show_secret) {
     debug::print_client_0rtt_secret(crypto_ctx_.rx_secret.data(),
                                     crypto_ctx_.secretlen);
     debug::print_client_pp_key(key.data(), keylen);
     debug::print_client_pp_iv(iv.data(), ivlen);
+    debug::print_client_pp_pn(pn.data(), pnlen);
   }
 
-  ngtcp2_conn_update_early_keys(conn_, key.data(), keylen, iv.data(), ivlen);
+  ngtcp2_conn_update_early_keys(conn_, key.data(), keylen, iv.data(), ivlen,
+                                pn.data(), pnlen);
 
   ngtcp2_conn_set_aead_overhead(conn_, crypto::aead_max_overhead(crypto_ctx_));
 
@@ -1173,7 +1228,7 @@ int Handler::setup_crypto_context() {
     return -1;
   }
 
-  std::array<uint8_t, 64> key{}, iv{};
+  std::array<uint8_t, 64> key, iv, pn;
 
   auto keylen = crypto::derive_packet_protection_key(
       key.data(), key.size(), crypto_ctx_.tx_secret.data(),
@@ -1189,14 +1244,23 @@ int Handler::setup_crypto_context() {
     return -1;
   }
 
+  auto pnlen = crypto::derive_pkt_num_protection_key(
+      pn.data(), pn.size(), crypto_ctx_.tx_secret.data(), crypto_ctx_.secretlen,
+      crypto_ctx_);
+  if (pnlen < 0) {
+    return -1;
+  }
+
   if (!config.quiet && config.show_secret) {
     debug::print_server_1rtt_secret(crypto_ctx_.tx_secret.data(),
                                     crypto_ctx_.secretlen);
     debug::print_server_pp_key(key.data(), keylen);
     debug::print_server_pp_iv(iv.data(), ivlen);
+    debug::print_server_pp_pn(pn.data(), pnlen);
   }
 
-  ngtcp2_conn_update_tx_keys(conn_, key.data(), keylen, iv.data(), ivlen);
+  ngtcp2_conn_update_tx_keys(conn_, key.data(), keylen, iv.data(), ivlen,
+                             pn.data(), pnlen);
 
   rv = crypto::export_client_secret(crypto_ctx_.rx_secret.data(),
                                     crypto_ctx_.secretlen, ssl_);
@@ -1218,14 +1282,23 @@ int Handler::setup_crypto_context() {
     return -1;
   }
 
+  pnlen = crypto::derive_pkt_num_protection_key(
+      pn.data(), pn.size(), crypto_ctx_.rx_secret.data(), crypto_ctx_.secretlen,
+      crypto_ctx_);
+  if (pnlen < 0) {
+    return -1;
+  }
+
   if (!config.quiet && config.show_secret) {
     debug::print_client_1rtt_secret(crypto_ctx_.rx_secret.data(),
                                     crypto_ctx_.secretlen);
     debug::print_client_pp_key(key.data(), keylen);
     debug::print_client_pp_iv(iv.data(), ivlen);
+    debug::print_client_pp_pn(pn.data(), pnlen);
   }
 
-  ngtcp2_conn_update_rx_keys(conn_, key.data(), keylen, iv.data(), ivlen);
+  ngtcp2_conn_update_rx_keys(conn_, key.data(), keylen, iv.data(), ivlen,
+                             pn.data(), pnlen);
 
   ngtcp2_conn_set_aead_overhead(conn_, crypto::aead_max_overhead(crypto_ctx_));
 
@@ -1270,12 +1343,30 @@ ssize_t Handler::decrypt_data(uint8_t *dest, size_t destlen,
                          key, keylen, nonce, noncelen, ad, adlen);
 }
 
+ssize_t Handler::hs_encrypt_pn(uint8_t *dest, size_t destlen,
+                               const uint8_t *ciphertext, size_t ciphertextlen,
+                               const uint8_t *key, size_t keylen,
+                               const uint8_t *nonce, size_t noncelen) {
+  return crypto::encrypt_pn(dest, destlen, ciphertext, ciphertextlen,
+                            hs_crypto_ctx_, key, keylen, nonce, noncelen);
+}
+
+ssize_t Handler::encrypt_pn(uint8_t *dest, size_t destlen,
+                            const uint8_t *ciphertext, size_t ciphertextlen,
+                            const uint8_t *key, size_t keylen,
+                            const uint8_t *nonce, size_t noncelen) {
+  return crypto::encrypt_pn(dest, destlen, ciphertext, ciphertextlen,
+                            crypto_ctx_, key, keylen, nonce, noncelen);
+}
+
 ssize_t Handler::do_handshake_once(const uint8_t *data, size_t datalen) {
   auto nwrite = ngtcp2_conn_handshake(conn_, sendbuf_.wpos(), max_pktlen_, data,
                                       datalen, util::timestamp(loop_));
   if (nwrite < 0) {
     switch (nwrite) {
     case NGTCP2_ERR_TLS_DECRYPT:
+      std::cerr << "ngtcp2_conn_handshake: " << ngtcp2_strerror(nwrite)
+                << std::endl;
     case NGTCP2_ERR_NOBUF:
       return 0;
     }
diff --git a/examples/server.h b/examples/server.h
index f33147e5..04fe6c5f 100644
--- a/examples/server.h
+++ b/examples/server.h
@@ -199,6 +199,13 @@ public:
                        size_t ciphertextlen, const uint8_t *key, size_t keylen,
                        const uint8_t *nonce, size_t noncelen, const uint8_t *ad,
                        size_t adlen);
+  ssize_t hs_encrypt_pn(uint8_t *dest, size_t destlen,
+                        const uint8_t *ciphertext, size_t ciphertextlen,
+                        const uint8_t *key, size_t keylen, const uint8_t *nonce,
+                        size_t noncelen);
+  ssize_t encrypt_pn(uint8_t *dest, size_t destlen, const uint8_t *ciphertext,
+                     size_t ciphertextlen, const uint8_t *key, size_t keylen,
+                     const uint8_t *nonce, size_t noncelen);
   Server *server() const;
   const Address &remote_addr() const;
   ngtcp2_conn *conn() const;
diff --git a/lib/includes/ngtcp2/ngtcp2.h b/lib/includes/ngtcp2/ngtcp2.h
index a9ef0ec2..2d512ee7 100644
--- a/lib/includes/ngtcp2/ngtcp2.h
+++ b/lib/includes/ngtcp2/ngtcp2.h
@@ -236,9 +236,8 @@ typedef enum {
   NGTCP2_PKT_RETRY = 0x7E,
   NGTCP2_PKT_HANDSHAKE = 0x7D,
   NGTCP2_PKT_0RTT_PROTECTED = 0x7C,
-  NGTCP2_PKT_01 = 0x00,
-  NGTCP2_PKT_02 = 0x01,
-  NGTCP2_PKT_03 = 0x02
+  /* NGTCP2_PKT_SHORT is defined by libngtcp2 for convenience. */
+  NGTCP2_PKT_SHORT = 0x00
 } ngtcp2_pkt_type;
 
 typedef enum {
@@ -316,7 +315,15 @@ typedef struct {
   ngtcp2_cid dcid;
   ngtcp2_cid scid;
   uint64_t pkt_num;
-  size_t payloadlen;
+  /**
+   * pkt_numlen is the number of bytes spent to encode pkt_num.
+   */
+  size_t pkt_numlen;
+  /**
+   * len is the sum of pkt_numlen and the length of QUIC packet
+   * payload.
+   */
+  size_t len;
   uint32_t version;
   uint8_t type;
   uint8_t flags;
@@ -640,11 +647,11 @@ ngtcp2_decode_transport_params(ngtcp2_transport_params *params, uint8_t exttype,
  * @function
  *
  * `ngtcp2_pkt_decode_hd_long` decodes QUIC long packet header in
- * |pkt| of length |pktlen|.
+ * |pkt| of length |pktlen|.  This function only parses the input just
+ * before packet number field.
  *
- * This function does not verify that payload length is correct.  In
- * other words, this function succeeds even if payload length >
- * |pktlen|.
+ * This function does not verify that length field is correct.  In
+ * other words, this function succeeds even if length > |pktlen|.
  *
  * This function handles Version Negotiation specially.  If version
  * field is 0, |pkt| must contain Version Negotiation packet.  Version
@@ -673,10 +680,11 @@ NGTCP2_EXTERN ssize_t ngtcp2_pkt_decode_hd_long(ngtcp2_pkt_hd *dest,
  * `ngtcp2_pkt_decode_hd_short` decodes QUIC short packet header in
  * |pkt| of length |pktlen|.  |dcidlen| is the length of DCID in
  * packet header.  Short packet does not encode the length of
- * connection ID, thus we need the input from the outside.  It stores
- * the result in the object pointed by |dest|, and returns the number
- * of bytes decoded to read the packet header if it succeeds, or one
- * of the following error codes:
+ * connection ID, thus we need the input from the outside.  This
+ * function only parses the input just before packet number field.  It
+ * stores the result in the object pointed by |dest|, and returns the
+ * number of bytes decoded to read the packet header if it succeeds,
+ * or one of the following error codes:
  *
  * :enum:`NGTCP2_ERR_INVALID_ARGUMENT`
  *     Packet is too short; or it is not a short header
@@ -772,9 +780,8 @@ struct ngtcp2_conn;
 typedef struct ngtcp2_conn ngtcp2_conn;
 
 typedef ssize_t (*ngtcp2_send_client_initial)(ngtcp2_conn *conn, uint32_t flags,
-                                              uint64_t *ppkt_num,
                                               const uint8_t **pdest,
-                                              void *user_data);
+                                              int initial, void *user_data);
 
 typedef ssize_t (*ngtcp2_send_client_handshake)(ngtcp2_conn *conn,
                                                 uint32_t flags,
@@ -804,9 +811,8 @@ typedef int (*ngtcp2_recv_client_initial)(ngtcp2_conn *conn,
 
 typedef ssize_t (*ngtcp2_send_server_handshake)(ngtcp2_conn *conn,
                                                 uint32_t flags,
-                                                uint64_t *ppkt_num,
                                                 const uint8_t **pdest,
-                                                void *user_data);
+                                                int initial, void *user_data);
 
 /**
  * @functypedef
@@ -874,6 +880,12 @@ typedef ssize_t (*ngtcp2_decrypt)(ngtcp2_conn *conn, uint8_t *dest,
                                   size_t noncelen, const uint8_t *ad,
                                   size_t adlen, void *user_data);
 
+typedef ssize_t (*ngtcp2_encrypt_pn)(ngtcp2_conn *conn, uint8_t *dest,
+                                     size_t destlen, const uint8_t *plaintext,
+                                     size_t plaintextlen, const uint8_t *key,
+                                     size_t keylen, const uint8_t *nonce,
+                                     size_t noncelen, void *user_data);
+
 typedef int (*ngtcp2_recv_stream_data)(ngtcp2_conn *conn, uint64_t stream_id,
                                        uint8_t fin, uint64_t offset,
                                        const uint8_t *data, size_t datalen,
@@ -952,6 +964,8 @@ typedef struct {
   ngtcp2_decrypt hs_decrypt;
   ngtcp2_encrypt encrypt;
   ngtcp2_decrypt decrypt;
+  ngtcp2_encrypt_pn hs_encrypt_pn;
+  ngtcp2_encrypt_pn encrypt_pn;
   ngtcp2_recv_stream_data recv_stream_data;
   ngtcp2_acked_stream_data_offset acked_stream_data_offset;
   ngtcp2_stream_close stream_close;
@@ -1154,8 +1168,8 @@ NGTCP2_EXTERN int ngtcp2_conn_get_handshake_completed(ngtcp2_conn *conn);
 /**
  * @function
  *
- * `ngtcp2_conn_set_handshake_tx_keys` sets key and iv to encrypt
- *  handshake packets.  If key and iv have already been set, they are
+ * `ngtcp2_conn_set_handshake_tx_keys` sets key, iv, and pn to encrypt
+ *  handshake packets.  If they have already been set, they are
  *  overwritten.
  *
  * This function returns 0 if it succeeds, or one of the following
@@ -1164,17 +1178,15 @@ NGTCP2_EXTERN int ngtcp2_conn_get_handshake_completed(ngtcp2_conn *conn);
  * :enum:`NGTCP2_ERR_NOMEM`
  *     Out of memory.
  */
-NGTCP2_EXTERN int ngtcp2_conn_set_handshake_tx_keys(ngtcp2_conn *conn,
-                                                    const uint8_t *key,
-                                                    size_t keylen,
-                                                    const uint8_t *iv,
-                                                    size_t ivlen);
+NGTCP2_EXTERN int ngtcp2_conn_set_handshake_tx_keys(
+    ngtcp2_conn *conn, const uint8_t *key, size_t keylen, const uint8_t *iv,
+    size_t ivlen, const uint8_t *pn, size_t pnlen);
 
 /**
  * @function
  *
- * `ngtcp2_conn_set_handshake_rx_keys` sets key and iv to decrypt
- * handshake packets.  If key and iv have already been set, they are
+ * `ngtcp2_conn_set_handshake_rx_keys` sets key, iv and pn to decrypt
+ * handshake packets.  If they have already been set, they are
  * overwritten.
  *
  * This function returns 0 if it succeeds, or one of the following
@@ -1183,26 +1195,27 @@ NGTCP2_EXTERN int ngtcp2_conn_set_handshake_tx_keys(ngtcp2_conn *conn,
  * :enum:`NGTCP2_ERR_NOMEM`
  *     Out of memory.
  */
-NGTCP2_EXTERN int ngtcp2_conn_set_handshake_rx_keys(ngtcp2_conn *conn,
-                                                    const uint8_t *key,
-                                                    size_t keylen,
-                                                    const uint8_t *iv,
-                                                    size_t ivlen);
+NGTCP2_EXTERN int ngtcp2_conn_set_handshake_rx_keys(
+    ngtcp2_conn *conn, const uint8_t *key, size_t keylen, const uint8_t *iv,
+    size_t ivlen, const uint8_t *pn, size_t pnlen);
 
 NGTCP2_EXTERN void ngtcp2_conn_set_aead_overhead(ngtcp2_conn *conn,
                                                  size_t aead_overhead);
 
 NGTCP2_EXTERN int
 ngtcp2_conn_update_early_keys(ngtcp2_conn *conn, const uint8_t *key,
-                              size_t keylen, const uint8_t *iv, size_t ivlen);
+                              size_t keylen, const uint8_t *iv, size_t ivlen,
+                              const uint8_t *pn, size_t pnlen);
 
 NGTCP2_EXTERN int ngtcp2_conn_update_tx_keys(ngtcp2_conn *conn,
                                              const uint8_t *key, size_t keylen,
-                                             const uint8_t *iv, size_t ivlen);
+                                             const uint8_t *iv, size_t ivlen,
+                                             const uint8_t *pn, size_t pnlen);
 
 NGTCP2_EXTERN int ngtcp2_conn_update_rx_keys(ngtcp2_conn *conn,
                                              const uint8_t *key, size_t keylen,
-                                             const uint8_t *iv, size_t ivlen);
+                                             const uint8_t *iv, size_t ivlen,
+                                             const uint8_t *pn, size_t pnlen);
 
 /**
  * @function
diff --git a/lib/ngtcp2_conn.c b/lib/ngtcp2_conn.c
index 44392883..85e15481 100644
--- a/lib/ngtcp2_conn.c
+++ b/lib/ngtcp2_conn.c
@@ -237,6 +237,7 @@ static int conn_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid,
   (*pconn)->version = version;
   (*pconn)->mem = mem;
   (*pconn)->user_data = user_data;
+  (*pconn)->last_tx_pkt_num = (uint64_t)-1;
   (*pconn)->largest_ack = -1;
   (*pconn)->local_settings = *settings;
   (*pconn)->unsent_max_rx_offset = (*pconn)->max_rx_offset = settings->max_data;
@@ -651,6 +652,7 @@ static ssize_t conn_retransmit_unprotected_once(ngtcp2_conn *conn,
   ctx.ckm = conn->hs_tx_ckm;
   ctx.aead_overhead = NGTCP2_HANDSHAKE_AEAD_OVERHEAD;
   ctx.encrypt = conn->callbacks.hs_encrypt;
+  ctx.encrypt_pn = conn->callbacks.hs_encrypt_pn;
   ctx.user_data = conn;
 
   ngtcp2_ppe_init(&ppe, dest, destlen, &ctx);
@@ -826,25 +828,27 @@ static ssize_t conn_retransmit_unprotected(ngtcp2_conn *conn, uint8_t *dest,
 }
 
 /*
- * conn_select_pkt_type selects shorted short packet type based on the
- * next packet number |pkt_num|.
+ * conn_select_pkt_numlen selects shortest packet number encoding
+ * based on the next packet number |pkt_num| and the largest
+ * acknowledged packet number.  It returns the number of bytes to
+ * encode the packet number.
  */
-static uint8_t conn_select_pkt_type(ngtcp2_conn *conn, uint64_t pkt_num) {
+static size_t conn_select_pkt_numlen(ngtcp2_conn *conn, uint64_t pkt_num) {
   uint64_t n =
       (uint64_t)((int64_t)pkt_num - conn->rtb.largest_acked_tx_pkt_num);
   if (UINT64_MAX / 2 <= pkt_num) {
-    return NGTCP2_PKT_03;
+    return 4;
   }
 
   n = n * 2 + 1;
 
-  if (n > 0xffff) {
-    return NGTCP2_PKT_03;
+  if (n > 0x3fff) {
+    return 4;
   }
-  if (n > 0xff) {
-    return NGTCP2_PKT_02;
+  if (n > 0x7f) {
+    return 2;
   }
-  return NGTCP2_PKT_01;
+  return 1;
 }
 
 /*
@@ -880,11 +884,13 @@ static ssize_t conn_retransmit_protected_once(ngtcp2_conn *conn, uint8_t *dest,
   hd.version = conn->version;
   hd.dcid = conn->dcid;
   hd.pkt_num = conn->last_tx_pkt_num + 1;
-  hd.type = conn_select_pkt_type(conn, hd.pkt_num);
+  hd.pkt_numlen = conn_select_pkt_numlen(conn, hd.pkt_num);
+  hd.type = NGTCP2_PKT_SHORT;
 
   ctx.ckm = conn->tx_ckm;
   ctx.aead_overhead = conn->aead_overhead;
   ctx.encrypt = conn->callbacks.encrypt;
+  ctx.encrypt_pn = conn->callbacks.encrypt_pn;
   ctx.user_data = conn;
 
   ngtcp2_ppe_init(&ppe, dest, destlen, &ctx);
@@ -1036,17 +1042,9 @@ static ssize_t conn_retransmit_protected(ngtcp2_conn *conn, uint8_t *dest,
     ngtcp2_rtb_lost_protected_pop(&conn->rtb);
 
     assert(!(ent->hd.flags & NGTCP2_PKT_FLAG_LONG_FORM));
-    switch (ent->hd.type) {
-    case NGTCP2_PKT_01:
-    case NGTCP2_PKT_02:
-    case NGTCP2_PKT_03:
-      nwrite = conn_retransmit_protected_once(conn, dest, destlen, ent, ts);
-      break;
-    default:
-      /* TODO fix this */
-      ngtcp2_rtb_entry_del(ent, conn->mem);
-      return NGTCP2_ERR_INVALID_ARGUMENT;
-    }
+    assert(ent->hd.type == NGTCP2_PKT_SHORT);
+
+    nwrite = conn_retransmit_protected_once(conn, dest, destlen, ent, ts);
 
     if (nwrite <= 0) {
       if (nwrite == 0) {
@@ -1201,11 +1199,14 @@ static ssize_t conn_write_handshake_pkt(ngtcp2_conn *conn, uint8_t *dest,
   pfrc = &frc_head;
 
   ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_LONG_FORM, type, &conn->dcid,
-                     &conn->scid, conn->last_tx_pkt_num + 1, conn->version, 0);
+                     &conn->scid, conn->last_tx_pkt_num + 1,
+                     conn_select_pkt_numlen(conn, conn->last_tx_pkt_num + 1),
+                     conn->version, 0);
 
   ctx.ckm = conn->hs_tx_ckm;
   ctx.aead_overhead = NGTCP2_HANDSHAKE_AEAD_OVERHEAD;
   ctx.encrypt = conn->callbacks.hs_encrypt;
+  ctx.encrypt_pn = conn->callbacks.hs_encrypt_pn;
   ctx.user_data = conn;
 
   ngtcp2_ppe_init(&ppe, dest, destlen, &ctx);
@@ -1354,6 +1355,7 @@ static ssize_t conn_write_handshake_pkt(ngtcp2_conn *conn, uint8_t *dest,
   if (type == NGTCP2_PKT_INITIAL &&
       (!conn->early_ckm || require_padding ||
        ngtcp2_ppe_left(&ppe) <
+           /* TODO Assuming that pkt_num is encoded in 1 byte. */
            NGTCP2_MIN_LONG_HEADERLEN + conn->rcid.datalen + conn->scid.datalen +
                1 /* payloadlen bytes - 1 */ + 128 /* minimum data */ +
                NGTCP2_MAX_AEAD_OVERHEAD)) {
@@ -1459,11 +1461,13 @@ static ssize_t conn_write_handshake_ack_pkt(ngtcp2_conn *conn, uint8_t *dest,
 
   ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_LONG_FORM, NGTCP2_PKT_HANDSHAKE,
                      &conn->dcid, &conn->scid, conn->last_tx_pkt_num + 1,
+                     conn_select_pkt_numlen(conn, conn->last_tx_pkt_num + 1),
                      conn->version, 0);
 
   ctx.ckm = conn->hs_tx_ckm;
   ctx.aead_overhead = NGTCP2_HANDSHAKE_AEAD_OVERHEAD;
   ctx.encrypt = conn->callbacks.hs_encrypt;
+  ctx.encrypt_pn = conn->callbacks.hs_encrypt_pn;
   ctx.user_data = conn;
 
   ngtcp2_ppe_init(&ppe, dest, destlen, &ctx);
@@ -1511,15 +1515,13 @@ fail:
 static ssize_t conn_write_client_initial(ngtcp2_conn *conn, uint8_t *dest,
                                          size_t destlen, int require_padding,
                                          ngtcp2_tstamp ts) {
-  uint64_t pkt_num = 0;
   const uint8_t *payload;
   ssize_t payloadlen;
   ngtcp2_buf *tx_buf = &conn->strm0->tx_buf;
 
   payloadlen = conn->callbacks.send_client_initial(
-      conn, NGTCP2_CONN_FLAG_NONE,
-      (conn->flags & NGTCP2_CONN_FLAG_STATELESS_RETRY) ? NULL : &pkt_num,
-      &payload, conn->user_data);
+      conn, NGTCP2_CONN_FLAG_NONE, &payload,
+      !(conn->flags & NGTCP2_CONN_FLAG_STATELESS_RETRY), conn->user_data);
 
   if (payloadlen <= 0) {
     return NGTCP2_ERR_CALLBACK_FAILURE;
@@ -1528,11 +1530,6 @@ static ssize_t conn_write_client_initial(ngtcp2_conn *conn, uint8_t *dest,
   ngtcp2_buf_init(tx_buf, (uint8_t *)payload, (size_t)payloadlen);
   tx_buf->last += payloadlen;
 
-  if (!(conn->flags & NGTCP2_CONN_FLAG_STATELESS_RETRY)) {
-    conn->last_tx_pkt_num = pkt_num - 1;
-    conn->rtb.largest_acked_tx_pkt_num = (int64_t)conn->last_tx_pkt_num;
-  }
-
   return conn_write_handshake_pkt(conn, dest, destlen, NGTCP2_PKT_INITIAL,
                                   tx_buf, require_padding, ts);
 }
@@ -1602,7 +1599,6 @@ static ssize_t conn_write_protected_ack_pkt(ngtcp2_conn *conn, uint8_t *dest,
 static ssize_t conn_write_server_handshake(ngtcp2_conn *conn, uint8_t *dest,
                                            size_t destlen, int initial,
                                            ngtcp2_tstamp ts) {
-  uint64_t pkt_num = 0;
   const uint8_t *payload;
   ssize_t payloadlen;
   ngtcp2_buf *tx_buf = &conn->strm0->tx_buf;
@@ -1610,8 +1606,7 @@ static ssize_t conn_write_server_handshake(ngtcp2_conn *conn, uint8_t *dest,
 
   if (ngtcp2_buf_len(tx_buf) == 0) {
     payloadlen = conn->callbacks.send_server_handshake(
-        conn, NGTCP2_CONN_FLAG_NONE, initial ? &pkt_num : NULL, &payload,
-        conn->user_data);
+        conn, NGTCP2_CONN_FLAG_NONE, &payload, initial, conn->user_data);
 
     if (payloadlen < 0) {
       return NGTCP2_ERR_CALLBACK_FAILURE;
@@ -1641,11 +1636,6 @@ static ssize_t conn_write_server_handshake(ngtcp2_conn *conn, uint8_t *dest,
     tx_buf->last += payloadlen;
   }
 
-  if (initial) {
-    conn->last_tx_pkt_num = pkt_num - 1;
-    conn->rtb.largest_acked_tx_pkt_num = (int64_t)conn->last_tx_pkt_num;
-  }
-
   return conn_write_handshake_pkt(conn, dest, destlen, NGTCP2_PKT_HANDSHAKE,
                                   tx_buf, 0 /*require_padding */, ts);
 }
@@ -1778,14 +1768,15 @@ static ssize_t conn_write_pkt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
     return 0;
   }
 
-  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE,
-                     conn_select_pkt_type(conn, conn->last_tx_pkt_num + 1),
-                     &conn->dcid, &conn->scid, conn->last_tx_pkt_num + 1,
+  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_SHORT, &conn->dcid,
+                     &conn->scid, conn->last_tx_pkt_num + 1,
+                     conn_select_pkt_numlen(conn, conn->last_tx_pkt_num + 1),
                      conn->version, 0);
 
   ctx.ckm = conn->tx_ckm;
   ctx.aead_overhead = conn->aead_overhead;
   ctx.encrypt = conn->callbacks.encrypt;
+  ctx.encrypt_pn = conn->callbacks.encrypt_pn;
   ctx.user_data = conn;
 
   ngtcp2_ppe_init(&ppe, dest, destlen, &ctx);
@@ -2011,14 +2002,15 @@ static ssize_t conn_write_single_frame_pkt(ngtcp2_conn *conn, uint8_t *dest,
   ssize_t nwrite;
   ngtcp2_crypto_ctx ctx;
 
-  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE,
-                     conn_select_pkt_type(conn, conn->last_tx_pkt_num + 1),
-                     &conn->dcid, &conn->scid, conn->last_tx_pkt_num + 1,
+  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_SHORT, &conn->dcid,
+                     &conn->scid, conn->last_tx_pkt_num + 1,
+                     conn_select_pkt_numlen(conn, conn->last_tx_pkt_num + 1),
                      conn->version, 0);
 
   ctx.ckm = conn->tx_ckm;
   ctx.aead_overhead = conn->aead_overhead;
   ctx.encrypt = conn->callbacks.encrypt;
+  ctx.encrypt_pn = conn->callbacks.encrypt_pn;
   ctx.user_data = conn;
 
   ngtcp2_ppe_init(&ppe, dest, destlen, &ctx);
@@ -2077,11 +2069,13 @@ static ssize_t conn_write_single_frame_handshake_pkt(ngtcp2_conn *conn,
 
   ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_LONG_FORM, NGTCP2_PKT_HANDSHAKE,
                      &conn->dcid, &conn->scid, conn->last_tx_pkt_num + 1,
+                     conn_select_pkt_numlen(conn, conn->last_tx_pkt_num + 1),
                      conn->version, 0);
 
   ctx.ckm = conn->hs_tx_ckm;
   ctx.aead_overhead = NGTCP2_HANDSHAKE_AEAD_OVERHEAD;
   ctx.encrypt = conn->callbacks.hs_encrypt;
+  ctx.encrypt_pn = conn->callbacks.hs_encrypt_pn;
   ctx.user_data = conn;
 
   ngtcp2_ppe_init(&ppe, dest, destlen, &ctx);
@@ -2645,6 +2639,45 @@ static ssize_t conn_decrypt_pkt(ngtcp2_conn *conn, uint8_t *dest,
   return nwrite;
 }
 
+/*
+ * conn_decrypt_pn decryptes packet number which starts at |pkt| +
+ * |pkt_num_offset|.  The entire plaintext QUIC packer header will be
+ * written to the buffer pointed by |dest|.  This function assumes
+ * that |dest| has enough capacity to store the entire packet header.
+ */
+static ssize_t conn_decrypt_pn(ngtcp2_conn *conn, ngtcp2_pkt_hd *hd,
+                               uint8_t *dest, const uint8_t *pkt, size_t pktlen,
+                               size_t pkt_num_offset, ngtcp2_crypto_km *ckm,
+                               ngtcp2_encrypt_pn enc, size_t aead_overhead) {
+  ssize_t nwrite;
+  size_t sample_offset;
+  uint8_t *p = dest;
+
+  assert(enc);
+  assert(ckm);
+  assert(aead_overhead >= NGTCP2_PN_SAMPLELEN);
+
+  if (pkt_num_offset + 1 + aead_overhead > pktlen) {
+    return NGTCP2_ERR_PROTO;
+  }
+
+  p = ngtcp2_cpymem(p, pkt, pkt_num_offset);
+
+  sample_offset = ngtcp2_min(pkt_num_offset + 4, pktlen - aead_overhead);
+
+  nwrite = enc(conn, p, 4, pkt + pkt_num_offset, 4, ckm->pn, ckm->pnlen,
+               pkt + sample_offset, NGTCP2_PN_SAMPLELEN, conn->user_data);
+  if (nwrite != 4) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  hd->pkt_num = ngtcp2_get_pkt_num(&hd->pkt_numlen, p);
+
+  p += hd->pkt_numlen;
+
+  return p - dest;
+}
+
 static void conn_extend_max_stream_offset(ngtcp2_conn *conn, ngtcp2_strm *strm,
                                           size_t datalen) {
   if (strm->unsent_max_rx_offset <= NGTCP2_MAX_VARINT - datalen) {
@@ -2822,6 +2855,23 @@ static int conn_ensure_retry_ack_initial(ngtcp2_conn *conn,
   return 0;
 }
 
+/*
+ * pkt_num_bits returns the number of bits available when packet
+ * number is encoded in |pkt_numlen| bytes.
+ */
+static size_t pkt_num_bits(size_t pkt_numlen) {
+  switch (pkt_numlen) {
+  case 1:
+    return 7;
+  case 2:
+    return 14;
+  case 4:
+    return 30;
+  default:
+    assert(0);
+  }
+}
+
 /*
  * conn_recv_handshake_pkt processes received packet |pkt| whose
  * length if |pktlen| during handshake period.  The buffer pointed by
@@ -2866,11 +2916,11 @@ static ssize_t conn_recv_handshake_pkt(ngtcp2_conn *conn, const uint8_t *pkt,
   uint64_t rx_offset;
   int handshake_failed = 0;
   uint64_t fr_end_offset;
-  const uint8_t *hdpkt = pkt;
   size_t hdpktlen;
   const uint8_t *payload;
   size_t payloadlen;
   ssize_t nwrite;
+  uint8_t plain_hdpkt[256];
 
   if (pktlen == 0) {
     return 0;
@@ -2894,11 +2944,11 @@ static ssize_t conn_recv_handshake_pkt(ngtcp2_conn *conn, const uint8_t *pkt,
     return (int)nread;
   }
 
-  ngtcp2_log_pkt_hd(&conn->log, &hd);
+  if (hd.type == NGTCP2_PKT_VERSION_NEGOTIATION) {
+    hdpktlen = (size_t)nread;
 
-  hdpktlen = (size_t)nread;
+    ngtcp2_log_pkt_hd(&conn->log, &hd);
 
-  if (hd.type == NGTCP2_PKT_VERSION_NEGOTIATION) {
     if (conn->server) {
       return NGTCP2_ERR_PROTO;
     }
@@ -2921,60 +2971,79 @@ static ssize_t conn_recv_handshake_pkt(ngtcp2_conn *conn, const uint8_t *pkt,
     return NGTCP2_ERR_RECV_VERSION_NEGOTIATION;
   }
 
-  if (conn->version != hd.version) {
+  if (pktlen < (size_t)nread + hd.len) {
     return (ssize_t)pktlen;
   }
 
-  if (pktlen < hdpktlen + hd.payloadlen) {
+  pktlen = (size_t)nread + hd.len;
+
+  if (conn->version != hd.version) {
     return (ssize_t)pktlen;
   }
 
-  payload = pkt + hdpktlen;
-  pktlen = hdpktlen + hd.payloadlen;
-  payloadlen = hd.payloadlen;
+  if (hd.type == NGTCP2_PKT_0RTT_PROTECTED) {
+    if (!conn->server) {
+      /* TODO protocol violation? */
+      return (ssize_t)pktlen;
+    }
+    if (conn->flags & NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED) {
+      if (conn->early_ckm) {
+        ssize_t nread2;
+        /* TODO Avoid to parse header twice. */
+        nread2 = conn_recv_pkt(conn, pkt, pktlen, ts);
+        if (nread2 < 0) {
+          return nread2;
+        }
+      }
 
-  if (conn->server && conn->early_ckm && ngtcp2_cid_eq(&conn->rcid, &hd.dcid) &&
-      hd.type == NGTCP2_PKT_0RTT_PROTECTED) {
-    ssize_t nread2;
-    /* TODO Avoid to parse header twice. */
-    nread2 = conn_recv_pkt(conn, pkt, pktlen, ts);
-    if (nread2 < 0) {
-      return nread2;
+      /* Discard 0-RTT packet if we don't have a key to decrypt it. */
+      return (ssize_t)pktlen;
+    } else {
+      /* Buffer re-ordered 0-RTT Protected packet. */
+      rv = conn_buffer_protected_pkt(conn, pkt, pktlen, ts);
+      if (rv != 0) {
+        return rv;
+      }
+      return (ssize_t)pktlen;
     }
-    return (ssize_t)pktlen;
   }
 
-  hd.pkt_num = ngtcp2_pkt_adjust_pkt_num(conn->max_rx_pkt_num, hd.pkt_num, 32);
+  if (conn->server && hd.type == NGTCP2_PKT_INITIAL &&
+      (conn->flags & NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED) == 0) {
+    conn->flags |= NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED;
+    conn->rcid = hd.dcid;
+
+    rv = conn_call_recv_client_initial(conn);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  nwrite = conn_decrypt_pn(conn, &hd, plain_hdpkt, pkt, pktlen, (size_t)nread,
+                           conn->hs_rx_ckm, conn->callbacks.hs_encrypt_pn,
+                           NGTCP2_HANDSHAKE_AEAD_OVERHEAD);
+  if (nwrite < 0) {
+    return (ssize_t)nwrite;
+  }
+
+  hdpktlen = (size_t)nwrite;
+  payload = pkt + hdpktlen;
+  payloadlen = hd.len - hd.pkt_numlen;
+
+  hd.pkt_num = ngtcp2_pkt_adjust_pkt_num(conn->max_rx_pkt_num, hd.pkt_num,
+                                         pkt_num_bits(hd.pkt_numlen));
+
+  ngtcp2_log_pkt_hd(&conn->log, &hd);
 
   if (conn->server) {
     switch (hd.type) {
     case NGTCP2_PKT_INITIAL:
-      if ((conn->flags & NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED) == 0) {
-        conn->flags |= NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED;
-        conn->rcid = hd.dcid;
-
-        rv = conn_call_recv_client_initial(conn);
-        if (rv != 0) {
-          return rv;
-        }
-      }
       break;
     case NGTCP2_PKT_HANDSHAKE:
       if (!ngtcp2_cid_eq(&conn->scid, &hd.dcid)) {
         return (ssize_t)pktlen;
       }
       break;
-    case NGTCP2_PKT_0RTT_PROTECTED:
-      if (!(conn->flags & NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED)) {
-        /* Buffer re-ordered 0-RTT Protected packet. */
-        rv = conn_buffer_protected_pkt(conn, pkt, pktlen, ts);
-        if (rv != 0) {
-          return rv;
-        }
-        return (ssize_t)pktlen;
-      }
-      /* Discard 0-RTT packet if we don't have a key to decrypt it. */
-      return (ssize_t)pktlen;
     default:
       return NGTCP2_ERR_PROTO;
     }
@@ -3019,7 +3088,7 @@ static ssize_t conn_recv_handshake_pkt(ngtcp2_conn *conn, const uint8_t *pkt,
   }
 
   nwrite = conn_decrypt_pkt(conn, conn->decrypt_buf.base, payloadlen, payload,
-                            payloadlen, hdpkt, hdpktlen, hd.pkt_num,
+                            payloadlen, plain_hdpkt, hdpktlen, hd.pkt_num,
                             conn->hs_rx_ckm, conn->callbacks.hs_decrypt);
   if (nwrite < 0) {
     return (int)nwrite;
@@ -3880,7 +3949,6 @@ static int conn_recv_max_stream_id(ngtcp2_conn *conn,
 static ssize_t conn_recv_pkt(ngtcp2_conn *conn, const uint8_t *pkt,
                              size_t pktlen, ngtcp2_tstamp ts) {
   ngtcp2_pkt_hd hd;
-  size_t pkt_num_bits;
   int rv = 0;
   const uint8_t *hdpkt = pkt;
   size_t hdpktlen;
@@ -3891,6 +3959,9 @@ static ssize_t conn_recv_pkt(ngtcp2_conn *conn, const uint8_t *pkt,
   ngtcp2_frame *fr = &mfr.fr;
   int require_ack = 0;
   ngtcp2_crypto_km *ckm;
+  uint8_t plain_hdpkt[256];
+  ngtcp2_encrypt_pn encrypt_pn;
+  size_t aead_overhead;
 
   if (pkt[0] & NGTCP2_HEADER_FORM_BIT) {
     nread = ngtcp2_pkt_decode_hd_long(&hd, pkt, pktlen);
@@ -3898,42 +3969,34 @@ static ssize_t conn_recv_pkt(ngtcp2_conn *conn, const uint8_t *pkt,
       return nread;
     }
 
-    ngtcp2_log_pkt_hd(&conn->log, &hd);
-
     if (hd.type == NGTCP2_PKT_VERSION_NEGOTIATION) {
+      ngtcp2_log_pkt_hd(&conn->log, &hd);
+
       /* Ignore late VN. */
       return (ssize_t)pktlen;
     }
 
-    if (pktlen < (size_t)nread + hd.payloadlen) {
+    if (pktlen < (size_t)nread + hd.len) {
       return (ssize_t)pktlen;
     }
 
-    pktlen = (size_t)nread + hd.payloadlen;
+    pktlen = (size_t)nread + hd.len;
 
     if (conn->version != hd.version) {
       return (ssize_t)pktlen;
     }
 
-    switch (hd.type) {
-    case NGTCP2_PKT_INITIAL:
-    case NGTCP2_PKT_HANDSHAKE:
-      /* Ignore incoming unprotected packet after we get all
-         acknowledgements to unprotected packet we sent so far. */
-      if (conn->final_hs_tx_offset &&
-          ngtcp2_gaptr_first_gap_offset(&conn->strm0->acked_tx_offset) >=
-              conn->final_hs_tx_offset &&
-          (!conn->server ||
-           (conn->acktr.flags & NGTCP2_ACKTR_FLAG_ACK_FINISHED_ACK))) {
-        ngtcp2_log_info(
-            &conn->log, NGTCP2_LOG_EVENT_CON,
-            "unprotected packet %" PRIu64
-            " is ignored because handshake has finished",
-            ngtcp2_pkt_adjust_pkt_num(conn->max_rx_pkt_num, hd.pkt_num, 32));
-
+    if (hd.type == NGTCP2_PKT_0RTT_PROTECTED) {
+      if (!conn->early_ckm) {
         return (ssize_t)pktlen;
       }
-      break;
+      ckm = conn->early_ckm;
+      encrypt_pn = conn->callbacks.encrypt_pn;
+      aead_overhead = conn->aead_overhead;
+    } else {
+      ckm = conn->hs_rx_ckm;
+      encrypt_pn = conn->callbacks.hs_encrypt_pn;
+      aead_overhead = NGTCP2_HANDSHAKE_AEAD_OVERHEAD;
     }
   } else {
     nread = ngtcp2_pkt_decode_hd_short(&hd, pkt, pktlen, conn->scid.datalen);
@@ -3941,38 +4004,45 @@ static ssize_t conn_recv_pkt(ngtcp2_conn *conn, const uint8_t *pkt,
       return (int)nread;
     }
 
-    ngtcp2_log_pkt_hd(&conn->log, &hd);
+    ckm = conn->rx_ckm;
+    encrypt_pn = conn->callbacks.encrypt_pn;
+    aead_overhead = conn->aead_overhead;
   }
 
-  hdpktlen = (size_t)nread;
+  nwrite = conn_decrypt_pn(conn, &hd, plain_hdpkt, pkt, pktlen, (size_t)nread,
+                           ckm, encrypt_pn, aead_overhead);
+  if (nwrite < 0) {
+    return (ssize_t)nwrite;
+  }
+
+  hdpktlen = (size_t)nwrite;
   payload = pkt + hdpktlen;
   payloadlen = pktlen - hdpktlen;
 
-  if (hd.flags & NGTCP2_PKT_FLAG_LONG_FORM) {
-    pkt_num_bits = 32;
-  } else {
-    switch (hd.type) {
-    case NGTCP2_PKT_01:
-      pkt_num_bits = 8;
-      break;
-    case NGTCP2_PKT_02:
-      pkt_num_bits = 16;
-      break;
-    case NGTCP2_PKT_03:
-      pkt_num_bits = 32;
-      break;
-    default:
-      assert(0);
-    }
-  }
+  hd.pkt_num = ngtcp2_pkt_adjust_pkt_num(conn->max_rx_pkt_num, hd.pkt_num,
+                                         pkt_num_bits(hd.pkt_numlen));
 
-  hd.pkt_num =
-      ngtcp2_pkt_adjust_pkt_num(conn->max_rx_pkt_num, hd.pkt_num, pkt_num_bits);
+  ngtcp2_log_pkt_hd(&conn->log, &hd);
 
   if (hd.flags & NGTCP2_PKT_FLAG_LONG_FORM) {
     switch (hd.type) {
     case NGTCP2_PKT_INITIAL:
     case NGTCP2_PKT_HANDSHAKE:
+      /* Ignore incoming unprotected packet after we get all
+         acknowledgements to unprotected packet we sent so far. */
+      if (conn->final_hs_tx_offset &&
+          ngtcp2_gaptr_first_gap_offset(&conn->strm0->acked_tx_offset) >=
+              conn->final_hs_tx_offset &&
+          (!conn->server ||
+           (conn->acktr.flags & NGTCP2_ACKTR_FLAG_ACK_FINISHED_ACK))) {
+        ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
+                        "unprotected packet %" PRIu64
+                        " is ignored because handshake has finished",
+                        hd.pkt_num);
+
+        return (ssize_t)pktlen;
+      }
+
       rv = conn_recv_delayed_handshake_pkt(conn, &hd, payload, payloadlen,
                                            hdpkt, hdpktlen, ts);
       if (rv != 0) {
@@ -4003,7 +4073,7 @@ static ssize_t conn_recv_pkt(ngtcp2_conn *conn, const uint8_t *pkt,
   }
 
   nwrite = conn_decrypt_pkt(conn, conn->decrypt_buf.base, payloadlen, payload,
-                            payloadlen, hdpkt, hdpktlen, hd.pkt_num, ckm,
+                            payloadlen, plain_hdpkt, hdpktlen, hd.pkt_num, ckm,
                             conn->callbacks.decrypt);
   if (nwrite < 0) {
     if (nwrite != NGTCP2_ERR_TLS_DECRYPT ||
@@ -4513,10 +4583,13 @@ static ssize_t conn_write_stream_early(ngtcp2_conn *conn, uint8_t *dest,
   ctx.ckm = conn->early_ckm;
 
   ngtcp2_pkt_hd_init(&hd, pkt_flags, pkt_type, &conn->rcid, &conn->scid,
-                     conn->last_tx_pkt_num + 1, conn->version, 0);
+                     conn->last_tx_pkt_num + 1,
+                     conn_select_pkt_numlen(conn, conn->last_tx_pkt_num + 1),
+                     conn->version, 0);
 
   ctx.aead_overhead = conn->aead_overhead;
   ctx.encrypt = conn->callbacks.encrypt;
+  ctx.encrypt_pn = conn->callbacks.encrypt_pn;
   ctx.user_data = conn;
 
   ngtcp2_ppe_init(&ppe, dest, destlen, &ctx);
@@ -4588,7 +4661,7 @@ static ssize_t conn_write_stream_early(ngtcp2_conn *conn, uint8_t *dest,
      completes.  This covers the case that 0-RTT data is rejected by
      the peer.  0-RTT packet is retransmitted as a Short packet. */
   ent->hd.flags &= (uint8_t)~NGTCP2_PKT_FLAG_LONG_FORM;
-  ent->hd.type = NGTCP2_PKT_01;
+  ent->hd.type = NGTCP2_PKT_SHORT;
 
   ngtcp2_list_insert(ent, &conn->early_rtb);
 
@@ -4753,55 +4826,62 @@ void ngtcp2_conn_set_aead_overhead(ngtcp2_conn *conn, size_t aead_overhead) {
 
 int ngtcp2_conn_set_handshake_tx_keys(ngtcp2_conn *conn, const uint8_t *key,
                                       size_t keylen, const uint8_t *iv,
-                                      size_t ivlen) {
+                                      size_t ivlen, const uint8_t *pn,
+                                      size_t pnlen) {
   if (conn->hs_tx_ckm) {
     ngtcp2_crypto_km_del(conn->hs_tx_ckm, conn->mem);
     conn->hs_tx_ckm = NULL;
   }
 
-  return ngtcp2_crypto_km_new(&conn->hs_tx_ckm, key, keylen, iv, ivlen,
-                              conn->mem);
+  return ngtcp2_crypto_km_new(&conn->hs_tx_ckm, key, keylen, iv, ivlen, pn,
+                              pnlen, conn->mem);
 }
 
 int ngtcp2_conn_set_handshake_rx_keys(ngtcp2_conn *conn, const uint8_t *key,
                                       size_t keylen, const uint8_t *iv,
-                                      size_t ivlen) {
+                                      size_t ivlen, const uint8_t *pn,
+                                      size_t pnlen) {
   if (conn->hs_rx_ckm) {
     ngtcp2_crypto_km_del(conn->hs_rx_ckm, conn->mem);
     conn->hs_rx_ckm = NULL;
   }
 
-  return ngtcp2_crypto_km_new(&conn->hs_rx_ckm, key, keylen, iv, ivlen,
-                              conn->mem);
+  return ngtcp2_crypto_km_new(&conn->hs_rx_ckm, key, keylen, iv, ivlen, pn,
+                              pnlen, conn->mem);
 }
 
 int ngtcp2_conn_update_early_keys(ngtcp2_conn *conn, const uint8_t *key,
                                   size_t keylen, const uint8_t *iv,
-                                  size_t ivlen) {
+                                  size_t ivlen, const uint8_t *pn,
+                                  size_t pnlen) {
   if (conn->early_ckm) {
     return NGTCP2_ERR_INVALID_STATE;
   }
 
-  return ngtcp2_crypto_km_new(&conn->early_ckm, key, keylen, iv, ivlen,
-                              conn->mem);
+  return ngtcp2_crypto_km_new(&conn->early_ckm, key, keylen, iv, ivlen, pn,
+                              pnlen, conn->mem);
 }
 
 int ngtcp2_conn_update_tx_keys(ngtcp2_conn *conn, const uint8_t *key,
-                               size_t keylen, const uint8_t *iv, size_t ivlen) {
+                               size_t keylen, const uint8_t *iv, size_t ivlen,
+                               const uint8_t *pn, size_t pnlen) {
   if (conn->tx_ckm) {
     return NGTCP2_ERR_INVALID_STATE;
   }
 
-  return ngtcp2_crypto_km_new(&conn->tx_ckm, key, keylen, iv, ivlen, conn->mem);
+  return ngtcp2_crypto_km_new(&conn->tx_ckm, key, keylen, iv, ivlen, pn, pnlen,
+                              conn->mem);
 }
 
 int ngtcp2_conn_update_rx_keys(ngtcp2_conn *conn, const uint8_t *key,
-                               size_t keylen, const uint8_t *iv, size_t ivlen) {
+                               size_t keylen, const uint8_t *iv, size_t ivlen,
+                               const uint8_t *pn, size_t pnlen) {
   if (conn->rx_ckm) {
     return NGTCP2_ERR_INVALID_STATE;
   }
 
-  return ngtcp2_crypto_km_new(&conn->rx_ckm, key, keylen, iv, ivlen, conn->mem);
+  return ngtcp2_crypto_km_new(&conn->rx_ckm, key, keylen, iv, ivlen, pn, pnlen,
+                              conn->mem);
 }
 
 ngtcp2_tstamp ngtcp2_conn_loss_detection_expiry(ngtcp2_conn *conn) {
diff --git a/lib/ngtcp2_conn.h b/lib/ngtcp2_conn.h
index 36f6bd36..0f9f1a2c 100644
--- a/lib/ngtcp2_conn.h
+++ b/lib/ngtcp2_conn.h
@@ -99,9 +99,6 @@ typedef enum {
    from flow control during handshake. */
 #define NGTCP2_MAX_HS_STREAM0_OFFSET 65536
 
-/* NGTCP2_MAX_PKT_NUM is the maximum packet number. */
-#define NGTCP2_MAX_PKT_NUM ((1llu << 62) - 1)
-
 struct ngtcp2_pkt_chain;
 typedef struct ngtcp2_pkt_chain ngtcp2_pkt_chain;
 
diff --git a/lib/ngtcp2_conv.c b/lib/ngtcp2_conv.c
index 81b76e7e..7101c655 100644
--- a/lib/ngtcp2_conv.c
+++ b/lib/ngtcp2_conv.c
@@ -97,6 +97,34 @@ uint64_t ngtcp2_get_varint(size_t *plen, const uint8_t *p) {
   assert(0);
 }
 
+uint64_t ngtcp2_get_pkt_num(size_t *plen, const uint8_t *p) {
+  union {
+    char b[4];
+    uint16_t n16;
+    uint32_t n32;
+  } n;
+
+  if ((*p >> 7) == 0) {
+    *plen = 1;
+    return *p;
+  }
+
+  switch (*p >> 6) {
+  case 2:
+    *plen = 2;
+    memcpy(&n, p, 2);
+    n.b[0] &= 0x3fu;
+    return ntohs(n.n16);
+  case 3:
+    *plen = 4;
+    memcpy(&n, p, 4);
+    n.b[0] &= 0x3fu;
+    return ntohl(n.n32);
+  }
+
+  assert(0);
+}
+
 uint8_t *ngtcp2_put_uint64be(uint8_t *p, uint64_t n) {
   n = bswap64(n);
   return ngtcp2_cpymem(p, (const uint8_t *)&n, sizeof(n));
@@ -155,10 +183,43 @@ uint8_t *ngtcp2_put_varint14(uint8_t *p, uint16_t n) {
   return rv;
 }
 
+uint8_t *ngtcp2_put_pkt_num(uint8_t *p, uint64_t pkt_num, size_t len) {
+  switch (len) {
+  case 1:
+    *p++ = (uint8_t)(pkt_num & ~0x80u);
+    return p;
+  case 2:
+    ngtcp2_put_uint16be(p, (uint16_t)pkt_num);
+    *p = (uint8_t)((*p & ~0xc0u) | 0x80u);
+    return p + 2;
+  case 4:
+    ngtcp2_put_uint32be(p, (uint32_t)pkt_num);
+    *p |= 0xc0u;
+    return p + 4;
+  default:
+    assert(0);
+  }
+}
+
 size_t ngtcp2_get_varint_len(const uint8_t *p) {
   return varintlen_def[*p >> 6];
 }
 
+size_t ngtcp2_get_pkt_num_len(const uint8_t *p) {
+  if ((*p >> 7) == 0) {
+    return 1;
+  }
+
+  switch (*p >> 6) {
+  case 2:
+    return 2;
+  case 3:
+    return 4;
+  default:
+    assert(0);
+  }
+}
+
 size_t ngtcp2_put_varint_len(uint64_t n) {
   if (n < 64) {
     return 1;
diff --git a/lib/ngtcp2_conv.h b/lib/ngtcp2_conv.h
index b6907908..4a15b18a 100644
--- a/lib/ngtcp2_conv.h
+++ b/lib/ngtcp2_conv.h
@@ -84,6 +84,13 @@ uint16_t ngtcp2_get_uint16(const uint8_t *p);
  */
 uint64_t ngtcp2_get_varint(size_t *plen, const uint8_t *p);
 
+/*
+ * ngtcp2_get_pkt_num reads packet number encoded in variable-length
+ * encoding from |p|, and returns it in host byte order.  The number
+ * of bytes read is stored in |*plen|.
+ */
+uint64_t ngtcp2_get_pkt_num(size_t *plen, const uint8_t *p);
+
 /*
  * ngtcp2_put_uint64be writes |n| in host byte order in |p| in network
  * byte order.  It returns the one beyond of the last written
@@ -133,12 +140,24 @@ uint8_t *ngtcp2_put_varint(uint8_t *p, uint64_t n);
  */
 uint8_t *ngtcp2_put_varint14(uint8_t *p, uint16_t n);
 
+/*
+ * ngtcp2_put_pkt_num encodes |pkt_num| using |len| bytes.  It
+ * returns the one beyond of the last written position.
+ */
+uint8_t *ngtcp2_put_pkt_num(uint8_t *p, uint64_t pkt_num, size_t len);
+
 /*
  * ngtcp2_get_varint_len returns the required number of bytes to read
  * variable-length integer starting at |p|.
  */
 size_t ngtcp2_get_varint_len(const uint8_t *p);
 
+/*
+ * ngtcp2_get_pkt_num_len returns the required number of bytes to read
+ * variable-length packet number starting at |p|.
+ */
+size_t ngtcp2_get_pkt_num_len(const uint8_t *p);
+
 /*
  * ngtcp2_put_varint_len returns the required number of bytes to
  * encode |n|.
diff --git a/lib/ngtcp2_crypto.c b/lib/ngtcp2_crypto.c
index 6181d8a2..4706951b 100644
--- a/lib/ngtcp2_crypto.c
+++ b/lib/ngtcp2_crypto.c
@@ -32,11 +32,11 @@
 
 int ngtcp2_crypto_km_new(ngtcp2_crypto_km **pckm, const uint8_t *key,
                          size_t keylen, const uint8_t *iv, size_t ivlen,
-                         ngtcp2_mem *mem) {
+                         const uint8_t *pn, size_t pnlen, ngtcp2_mem *mem) {
   size_t len;
   uint8_t *p;
 
-  len = sizeof(ngtcp2_crypto_km) + keylen + ivlen;
+  len = sizeof(ngtcp2_crypto_km) + keylen + ivlen + pnlen;
 
   *pckm = ngtcp2_mem_malloc(mem, len);
   if (*pckm == NULL) {
@@ -49,7 +49,10 @@ int ngtcp2_crypto_km_new(ngtcp2_crypto_km **pckm, const uint8_t *key,
   p = ngtcp2_cpymem(p, key, keylen);
   (*pckm)->iv = p;
   (*pckm)->ivlen = ivlen;
-  /*p = */ ngtcp2_cpymem(p, iv, ivlen);
+  p = ngtcp2_cpymem(p, iv, ivlen);
+  (*pckm)->pn = p;
+  (*pckm)->pnlen = pnlen;
+  /* p = */ ngtcp2_cpymem(p, pn, pnlen);
 
   return 0;
 }
diff --git a/lib/ngtcp2_crypto.h b/lib/ngtcp2_crypto.h
index ce3fa2f8..33e52c33 100644
--- a/lib/ngtcp2_crypto.h
+++ b/lib/ngtcp2_crypto.h
@@ -40,16 +40,22 @@
 /* NGTCP2_MAX_AEAD_OVERHEAD is expected maximum AEAD overhead. */
 #define NGTCP2_MAX_AEAD_OVERHEAD 16
 
+/* NGTCP2_PN_SAMPLELEN is the number bytes sampled when encoding a
+   packet number. */
+#define NGTCP2_PN_SAMPLELEN 16
+
 typedef struct {
   const uint8_t *key;
   size_t keylen;
   const uint8_t *iv;
   size_t ivlen;
+  const uint8_t *pn;
+  size_t pnlen;
 } ngtcp2_crypto_km;
 
 int ngtcp2_crypto_km_new(ngtcp2_crypto_km **pckm, const uint8_t *key,
                          size_t keylen, const uint8_t *iv, size_t ivlen,
-                         ngtcp2_mem *mem);
+                         const uint8_t *pn, size_t pnlen, ngtcp2_mem *mem);
 
 void ngtcp2_crypto_km_del(ngtcp2_crypto_km *ckm, ngtcp2_mem *mem);
 
@@ -58,6 +64,7 @@ typedef struct {
   size_t aead_overhead;
   ngtcp2_encrypt encrypt;
   ngtcp2_decrypt decrypt;
+  ngtcp2_encrypt_pn encrypt_pn;
   void *user_data;
 } ngtcp2_crypto_ctx;
 
diff --git a/lib/ngtcp2_log.c b/lib/ngtcp2_log.c
index 5c696288..2b98e6ce 100644
--- a/lib/ngtcp2_log.c
+++ b/lib/ngtcp2_log.c
@@ -170,24 +170,11 @@ static const char *strpkttype_long(uint8_t type) {
   }
 }
 
-static const char *strpkttype_short(uint8_t type) {
-  switch (type) {
-  case NGTCP2_PKT_01:
-    return "S01";
-  case NGTCP2_PKT_02:
-    return "S02";
-  case NGTCP2_PKT_03:
-    return "S03";
-  default:
-    return "(unknown)";
-  }
-}
-
 static const char *strpkttype(const ngtcp2_pkt_hd *hd) {
   if (hd->flags & NGTCP2_PKT_FLAG_LONG_FORM) {
     return strpkttype_long(hd->type);
   }
-  return strpkttype_short(hd->type);
+  return "Short";
 }
 
 static const char *strevent(ngtcp2_log_event ev) {
@@ -584,12 +571,13 @@ void ngtcp2_log_pkt_hd(ngtcp2_log *log, const ngtcp2_pkt_hd *hd) {
 
   ngtcp2_log_info(
       log, NGTCP2_LOG_EVENT_PKT,
-      "rx pkt dcid=0x%s scid=0x%s type=%s(0x%02x) payloadlen=%zu",
+      "rx pkt %" PRIu64 " dcid=0x%s scid=0x%s type=%s(0x%02x) len=%zu",
+      hd->pkt_num,
       (const char *)ngtcp2_encode_hex(dcid, hd->dcid.data, hd->dcid.datalen),
       (const char *)ngtcp2_encode_hex(scid, hd->scid.data, hd->scid.datalen),
       (hd->flags & NGTCP2_PKT_FLAG_LONG_FORM) ? strpkttype_long(hd->type)
-                                              : strpkttype_short(hd->type),
-      hd->type, hd->payloadlen);
+                                              : "Short",
+      hd->type, hd->len);
 }
 
 void ngtcp2_log_info(ngtcp2_log *log, ngtcp2_log_event ev, const char *fmt,
diff --git a/lib/ngtcp2_pkt.c b/lib/ngtcp2_pkt.c
index dbddad87..a5c234bd 100644
--- a/lib/ngtcp2_pkt.c
+++ b/lib/ngtcp2_pkt.c
@@ -34,7 +34,8 @@
 
 void ngtcp2_pkt_hd_init(ngtcp2_pkt_hd *hd, uint8_t flags, uint8_t type,
                         const ngtcp2_cid *dcid, const ngtcp2_cid *scid,
-                        uint64_t pkt_num, uint32_t version, size_t payloadlen) {
+                        uint64_t pkt_num, size_t pkt_numlen, uint32_t version,
+                        size_t len) {
   hd->flags = flags;
   hd->type = type;
   if (dcid) {
@@ -48,8 +49,9 @@ void ngtcp2_pkt_hd_init(ngtcp2_pkt_hd *hd, uint8_t flags, uint8_t type,
     ngtcp2_cid_zero(&hd->scid);
   }
   hd->pkt_num = pkt_num;
+  hd->pkt_numlen = pkt_numlen;
   hd->version = version;
-  hd->payloadlen = payloadlen;
+  hd->len = len;
 }
 
 ssize_t ngtcp2_pkt_decode_hd_long(ngtcp2_pkt_hd *dest, const uint8_t *pkt,
@@ -87,7 +89,7 @@ ssize_t ngtcp2_pkt_decode_hd_long(ngtcp2_pkt_hd *dest, const uint8_t *pkt,
     default:
       return NGTCP2_ERR_UNKNOWN_PKT_TYPE;
     }
-    len = NGTCP2_MIN_LONG_HEADERLEN;
+    len = NGTCP2_MIN_LONG_HEADERLEN - 1; /* Cut packet number field */
   }
 
   if (pktlen < len) {
@@ -111,9 +113,11 @@ ssize_t ngtcp2_pkt_decode_hd_long(ngtcp2_pkt_hd *dest, const uint8_t *pkt,
   }
 
   if (type != NGTCP2_PKT_VERSION_NEGOTIATION) {
+    /* Length */
     p = &pkt[6 + dcil + scil];
 
-    len += ngtcp2_get_varint_len(p) - 1;
+    n = ngtcp2_get_varint_len(p);
+    len += n - 1;
 
     if (pktlen < len) {
       return NGTCP2_ERR_INVALID_ARGUMENT;
@@ -123,6 +127,8 @@ ssize_t ngtcp2_pkt_decode_hd_long(ngtcp2_pkt_hd *dest, const uint8_t *pkt,
   dest->flags = NGTCP2_PKT_FLAG_LONG_FORM;
   dest->type = type;
   dest->version = version;
+  dest->pkt_num = 0;
+  dest->pkt_numlen = 0;
 
   p = &pkt[6];
 
@@ -131,16 +137,11 @@ ssize_t ngtcp2_pkt_decode_hd_long(ngtcp2_pkt_hd *dest, const uint8_t *pkt,
   ngtcp2_cid_init(&dest->scid, p, scil);
   p += scil;
 
-  if (type != NGTCP2_PKT_VERSION_NEGOTIATION) {
-    dest->payloadlen = ngtcp2_get_varint(&n, p);
-    p += n;
-
-    dest->pkt_num = ngtcp2_get_uint32(p);
-
-    p += sizeof(uint32_t);
+  if (type == NGTCP2_PKT_VERSION_NEGOTIATION) {
+    dest->len = 0;
   } else {
-    dest->payloadlen = 0;
-    dest->pkt_num = 0;
+    dest->len = ngtcp2_get_varint(&n, p);
+    p += n;
   }
 
   assert((size_t)(p - pkt) == len);
@@ -151,10 +152,13 @@ ssize_t ngtcp2_pkt_decode_hd_long(ngtcp2_pkt_hd *dest, const uint8_t *pkt,
 ssize_t ngtcp2_pkt_decode_hd_short(ngtcp2_pkt_hd *dest, const uint8_t *pkt,
                                    size_t pktlen, size_t dcidlen) {
   uint8_t flags = 0;
-  uint8_t type;
   size_t len = 1 + dcidlen;
   const uint8_t *p = pkt;
 
+  if (pktlen < len) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
   if (pkt[0] & NGTCP2_HEADER_FORM_BIT) {
     return NGTCP2_ERR_INVALID_ARGUMENT;
   }
@@ -168,28 +172,9 @@ ssize_t ngtcp2_pkt_decode_hd_short(ngtcp2_pkt_hd *dest, const uint8_t *pkt,
     return NGTCP2_ERR_INVALID_ARGUMENT;
   }
 
-  type = pkt[0] & NGTCP2_SHORT_TYPE_MASK;
-  switch (type) {
-  case NGTCP2_PKT_01:
-    ++len;
-    break;
-  case NGTCP2_PKT_02:
-    len += 2;
-    break;
-  case NGTCP2_PKT_03:
-    len += 4;
-    break;
-  default:
-    return NGTCP2_ERR_UNKNOWN_PKT_TYPE;
-  }
+  p = &pkt[1];
 
-  if (pktlen < len) {
-    return NGTCP2_ERR_INVALID_ARGUMENT;
-  }
-
-  ++p;
-
-  dest->type = type;
+  dest->type = NGTCP2_PKT_SHORT;
 
   ngtcp2_cid_init(&dest->dcid, p, dcidlen);
   p += dcidlen;
@@ -198,21 +183,13 @@ ssize_t ngtcp2_pkt_decode_hd_short(ngtcp2_pkt_hd *dest, const uint8_t *pkt,
      garbage. */
   ngtcp2_cid_zero(&dest->scid);
 
-  switch (type) {
-  case NGTCP2_PKT_01:
-    dest->pkt_num = *p;
-    break;
-  case NGTCP2_PKT_02:
-    dest->pkt_num = ngtcp2_get_uint16(p);
-    break;
-  case NGTCP2_PKT_03:
-    dest->pkt_num = ngtcp2_get_uint32(p);
-    break;
-  }
-
   dest->flags = flags;
   dest->version = 0;
-  dest->payloadlen = 0;
+  dest->len = 0;
+  dest->pkt_num = 0;
+  dest->pkt_numlen = 0;
+
+  assert((size_t)(p - pkt) == len);
 
   return (ssize_t)len;
 }
@@ -221,8 +198,9 @@ ssize_t ngtcp2_pkt_encode_hd_long(uint8_t *out, size_t outlen,
                                   const ngtcp2_pkt_hd *hd) {
   uint8_t *p;
   size_t len = NGTCP2_MIN_LONG_HEADERLEN + hd->dcid.datalen + hd->scid.datalen +
-               2 - 1 /* NGTCP2_MIN_LONG_HEADERLEN includes 1 byte for
-                        payloadlen */;
+               2 + hd->pkt_numlen -
+               2 /* NGTCP2_MIN_LONG_HEADERLEN includes 1 byte for len
+                        and 1 byte for packet number. */;
 
   if (outlen < len) {
     return NGTCP2_ERR_NOBUF;
@@ -248,8 +226,8 @@ ssize_t ngtcp2_pkt_encode_hd_long(uint8_t *out, size_t outlen,
   if (hd->scid.datalen) {
     p = ngtcp2_cpymem(p, hd->scid.data, hd->scid.datalen);
   }
-  p = ngtcp2_put_varint14(p, (uint16_t)hd->payloadlen);
-  p = ngtcp2_put_uint32be(p, (uint32_t)hd->pkt_num);
+  p = ngtcp2_put_varint14(p, (uint16_t)hd->len);
+  p = ngtcp2_put_pkt_num(p, hd->pkt_num, hd->pkt_numlen);
 
   assert((size_t)(p - out) == len);
 
@@ -259,21 +237,7 @@ ssize_t ngtcp2_pkt_encode_hd_long(uint8_t *out, size_t outlen,
 ssize_t ngtcp2_pkt_encode_hd_short(uint8_t *out, size_t outlen,
                                    const ngtcp2_pkt_hd *hd) {
   uint8_t *p;
-  size_t len = 1 + hd->dcid.datalen;
-
-  switch (hd->type) {
-  case NGTCP2_PKT_01:
-    ++len;
-    break;
-  case NGTCP2_PKT_02:
-    len += 2;
-    break;
-  case NGTCP2_PKT_03:
-    len += 4;
-    break;
-  default:
-    return NGTCP2_ERR_INVALID_ARGUMENT;
-  }
+  size_t len = 1 + hd->dcid.datalen + hd->pkt_numlen;
 
   if (outlen < len) {
     return NGTCP2_ERR_NOBUF;
@@ -281,7 +245,7 @@ ssize_t ngtcp2_pkt_encode_hd_short(uint8_t *out, size_t outlen,
 
   p = out;
 
-  *p = NGTCP2_THIRD_BIT | NGTCP2_FOURTH_BIT | hd->type;
+  *p = NGTCP2_THIRD_BIT | NGTCP2_FOURTH_BIT;
   if (hd->flags & NGTCP2_PKT_FLAG_KEY_PHASE) {
     *p |= NGTCP2_KEY_PHASE_BIT;
   }
@@ -292,19 +256,7 @@ ssize_t ngtcp2_pkt_encode_hd_short(uint8_t *out, size_t outlen,
     p = ngtcp2_cpymem(p, hd->dcid.data, hd->dcid.datalen);
   }
 
-  switch (hd->type) {
-  case NGTCP2_PKT_01:
-    *p++ = (uint8_t)hd->pkt_num;
-    break;
-  case NGTCP2_PKT_02:
-    p = ngtcp2_put_uint16be(p, (uint16_t)hd->pkt_num);
-    break;
-  case NGTCP2_PKT_03:
-    p = ngtcp2_put_uint32be(p, (uint32_t)hd->pkt_num);
-    break;
-  default:
-    assert(0);
-  }
+  p = ngtcp2_put_pkt_num(p, hd->pkt_num, hd->pkt_numlen);
 
   assert((size_t)(p - out) == len);
 
@@ -1570,7 +1522,8 @@ int ngtcp2_pkt_decode_stateless_reset(ngtcp2_pkt_stateless_reset *sr,
 
 uint64_t ngtcp2_pkt_adjust_pkt_num(uint64_t max_pkt_num, uint64_t pkt_num,
                                    size_t n) {
-  uint64_t k = max_pkt_num == UINT64_MAX ? max_pkt_num : max_pkt_num + 1;
+  uint64_t k =
+      max_pkt_num == NGTCP2_MAX_PKT_NUM ? max_pkt_num : max_pkt_num + 1;
   uint64_t u = k & ~((1llu << n) - 1);
   uint64_t a = u | pkt_num;
   uint64_t b = (u + (1llu << n)) | pkt_num;
diff --git a/lib/ngtcp2_pkt.h b/lib/ngtcp2_pkt.h
index cfcb4602..eafe3126 100644
--- a/lib/ngtcp2_pkt.h
+++ b/lib/ngtcp2_pkt.h
@@ -37,18 +37,14 @@
 #define NGTCP2_FOURTH_BIT 0x10
 #define NGTCP2_GQUIC_BIT 0x08
 #define NGTCP2_LONG_TYPE_MASK 0x7f
-#define NGTCP2_SHORT_TYPE_MASK 0x03
 
 /* NGTCP2_SR_TYPE is a Type field of Stateless Reset. */
 #define NGTCP2_SR_TYPE 0x1f
 
-/* NGTCP2_LONG_HEADERLEN is the length of long header */
-#define NGTCP2_LONG_HEADERLEN 17
-
 /* NGTCP2_MIN_LONG_HEADERLEN is the minimum length of long header.
    That is (1|TYPE)<1> + VERSION<4> + (DCIL|SCIL)<1> + PAYLOADLEN<1> +
-   PKN<4> */
-#define NGTCP2_MIN_LONG_HEADERLEN (1 + 4 + 1 + 1 + 4)
+   PKN<1> */
+#define NGTCP2_MIN_LONG_HEADERLEN (1 + 4 + 1 + 1 + 1)
 
 #define NGTCP2_STREAM_FIN_BIT 0x01
 #define NGTCP2_STREAM_LEN_BIT 0x02
@@ -67,14 +63,19 @@
    blocks which this library can create, or decode. */
 #define NGTCP2_MAX_ACK_BLKS 255
 
+/* NGTCP2_MAX_PKT_NUM is the maximum packet number. */
+#define NGTCP2_MAX_PKT_NUM ((1llu << 62) - 1)
+
 /*
  * ngtcp2_pkt_hd_init initializes |hd| with the given values.  If
  * |dcid| and/or |scid| is NULL, DCID and SCID of |hd| is empty
- * respectively.
+ * respectively.  |pkt_numlen| is the number of bytes used to encode
+ * |pkt_num| and either 1, 2, or 4.
  */
 void ngtcp2_pkt_hd_init(ngtcp2_pkt_hd *hd, uint8_t flags, uint8_t type,
                         const ngtcp2_cid *dcid, const ngtcp2_cid *scid,
-                        uint64_t pkt_num, uint32_t version, size_t payloadlen);
+                        uint64_t pkt_num, size_t pkt_numlen, uint32_t version,
+                        size_t len);
 
 /*
  * ngtcp2_pkt_encode_hd_long encodes |hd| as QUIC long header into
diff --git a/lib/ngtcp2_ppe.c b/lib/ngtcp2_ppe.c
index e1fe74ba..f3f7552b 100644
--- a/lib/ngtcp2_ppe.c
+++ b/lib/ngtcp2_ppe.c
@@ -31,14 +31,18 @@
 #include "ngtcp2_str.h"
 #include "ngtcp2_conv.h"
 #include "ngtcp2_conn.h"
+#include "ngtcp2_macro.h"
 
 void ngtcp2_ppe_init(ngtcp2_ppe *ppe, uint8_t *out, size_t outlen,
                      ngtcp2_crypto_ctx *cctx) {
   ngtcp2_buf_init(&ppe->buf, out, outlen);
 
   ppe->hdlen = 0;
-  ppe->payloadlen_offset = 0;
+  ppe->len_offset = 0;
+  ppe->pkt_num_offset = 0;
+  ppe->pkt_numlen = 0;
   ppe->pkt_num = 0;
+  ppe->sample_offset = 0;
   ppe->ctx = cctx;
 }
 
@@ -52,10 +56,15 @@ int ngtcp2_ppe_encode_hd(ngtcp2_ppe *ppe, const ngtcp2_pkt_hd *hd) {
   }
 
   if (hd->flags & NGTCP2_PKT_FLAG_LONG_FORM) {
-    ppe->payloadlen_offset = 1 + 4 + 1 + hd->dcid.datalen + hd->scid.datalen;
+    ppe->len_offset = 1 + 4 + 1 + hd->dcid.datalen + hd->scid.datalen;
+    ppe->pkt_num_offset = ppe->len_offset + 2;
+    ppe->sample_offset =
+        1 + 4 + 1 + hd->dcid.datalen + hd->scid.datalen + 2 + 4;
     rv = ngtcp2_pkt_encode_hd_long(
         buf->last, ngtcp2_buf_left(buf) - ctx->aead_overhead, hd);
   } else {
+    ppe->pkt_num_offset = 1 + hd->dcid.datalen;
+    ppe->sample_offset = 1 + hd->dcid.datalen + 4;
     rv = ngtcp2_pkt_encode_hd_short(
         buf->last, ngtcp2_buf_left(buf) - ctx->aead_overhead, hd);
   }
@@ -65,6 +74,7 @@ int ngtcp2_ppe_encode_hd(ngtcp2_ppe *ppe, const ngtcp2_pkt_hd *hd) {
 
   buf->last += rv;
 
+  ppe->pkt_numlen = hd->pkt_numlen;
   ppe->hdlen = (size_t)rv;
 
   ppe->pkt_num = hd->pkt_num;
@@ -93,7 +103,7 @@ int ngtcp2_ppe_encode_frame(ngtcp2_ppe *ppe, ngtcp2_frame *fr) {
 }
 
 ssize_t ngtcp2_ppe_final(ngtcp2_ppe *ppe, const uint8_t **ppkt) {
-  ssize_t rv;
+  ssize_t nwrite;
   ngtcp2_buf *buf = &ppe->buf;
   ngtcp2_crypto_ctx *ctx = ppe->ctx;
   ngtcp2_conn *conn = ctx->user_data;
@@ -101,23 +111,40 @@ ssize_t ngtcp2_ppe_final(ngtcp2_ppe *ppe, const uint8_t **ppkt) {
   size_t payloadlen = ngtcp2_buf_len(buf) - ppe->hdlen;
   size_t destlen = (size_t)(buf->end - buf->begin) - ppe->hdlen;
 
-  if (ppe->payloadlen_offset) {
-    ngtcp2_put_varint14(buf->begin + ppe->payloadlen_offset,
-                        (uint16_t)(payloadlen + ctx->aead_overhead));
+  assert(ppe->ctx->encrypt);
+  assert(ppe->ctx->encrypt_pn);
+
+  if (ppe->len_offset) {
+    ngtcp2_put_varint14(
+        buf->begin + ppe->len_offset,
+        (uint16_t)(payloadlen + ppe->pkt_numlen + ctx->aead_overhead));
   }
 
   ngtcp2_crypto_create_nonce(ppe->nonce, ctx->ckm->iv, ctx->ckm->ivlen,
                              ppe->pkt_num);
 
-  rv = ppe->ctx->encrypt(conn, payload, destlen, payload, payloadlen,
-                         ctx->ckm->key, ctx->ckm->keylen, ppe->nonce,
-                         ctx->ckm->ivlen, buf->begin, ppe->hdlen,
-                         conn->user_data);
-  if (rv < 0) {
+  nwrite = ppe->ctx->encrypt(conn, payload, destlen, payload, payloadlen,
+                             ctx->ckm->key, ctx->ckm->keylen, ppe->nonce,
+                             ctx->ckm->ivlen, buf->begin, ppe->hdlen,
+                             conn->user_data);
+  if (nwrite < 0) {
     return NGTCP2_ERR_CALLBACK_FAILURE;
   }
 
-  buf->last = payload + rv;
+  buf->last = payload + nwrite;
+
+  ppe->sample_offset =
+      ngtcp2_min(ppe->sample_offset, ngtcp2_buf_len(buf) - ctx->aead_overhead);
+
+  nwrite = ppe->ctx->encrypt_pn(
+      conn, buf->begin + ppe->pkt_num_offset, ppe->pkt_numlen,
+      buf->begin + ppe->pkt_num_offset, ppe->pkt_numlen, ctx->ckm->pn,
+      ctx->ckm->pnlen, buf->begin + ppe->sample_offset, NGTCP2_PN_SAMPLELEN,
+      conn->user_data);
+
+  if (nwrite < 0) {
+    return nwrite;
+  }
 
   if (ppkt != NULL) {
     *ppkt = buf->begin;
diff --git a/lib/ngtcp2_ppe.h b/lib/ngtcp2_ppe.h
index 842776ec..b569d0a8 100644
--- a/lib/ngtcp2_ppe.h
+++ b/lib/ngtcp2_ppe.h
@@ -42,8 +42,16 @@ typedef struct {
   ngtcp2_crypto_ctx *ctx;
   /* hdlen is the number of bytes for packet header written in buf. */
   size_t hdlen;
-  /* payloadlen_offset is the offset to payload length field. */
-  size_t payloadlen_offset;
+  /* len_offset is the offset to Length field. */
+  size_t len_offset;
+  /* pkt_num_offset is the offset to packet number field. */
+  size_t pkt_num_offset;
+  /* pkt_numlen is the number of bytes used to encode a packet
+     number */
+  size_t pkt_numlen;
+  /* sample_offset is the offset to sample for packet number
+     encryption. */
+  size_t sample_offset;
   /* pkt_num is the packet number written in buf. */
   uint64_t pkt_num;
   /* nonce is the buffer to store nonce.  It should be equal or longer
diff --git a/tests/ngtcp2_conn_test.c b/tests/ngtcp2_conn_test.c
index b7d1181d..2130f685 100644
--- a/tests/ngtcp2_conn_test.c
+++ b/tests/ngtcp2_conn_test.c
@@ -51,7 +51,7 @@ static ssize_t null_encrypt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
   (void)ad;
   (void)adlen;
   (void)user_data;
-  return (ssize_t)plaintextlen;
+  return (ssize_t)plaintextlen + NGTCP2_FAKE_AEAD_OVERHEAD;
 }
 
 static ssize_t null_decrypt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
@@ -71,8 +71,9 @@ static ssize_t null_decrypt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
   (void)adlen;
   (void)user_data;
   assert(destlen >= ciphertextlen);
-  memcpy(dest, ciphertext, ciphertextlen);
-  return (ssize_t)ciphertextlen;
+  assert(ciphertextlen >= NGTCP2_FAKE_AEAD_OVERHEAD);
+  memmove(dest, ciphertext, ciphertextlen - NGTCP2_FAKE_AEAD_OVERHEAD);
+  return (ssize_t)ciphertextlen - NGTCP2_FAKE_AEAD_OVERHEAD;
 }
 
 static ssize_t fail_decrypt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
@@ -95,8 +96,28 @@ static ssize_t fail_decrypt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
   return NGTCP2_ERR_TLS_DECRYPT;
 }
 
+static ssize_t null_encrypt_pn(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
+                               const uint8_t *ciphertext, size_t ciphertextlen,
+                               const uint8_t *key, size_t keylen,
+                               const uint8_t *nonce, size_t noncelen,
+                               void *user_data) {
+  (void)conn;
+  (void)dest;
+  (void)destlen;
+  (void)ciphertext;
+  (void)key;
+  (void)keylen;
+  (void)nonce;
+  (void)noncelen;
+  (void)user_data;
+  assert(destlen >= ciphertextlen);
+  memmove(dest, ciphertext, ciphertextlen);
+  return (ssize_t)ciphertextlen;
+}
+
 static uint8_t null_key[16];
 static uint8_t null_iv[16];
+static uint8_t null_pn[16];
 static uint8_t null_data[4096];
 
 typedef struct {
@@ -111,22 +132,14 @@ typedef struct {
 } my_user_data;
 
 static ssize_t send_client_initial(ngtcp2_conn *conn, uint32_t flags,
-                                   uint64_t *ppkt_num, const uint8_t **pdest,
+                                   const uint8_t **pdest, int initial,
                                    void *user_data) {
-  my_user_data *ud = user_data;
   (void)conn;
   (void)flags;
-
+  (void)initial;
+  (void)user_data;
   *pdest = null_data;
 
-  if (ppkt_num) {
-    if (ud) {
-      *ppkt_num = ++ud->pkt_num;
-    } else {
-      *ppkt_num = 1000000007;
-    }
-  }
-
   return 217;
 }
 
@@ -149,27 +162,24 @@ static int recv_client_initial(ngtcp2_conn *conn, const ngtcp2_cid *dcid,
 }
 
 static ssize_t send_server_handshake(ngtcp2_conn *conn, uint32_t flags,
-                                     uint64_t *ppkt_num, const uint8_t **pdest,
+                                     const uint8_t **pdest, int initial,
                                      void *user_data) {
   (void)conn;
   (void)flags;
+  (void)initial;
   (void)user_data;
   *pdest = null_data;
-  if (ppkt_num) {
-    *ppkt_num = 1000000009;
-  }
 
   return 218;
 }
 
 static ssize_t send_server_handshake_zero(ngtcp2_conn *conn, uint32_t flags,
-                                          uint64_t *ppkt_num,
-                                          const uint8_t **pdest,
+                                          const uint8_t **pdest, int initial,
                                           void *user_data) {
   (void)conn;
   (void)flags;
-  (void)ppkt_num;
   (void)pdest;
+  (void)initial;
   (void)user_data;
   return 0;
 }
@@ -278,21 +288,24 @@ static void setup_default_server(ngtcp2_conn **pconn) {
   memset(&cb, 0, sizeof(cb));
   cb.hs_decrypt = null_decrypt;
   cb.hs_encrypt = null_encrypt;
+  cb.hs_encrypt_pn = null_encrypt_pn;
   cb.decrypt = null_decrypt;
   cb.encrypt = null_encrypt;
+  cb.encrypt_pn = null_encrypt_pn;
   cb.recv_stream0_data = recv_stream0_data;
   server_default_settings(&settings);
 
   ngtcp2_conn_server_new(pconn, &dcid, &scid, NGTCP2_PROTO_VER_MAX, &cb,
                          &settings, NULL);
   ngtcp2_conn_set_handshake_tx_keys(*pconn, null_key, sizeof(null_key), null_iv,
-                                    sizeof(null_iv));
+                                    sizeof(null_iv), null_pn, sizeof(null_pn));
   ngtcp2_conn_set_handshake_rx_keys(*pconn, null_key, sizeof(null_key), null_iv,
-                                    sizeof(null_iv));
+                                    sizeof(null_iv), null_pn, sizeof(null_pn));
   ngtcp2_conn_update_tx_keys(*pconn, null_key, sizeof(null_key), null_iv,
-                             sizeof(null_iv));
+                             sizeof(null_iv), null_pn, sizeof(null_pn));
   ngtcp2_conn_update_rx_keys(*pconn, null_key, sizeof(null_key), null_iv,
-                             sizeof(null_iv));
+                             sizeof(null_iv), null_pn, sizeof(null_pn));
+  ngtcp2_conn_set_aead_overhead(*pconn, NGTCP2_FAKE_AEAD_OVERHEAD);
   (*pconn)->state = NGTCP2_CS_POST_HANDSHAKE;
   (*pconn)->remote_settings.max_stream_data = 64 * 1024;
   (*pconn)->remote_settings.max_streams_bidi = 0;
@@ -316,21 +329,24 @@ static void setup_default_client(ngtcp2_conn **pconn) {
   memset(&cb, 0, sizeof(cb));
   cb.hs_decrypt = null_decrypt;
   cb.hs_encrypt = null_encrypt;
+  cb.hs_encrypt_pn = null_encrypt_pn;
   cb.decrypt = null_decrypt;
   cb.encrypt = null_encrypt;
+  cb.encrypt_pn = null_encrypt_pn;
   cb.recv_stream0_data = recv_stream0_data;
   client_default_settings(&settings);
 
   ngtcp2_conn_client_new(pconn, &dcid, &scid, NGTCP2_PROTO_VER_MAX, &cb,
                          &settings, NULL);
   ngtcp2_conn_set_handshake_tx_keys(*pconn, null_key, sizeof(null_key), null_iv,
-                                    sizeof(null_iv));
+                                    sizeof(null_iv), null_pn, sizeof(null_pn));
   ngtcp2_conn_set_handshake_rx_keys(*pconn, null_key, sizeof(null_key), null_iv,
-                                    sizeof(null_iv));
+                                    sizeof(null_iv), null_pn, sizeof(null_pn));
   ngtcp2_conn_update_tx_keys(*pconn, null_key, sizeof(null_key), null_iv,
-                             sizeof(null_iv));
+                             sizeof(null_iv), null_pn, sizeof(null_pn));
   ngtcp2_conn_update_rx_keys(*pconn, null_key, sizeof(null_key), null_iv,
-                             sizeof(null_iv));
+                             sizeof(null_iv), null_pn, sizeof(null_pn));
+  ngtcp2_conn_set_aead_overhead(*pconn, NGTCP2_FAKE_AEAD_OVERHEAD);
   (*pconn)->state = NGTCP2_CS_POST_HANDSHAKE;
   (*pconn)->remote_settings.max_stream_data = 64 * 1024;
   (*pconn)->remote_settings.max_streams_bidi = 1;
@@ -357,15 +373,16 @@ static void setup_handshake_server(ngtcp2_conn **pconn) {
   cb.recv_stream0_data = recv_stream0_data;
   cb.hs_decrypt = null_decrypt;
   cb.hs_encrypt = null_encrypt;
+  cb.hs_encrypt_pn = null_encrypt_pn;
   cb.rand = genrand;
   server_default_settings(&settings);
 
   ngtcp2_conn_server_new(pconn, &dcid, &scid, NGTCP2_PROTO_VER_MAX, &cb,
                          &settings, NULL);
   ngtcp2_conn_set_handshake_tx_keys(*pconn, null_key, sizeof(null_key), null_iv,
-                                    sizeof(null_iv));
+                                    sizeof(null_iv), null_pn, sizeof(null_pn));
   ngtcp2_conn_set_handshake_rx_keys(*pconn, null_key, sizeof(null_key), null_iv,
-                                    sizeof(null_iv));
+                                    sizeof(null_iv), null_pn, sizeof(null_pn));
 }
 
 static void setup_handshake_client(ngtcp2_conn **pconn) {
@@ -381,14 +398,15 @@ static void setup_handshake_client(ngtcp2_conn **pconn) {
   cb.recv_stream0_data = recv_stream0_data;
   cb.hs_decrypt = null_decrypt;
   cb.hs_encrypt = null_encrypt;
+  cb.hs_encrypt_pn = null_encrypt_pn;
   client_default_settings(&settings);
 
   ngtcp2_conn_client_new(pconn, &rcid, &scid, NGTCP2_PROTO_VER_MAX, &cb,
                          &settings, NULL);
   ngtcp2_conn_set_handshake_tx_keys(*pconn, null_key, sizeof(null_key), null_iv,
-                                    sizeof(null_iv));
+                                    sizeof(null_iv), null_pn, sizeof(null_pn));
   ngtcp2_conn_set_handshake_rx_keys(*pconn, null_key, sizeof(null_key), null_iv,
-                                    sizeof(null_iv));
+                                    sizeof(null_iv), null_pn, sizeof(null_pn));
 }
 
 static void setup_early_server(ngtcp2_conn **pconn) {
@@ -405,19 +423,22 @@ static void setup_early_server(ngtcp2_conn **pconn) {
   cb.recv_stream0_data = recv_stream0_data;
   cb.hs_decrypt = null_decrypt;
   cb.hs_encrypt = null_encrypt;
+  cb.hs_encrypt_pn = null_encrypt_pn;
   cb.decrypt = null_decrypt;
   cb.encrypt = null_encrypt;
+  cb.encrypt_pn = null_encrypt_pn;
   cb.rand = genrand;
   server_default_settings(&settings);
 
   ngtcp2_conn_server_new(pconn, &dcid, &scid, NGTCP2_PROTO_VER_MAX, &cb,
                          &settings, NULL);
   ngtcp2_conn_set_handshake_tx_keys(*pconn, null_key, sizeof(null_key), null_iv,
-                                    sizeof(null_iv));
+                                    sizeof(null_iv), null_pn, sizeof(null_pn));
   ngtcp2_conn_set_handshake_rx_keys(*pconn, null_key, sizeof(null_key), null_iv,
-                                    sizeof(null_iv));
+                                    sizeof(null_iv), null_pn, sizeof(null_pn));
   ngtcp2_conn_update_early_keys(*pconn, null_key, sizeof(null_key), null_iv,
-                                sizeof(null_iv));
+                                sizeof(null_iv), null_pn, sizeof(null_pn));
+  ngtcp2_conn_set_aead_overhead(*pconn, NGTCP2_FAKE_AEAD_OVERHEAD);
   (*pconn)->remote_settings.max_stream_data = 64 * 1024;
   (*pconn)->remote_settings.max_streams_bidi = 0;
   (*pconn)->remote_settings.max_streams_uni = 1;
@@ -443,18 +464,21 @@ static void setup_early_client(ngtcp2_conn **pconn) {
   cb.recv_stream0_data = recv_stream0_data;
   cb.hs_decrypt = null_decrypt;
   cb.hs_encrypt = null_encrypt;
+  cb.hs_encrypt_pn = null_encrypt_pn;
   cb.decrypt = null_decrypt;
   cb.encrypt = null_encrypt;
+  cb.encrypt_pn = null_encrypt_pn;
   client_default_settings(&settings);
 
   ngtcp2_conn_client_new(pconn, &dcid, &scid, NGTCP2_PROTO_VER_MAX, &cb,
                          &settings, NULL);
   ngtcp2_conn_set_handshake_tx_keys(*pconn, null_key, sizeof(null_key), null_iv,
-                                    sizeof(null_iv));
+                                    sizeof(null_iv), null_pn, sizeof(null_pn));
   ngtcp2_conn_set_handshake_rx_keys(*pconn, null_key, sizeof(null_key), null_iv,
-                                    sizeof(null_iv));
+                                    sizeof(null_iv), null_pn, sizeof(null_pn));
   ngtcp2_conn_update_early_keys(*pconn, null_key, sizeof(null_key), null_iv,
-                                sizeof(null_iv));
+                                sizeof(null_iv), null_pn, sizeof(null_pn));
+  ngtcp2_conn_set_aead_overhead(*pconn, NGTCP2_FAKE_AEAD_OVERHEAD);
 
   params.initial_max_stream_data = 64 * 1024;
   params.initial_max_streams_bidi = 1;
@@ -1487,6 +1511,7 @@ void test_ngtcp2_conn_recv_conn_id_omitted(void) {
 
 void test_ngtcp2_conn_short_pkt_type(void) {
   ngtcp2_conn *conn;
+  ngtcp2_pkt_hd hd;
   uint8_t buf[2048];
   ssize_t spktlen;
   uint64_t stream_id;
@@ -1499,25 +1524,29 @@ void test_ngtcp2_conn_short_pkt_type(void) {
                                      null_data, 19, 1);
 
   CU_ASSERT(spktlen > 0);
-  CU_ASSERT(NGTCP2_PKT_01 == (buf[0] & NGTCP2_SHORT_TYPE_MASK));
+  CU_ASSERT(pkt_decode_hd_short(&hd, buf, (size_t)spktlen, conn->scid.datalen) >
+            0);
+  CU_ASSERT(1 == hd.pkt_numlen);
 
   ngtcp2_conn_del(conn);
 
-  /* 2 octet pkt num */
+  /* 2 octets pkt num */
   setup_default_client(&conn);
   conn->rtb.largest_acked_tx_pkt_num = 0x6afa2f;
-  conn->last_tx_pkt_num = 0x6b4263;
+  conn->last_tx_pkt_num = 0x6afd78;
 
   ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL);
   spktlen = ngtcp2_conn_write_stream(conn, buf, sizeof(buf), NULL, stream_id, 0,
                                      null_data, 19, 1);
 
   CU_ASSERT(spktlen > 0);
-  CU_ASSERT(NGTCP2_PKT_02 == (buf[0] & NGTCP2_SHORT_TYPE_MASK));
+  CU_ASSERT(pkt_decode_hd_short(&hd, buf, (size_t)spktlen, conn->scid.datalen) >
+            0);
+  CU_ASSERT(2 == hd.pkt_numlen);
 
   ngtcp2_conn_del(conn);
 
-  /* 3 octet pkt num */
+  /* 4 octets pkt num */
   setup_default_client(&conn);
   conn->rtb.largest_acked_tx_pkt_num = 0x6afa2f;
   conn->last_tx_pkt_num = 0x6bc106;
@@ -1527,63 +1556,73 @@ void test_ngtcp2_conn_short_pkt_type(void) {
                                      null_data, 19, 1);
 
   CU_ASSERT(spktlen > 0);
-  CU_ASSERT(NGTCP2_PKT_03 == (buf[0] & NGTCP2_SHORT_TYPE_MASK));
+  CU_ASSERT(pkt_decode_hd_short(&hd, buf, (size_t)spktlen, conn->scid.datalen) >
+            0);
+  CU_ASSERT(4 == hd.pkt_numlen);
 
   ngtcp2_conn_del(conn);
 
   /* 1 octet pkt num (largest)*/
   setup_default_client(&conn);
   conn->rtb.largest_acked_tx_pkt_num = 1;
-  conn->last_tx_pkt_num = 127;
+  conn->last_tx_pkt_num = 63;
 
   ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL);
   spktlen = ngtcp2_conn_write_stream(conn, buf, sizeof(buf), NULL, stream_id, 0,
                                      null_data, 19, 1);
 
   CU_ASSERT(spktlen > 0);
-  CU_ASSERT(NGTCP2_PKT_01 == (buf[0] & NGTCP2_SHORT_TYPE_MASK));
+  CU_ASSERT(pkt_decode_hd_short(&hd, buf, (size_t)spktlen, conn->scid.datalen) >
+            0);
+  CU_ASSERT(1 == hd.pkt_numlen);
 
   ngtcp2_conn_del(conn);
 
   /* 2 octet pkt num (shortest)*/
   setup_default_client(&conn);
   conn->rtb.largest_acked_tx_pkt_num = 1;
-  conn->last_tx_pkt_num = 128;
+  conn->last_tx_pkt_num = 64;
 
   ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL);
   spktlen = ngtcp2_conn_write_stream(conn, buf, sizeof(buf), NULL, stream_id, 0,
                                      null_data, 19, 1);
 
   CU_ASSERT(spktlen > 0);
-  CU_ASSERT(NGTCP2_PKT_02 == (buf[0] & NGTCP2_SHORT_TYPE_MASK));
+  CU_ASSERT(pkt_decode_hd_short(&hd, buf, (size_t)spktlen, conn->scid.datalen) >
+            0);
+  CU_ASSERT(2 == hd.pkt_numlen);
 
   ngtcp2_conn_del(conn);
 
   /* 2 octet pkt num (largest)*/
   setup_default_client(&conn);
   conn->rtb.largest_acked_tx_pkt_num = 1;
-  conn->last_tx_pkt_num = 32767;
+  conn->last_tx_pkt_num = 8191;
 
   ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL);
   spktlen = ngtcp2_conn_write_stream(conn, buf, sizeof(buf), NULL, stream_id, 0,
                                      null_data, 19, 1);
 
   CU_ASSERT(spktlen > 0);
-  CU_ASSERT(NGTCP2_PKT_02 == (buf[0] & NGTCP2_SHORT_TYPE_MASK));
+  CU_ASSERT(pkt_decode_hd_short(&hd, buf, (size_t)spktlen, conn->scid.datalen) >
+            0);
+  CU_ASSERT(2 == hd.pkt_numlen);
 
   ngtcp2_conn_del(conn);
 
-  /* 3 octet pkt num (shortest)*/
+  /* 4 octet pkt num (shortest)*/
   setup_default_client(&conn);
   conn->rtb.largest_acked_tx_pkt_num = 1;
-  conn->last_tx_pkt_num = 32768;
+  conn->last_tx_pkt_num = 8192;
 
   ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL);
   spktlen = ngtcp2_conn_write_stream(conn, buf, sizeof(buf), NULL, stream_id, 0,
                                      null_data, 19, 1);
 
   CU_ASSERT(spktlen > 0);
-  CU_ASSERT(NGTCP2_PKT_03 == (buf[0] & NGTCP2_SHORT_TYPE_MASK));
+  CU_ASSERT(pkt_decode_hd_short(&hd, buf, (size_t)spktlen, conn->scid.datalen) >
+            0);
+  CU_ASSERT(4 == hd.pkt_numlen);
 
   ngtcp2_conn_del(conn);
 
@@ -1597,7 +1636,9 @@ void test_ngtcp2_conn_short_pkt_type(void) {
                                      null_data, 19, 1);
 
   CU_ASSERT(spktlen > 0);
-  CU_ASSERT(NGTCP2_PKT_03 == (buf[0] & NGTCP2_SHORT_TYPE_MASK));
+  CU_ASSERT(pkt_decode_hd_short(&hd, buf, (size_t)spktlen, conn->scid.datalen) >
+            0);
+  CU_ASSERT(4 == hd.pkt_numlen);
 
   ngtcp2_conn_del(conn);
 }
@@ -1618,8 +1659,8 @@ void test_ngtcp2_conn_recv_stateless_reset(void) {
     token[i] = (uint8_t)~i;
   }
 
-  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_01, &dcid, NULL,
-                     0xe1, 0, 0);
+  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_SHORT, &dcid, NULL,
+                     0xe1, 1, 0, 0);
 
   /* server: Just ignore SR */
   setup_default_server(&conn);
@@ -1717,7 +1758,7 @@ void test_ngtcp2_conn_recv_server_stateless_retry(void) {
   spktlen = ngtcp2_conn_handshake(conn, buf, sizeof(buf), buf, pktlen, 2);
 
   CU_ASSERT(spktlen > 0);
-  CU_ASSERT(2 == conn->last_tx_pkt_num);
+  CU_ASSERT(1 == conn->last_tx_pkt_num);
 
   ngtcp2_conn_del(conn);
 }
diff --git a/tests/ngtcp2_pkt_test.c b/tests/ngtcp2_pkt_test.c
index 48ac343e..1d1c8b38 100644
--- a/tests/ngtcp2_pkt_test.c
+++ b/tests/ngtcp2_pkt_test.c
@@ -45,7 +45,7 @@ void test_ngtcp2_pkt_decode_hd_long(void) {
 
   /* Handshake */
   ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_LONG_FORM, NGTCP2_PKT_HANDSHAKE,
-                     &dcid, &scid, 0xe1e2e3e4u, 0x000000ff, 16383);
+                     &dcid, &scid, 0xe1e2e3e4u, 4, 0x000000ff, 16383);
 
   rv = ngtcp2_pkt_encode_hd_long(buf, sizeof(buf), &hd);
 
@@ -53,21 +53,21 @@ void test_ngtcp2_pkt_decode_hd_long(void) {
 
   CU_ASSERT((ssize_t)len == rv);
 
-  rv = ngtcp2_pkt_decode_hd_long(&nhd, buf, len);
+  rv = pkt_decode_hd_long(&nhd, buf, len);
 
   CU_ASSERT((ssize_t)len == rv);
   CU_ASSERT(hd.type == nhd.type);
   CU_ASSERT(hd.flags == nhd.flags);
   CU_ASSERT(ngtcp2_cid_eq(&hd.dcid, &nhd.dcid));
   CU_ASSERT(ngtcp2_cid_eq(&hd.scid, &nhd.scid));
-  CU_ASSERT(hd.pkt_num == nhd.pkt_num);
+  CU_ASSERT((hd.pkt_num & 0x3fffffffu) == nhd.pkt_num);
   CU_ASSERT(hd.version == nhd.version);
-  CU_ASSERT(hd.payloadlen == nhd.payloadlen);
+  CU_ASSERT(hd.len == nhd.len);
 
   /* VN */
   /* Set random packet type */
   ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_LONG_FORM, NGTCP2_PKT_HANDSHAKE,
-                     &dcid, &scid, 0, 0, 0);
+                     &dcid, &scid, 0, 4, 0, 0);
 
   rv = ngtcp2_pkt_encode_hd_long(buf, sizeof(buf), &hd);
 
@@ -75,7 +75,7 @@ void test_ngtcp2_pkt_decode_hd_long(void) {
 
   CU_ASSERT((ssize_t)len == rv - 2 /* payloadlen */ - 4 /* pkt_num */);
 
-  rv = ngtcp2_pkt_decode_hd_long(&nhd, buf, len);
+  rv = pkt_decode_hd_long(&nhd, buf, len);
 
   CU_ASSERT((ssize_t)len == rv);
   CU_ASSERT(NGTCP2_PKT_VERSION_NEGOTIATION == nhd.type);
@@ -84,7 +84,7 @@ void test_ngtcp2_pkt_decode_hd_long(void) {
   CU_ASSERT(ngtcp2_cid_eq(&hd.scid, &nhd.scid));
   CU_ASSERT(hd.pkt_num == nhd.pkt_num);
   CU_ASSERT(hd.version == nhd.version);
-  CU_ASSERT(hd.payloadlen == nhd.payloadlen);
+  CU_ASSERT(hd.len == nhd.len);
 }
 
 void test_ngtcp2_pkt_decode_hd_short(void) {
@@ -97,9 +97,9 @@ void test_ngtcp2_pkt_decode_hd_short(void) {
   dcid_init(&dcid);
   ngtcp2_cid_zero(&zcid);
 
-  /* NGTCP2_PKT_03 */
-  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_03, &dcid, NULL,
-                     0xe1e2e3e4u, 0xd1d2d3d4u, 0);
+  /* 4 bytes packet number */
+  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_SHORT, &dcid, NULL,
+                     0xe1e2e3e4u, 4, 0xd1d2d3d4u, 0);
 
   expectedlen = 1 + dcid.datalen + 4;
 
@@ -107,20 +107,21 @@ void test_ngtcp2_pkt_decode_hd_short(void) {
 
   CU_ASSERT((ssize_t)expectedlen == rv);
 
-  rv = ngtcp2_pkt_decode_hd_short(&nhd, buf, expectedlen, dcid.datalen);
+  rv = pkt_decode_hd_short(&nhd, buf, expectedlen, dcid.datalen);
 
   CU_ASSERT((ssize_t)expectedlen == rv);
   CU_ASSERT(hd.flags == nhd.flags);
-  CU_ASSERT(NGTCP2_PKT_03 == nhd.type);
+  CU_ASSERT(NGTCP2_PKT_SHORT == nhd.type);
   CU_ASSERT(ngtcp2_cid_eq(&dcid, &nhd.dcid));
   CU_ASSERT(ngtcp2_cid_empty(&nhd.scid));
-  CU_ASSERT(hd.pkt_num == nhd.pkt_num);
+  CU_ASSERT((hd.pkt_num & 0x3fffffffu) == nhd.pkt_num);
+  CU_ASSERT(hd.pkt_numlen == nhd.pkt_numlen);
   CU_ASSERT(0 == nhd.version);
-  CU_ASSERT(0 == nhd.payloadlen);
+  CU_ASSERT(0 == nhd.len);
 
-  /* NGTCP2_PKT_02 */
-  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_02, &dcid, NULL,
-                     0xe1e2e3e4u, 0xd1d2d3d4u, 0);
+  /* 2 bytes packet number */
+  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_SHORT, &dcid, NULL,
+                     0xe1e2e3e4u, 2, 0xd1d2d3d4u, 0);
 
   expectedlen = 1 + dcid.datalen + 2;
 
@@ -128,20 +129,21 @@ void test_ngtcp2_pkt_decode_hd_short(void) {
 
   CU_ASSERT((ssize_t)expectedlen == rv);
 
-  rv = ngtcp2_pkt_decode_hd_short(&nhd, buf, expectedlen, dcid.datalen);
+  rv = pkt_decode_hd_short(&nhd, buf, expectedlen, dcid.datalen);
 
   CU_ASSERT((ssize_t)expectedlen == rv);
   CU_ASSERT(hd.flags == nhd.flags);
-  CU_ASSERT(NGTCP2_PKT_02 == nhd.type);
+  CU_ASSERT(NGTCP2_PKT_SHORT == nhd.type);
   CU_ASSERT(ngtcp2_cid_eq(&dcid, &nhd.dcid));
   CU_ASSERT(ngtcp2_cid_empty(&nhd.scid));
-  CU_ASSERT((hd.pkt_num & 0xffff) == nhd.pkt_num);
+  CU_ASSERT((hd.pkt_num & 0x3fff) == nhd.pkt_num);
+  CU_ASSERT(hd.pkt_numlen == nhd.pkt_numlen);
   CU_ASSERT(0 == nhd.version);
-  CU_ASSERT(0 == nhd.payloadlen);
+  CU_ASSERT(0 == nhd.len);
 
-  /* NGTCP2_PKT_01 */
-  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_01, &dcid, NULL,
-                     0xe1e2e3e4u, 0xd1d2d3d4u, 0);
+  /* 1 byte packet number */
+  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_SHORT, &dcid, NULL,
+                     0xe1e2e3e4u, 1, 0xd1d2d3d4u, 0);
 
   expectedlen = 1 + dcid.datalen + 1;
 
@@ -149,20 +151,21 @@ void test_ngtcp2_pkt_decode_hd_short(void) {
 
   CU_ASSERT((ssize_t)expectedlen == rv);
 
-  rv = ngtcp2_pkt_decode_hd_short(&nhd, buf, expectedlen, dcid.datalen);
+  rv = pkt_decode_hd_short(&nhd, buf, expectedlen, dcid.datalen);
 
   CU_ASSERT((ssize_t)expectedlen == rv);
   CU_ASSERT(hd.flags == nhd.flags);
-  CU_ASSERT(NGTCP2_PKT_01 == nhd.type);
+  CU_ASSERT(NGTCP2_PKT_SHORT == nhd.type);
   CU_ASSERT(ngtcp2_cid_eq(&dcid, &nhd.dcid));
   CU_ASSERT(ngtcp2_cid_empty(&nhd.scid));
-  CU_ASSERT((hd.pkt_num & 0xff) == nhd.pkt_num);
+  CU_ASSERT((hd.pkt_num & 0x7f) == nhd.pkt_num);
+  CU_ASSERT(hd.pkt_numlen == nhd.pkt_numlen);
   CU_ASSERT(0 == nhd.version);
-  CU_ASSERT(0 == nhd.payloadlen);
+  CU_ASSERT(0 == nhd.len);
 
   /* With Key Phase */
-  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_KEY_PHASE, NGTCP2_PKT_03, &dcid, NULL,
-                     0xe1e2e3e4u, 0xd1d2d3d4u, 0);
+  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_KEY_PHASE, NGTCP2_PKT_SHORT, &dcid,
+                     NULL, 0xe1e2e3e4u, 4, 0xd1d2d3d4u, 0);
 
   expectedlen = 1 + dcid.datalen + 4;
 
@@ -170,20 +173,21 @@ void test_ngtcp2_pkt_decode_hd_short(void) {
 
   CU_ASSERT((ssize_t)expectedlen == rv);
 
-  rv = ngtcp2_pkt_decode_hd_short(&nhd, buf, expectedlen, dcid.datalen);
+  rv = pkt_decode_hd_short(&nhd, buf, expectedlen, dcid.datalen);
 
   CU_ASSERT((ssize_t)expectedlen == rv);
   CU_ASSERT(hd.flags == nhd.flags);
-  CU_ASSERT(NGTCP2_PKT_03 == nhd.type);
+  CU_ASSERT(NGTCP2_PKT_SHORT == nhd.type);
   CU_ASSERT(ngtcp2_cid_eq(&dcid, &nhd.dcid));
   CU_ASSERT(ngtcp2_cid_empty(&nhd.scid));
-  CU_ASSERT(hd.pkt_num == nhd.pkt_num);
+  CU_ASSERT((hd.pkt_num & 0x3fffffff) == nhd.pkt_num);
+  CU_ASSERT(hd.pkt_numlen == nhd.pkt_numlen);
   CU_ASSERT(0 == nhd.version);
-  CU_ASSERT(0 == nhd.payloadlen);
+  CU_ASSERT(0 == nhd.len);
 
   /* With empty DCID */
-  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_03, NULL, NULL,
-                     0xe1e2e3e4u, 0xd1d2d3d4u, 0);
+  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_SHORT, NULL, NULL,
+                     0xe1e2e3e4u, 4, 0xd1d2d3d4u, 0);
 
   expectedlen = 1 + 4;
 
@@ -191,16 +195,17 @@ void test_ngtcp2_pkt_decode_hd_short(void) {
 
   CU_ASSERT((ssize_t)expectedlen == rv);
 
-  rv = ngtcp2_pkt_decode_hd_short(&nhd, buf, expectedlen, 0);
+  rv = pkt_decode_hd_short(&nhd, buf, expectedlen, 0);
 
   CU_ASSERT((ssize_t)expectedlen == rv);
   CU_ASSERT(hd.flags == nhd.flags);
-  CU_ASSERT(NGTCP2_PKT_03 == nhd.type);
+  CU_ASSERT(NGTCP2_PKT_SHORT == nhd.type);
   CU_ASSERT(ngtcp2_cid_empty(&nhd.dcid));
   CU_ASSERT(ngtcp2_cid_empty(&nhd.scid));
-  CU_ASSERT(hd.pkt_num == nhd.pkt_num);
+  CU_ASSERT((hd.pkt_num & 0x3fffffff) == nhd.pkt_num);
+  CU_ASSERT(hd.pkt_numlen == nhd.pkt_numlen);
   CU_ASSERT(0 == nhd.version);
-  CU_ASSERT(0 == nhd.payloadlen);
+  CU_ASSERT(0 == nhd.len);
 
 }
 
@@ -884,8 +889,8 @@ void test_ngtcp2_pkt_adjust_pkt_num(void) {
   CU_ASSERT(0x01ff == ngtcp2_pkt_adjust_pkt_num(0x0100, 0xff, 8));
   CU_ASSERT(0x02ff == ngtcp2_pkt_adjust_pkt_num(0x01ff, 0xff, 8));
 
-  CU_ASSERT(0xffffffffffffffabllu ==
-            ngtcp2_pkt_adjust_pkt_num(0xffffffffffffffffllu, 0xab, 8));
+  CU_ASSERT(0x3fffffffffffffabllu ==
+            ngtcp2_pkt_adjust_pkt_num(NGTCP2_MAX_PKT_NUM, 0xab, 8));
 }
 
 void test_ngtcp2_pkt_validate_ack(void) {
@@ -945,16 +950,16 @@ void test_ngtcp2_pkt_write_stateless_reset(void) {
     token[i] = (uint8_t)(i + 1);
   }
 
-  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_KEY_PHASE, NGTCP2_PKT_01, &dcid, NULL,
-                     0xf1, 0, 0);
+  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_KEY_PHASE, NGTCP2_PKT_SHORT, &dcid,
+                     NULL, 0xf1, 1, 0, 0);
   spktlen = ngtcp2_pkt_write_stateless_reset(buf, sizeof(buf), &hd, token, rand,
                                              sizeof(rand));
 
   p = buf;
 
   CU_ASSERT(256 == spktlen);
-  CU_ASSERT((NGTCP2_KEY_PHASE_BIT | NGTCP2_THIRD_BIT | NGTCP2_FOURTH_BIT |
-             NGTCP2_PKT_01) == *p);
+  CU_ASSERT((NGTCP2_KEY_PHASE_BIT | NGTCP2_THIRD_BIT | NGTCP2_FOURTH_BIT) ==
+            *p);
 
   ++p;
 
@@ -962,7 +967,7 @@ void test_ngtcp2_pkt_write_stateless_reset(void) {
 
   p += dcid.datalen;
 
-  CU_ASSERT(0xf1 == *p);
+  CU_ASSERT((0xf1 & 0x7f) == *p);
 
   ++p;
 
@@ -979,8 +984,8 @@ void test_ngtcp2_pkt_write_stateless_reset(void) {
   CU_ASSERT(spktlen == p - buf);
 
   /* Not enough buffer */
-  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_02, &dcid, NULL,
-                     0xf1, 0, 0);
+  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_SHORT, &dcid, NULL,
+                     0xf1, 2, 0, 0);
   spktlen = ngtcp2_pkt_write_stateless_reset(
       buf, 1 + dcid.datalen + 2 + NGTCP2_STATELESS_RESET_TOKENLEN - 1, &hd,
       token, rand, sizeof(rand));
diff --git a/tests/ngtcp2_rtb_test.c b/tests/ngtcp2_rtb_test.c
index 13a1801a..0325c182 100644
--- a/tests/ngtcp2_rtb_test.c
+++ b/tests/ngtcp2_rtb_test.c
@@ -45,8 +45,8 @@ void test_ngtcp2_rtb_add(void) {
   ngtcp2_log_init(&log, NULL, NULL, 0, NULL);
   ngtcp2_rtb_init(&rtb, &log, mem);
 
-  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_01, &dcid, NULL,
-                     1000000007, NGTCP2_PROTO_VER_MAX, 0);
+  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_SHORT, &dcid, NULL,
+                     1000000007, 1, NGTCP2_PROTO_VER_MAX, 0);
 
   rv = ngtcp2_rtb_entry_new(&ent, &hd, NULL, 10, 0, NGTCP2_RTB_FLAG_NONE, mem);
 
@@ -54,8 +54,8 @@ void test_ngtcp2_rtb_add(void) {
 
   ngtcp2_rtb_add(&rtb, ent);
 
-  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_02, &dcid, NULL,
-                     1000000008, NGTCP2_PROTO_VER_MAX, 0);
+  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_SHORT, &dcid, NULL,
+                     1000000008, 2, NGTCP2_PROTO_VER_MAX, 0);
 
   rv = ngtcp2_rtb_entry_new(&ent, &hd, NULL, 9, 0, NGTCP2_RTB_FLAG_NONE, mem);
 
@@ -63,8 +63,8 @@ void test_ngtcp2_rtb_add(void) {
 
   ngtcp2_rtb_add(&rtb, ent);
 
-  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_03, &dcid, NULL,
-                     1000000009, NGTCP2_PROTO_VER_MAX, 0);
+  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_SHORT, &dcid, NULL,
+                     1000000009, 4, NGTCP2_PROTO_VER_MAX, 0);
 
   rv = ngtcp2_rtb_entry_new(&ent, &hd, NULL, 11, 0, NGTCP2_RTB_FLAG_NONE, mem);
 
@@ -105,8 +105,8 @@ static void add_rtb_entry_range(ngtcp2_rtb *rtb, uint64_t base_pkt_num,
   dcid_init(&dcid);
 
   for (i = base_pkt_num; i < base_pkt_num + len; ++i) {
-    ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_01, &dcid, NULL, i,
-                       NGTCP2_PROTO_VER_MAX, 0);
+    ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_SHORT, &dcid, NULL,
+                       i, 1, NGTCP2_PROTO_VER_MAX, 0);
     ngtcp2_rtb_entry_new(&ent, &hd, NULL, 0, 0, NGTCP2_RTB_FLAG_NONE, mem);
     ngtcp2_rtb_add(rtb, ent);
   }
@@ -276,23 +276,23 @@ void test_ngtcp2_rtb_insert_range(void) {
   ngtcp2_rtb_init(&rtb, &log, mem);
 
   ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_HANDSHAKE, &dcid,
-                     &scid, 900, NGTCP2_PROTO_VER_MAX, 0);
+                     &scid, 900, 4, NGTCP2_PROTO_VER_MAX, 0);
   ngtcp2_rtb_entry_new(&ent1, &hd, NULL, 0, 1, NGTCP2_RTB_FLAG_NONE, mem);
 
   ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_HANDSHAKE, &dcid,
-                     &scid, 898, NGTCP2_PROTO_VER_MAX, 0);
+                     &scid, 898, 4, NGTCP2_PROTO_VER_MAX, 0);
   ngtcp2_rtb_entry_new(&ent2, &hd, NULL, 0, 2, NGTCP2_RTB_FLAG_NONE, mem);
 
   ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_HANDSHAKE, &dcid,
-                     &scid, 897, NGTCP2_PROTO_VER_MAX, 0);
+                     &scid, 897, 4, NGTCP2_PROTO_VER_MAX, 0);
   ngtcp2_rtb_entry_new(&ent3, &hd, NULL, 0, 4, NGTCP2_RTB_FLAG_NONE, mem);
 
   ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_HANDSHAKE, &dcid,
-                     &scid, 790, NGTCP2_PROTO_VER_MAX, 0);
+                     &scid, 790, 4, NGTCP2_PROTO_VER_MAX, 0);
   ngtcp2_rtb_entry_new(&ent4, &hd, NULL, 0, 8, NGTCP2_RTB_FLAG_NONE, mem);
 
   ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_HANDSHAKE, &dcid,
-                     &scid, 788, NGTCP2_PROTO_VER_MAX, 0);
+                     &scid, 788, 4, NGTCP2_PROTO_VER_MAX, 0);
   ngtcp2_rtb_entry_new(&ent5, &hd, NULL, 0, 16, NGTCP2_RTB_FLAG_NONE, mem);
 
   head = ent1;
@@ -302,17 +302,17 @@ void test_ngtcp2_rtb_insert_range(void) {
   ent4->next = ent5;
 
   ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_HANDSHAKE, &dcid,
-                     &scid, 896, NGTCP2_PROTO_VER_MAX, 0);
+                     &scid, 896, 4, NGTCP2_PROTO_VER_MAX, 0);
   ngtcp2_rtb_entry_new(&ent, &hd, NULL, 0, 0, NGTCP2_RTB_FLAG_NONE, mem);
   ngtcp2_rtb_add(&rtb, ent);
 
   ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_HANDSHAKE, &dcid,
-                     &scid, 899, NGTCP2_PROTO_VER_MAX, 0);
+                     &scid, 899, 4, NGTCP2_PROTO_VER_MAX, 0);
   ngtcp2_rtb_entry_new(&ent, &hd, NULL, 0, 0, NGTCP2_RTB_FLAG_NONE, mem);
   ngtcp2_rtb_add(&rtb, ent);
 
   ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_HANDSHAKE, &dcid,
-                     &scid, 901, NGTCP2_PROTO_VER_MAX, 0);
+                     &scid, 901, 4, NGTCP2_PROTO_VER_MAX, 0);
   ngtcp2_rtb_entry_new(&ent, &hd, NULL, 0, 0, NGTCP2_RTB_FLAG_NONE, mem);
   ngtcp2_rtb_add(&rtb, ent);
 
diff --git a/tests/ngtcp2_test_helper.c b/tests/ngtcp2_test_helper.c
index 2b55dfdc..e218ab19 100644
--- a/tests/ngtcp2_test_helper.c
+++ b/tests/ngtcp2_test_helper.c
@@ -97,6 +97,23 @@ static ssize_t null_encrypt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
   (void)ad;
   (void)adlen;
   (void)user_data;
+  return (ssize_t)plaintextlen + NGTCP2_FAKE_AEAD_OVERHEAD;
+}
+
+static ssize_t null_encrypt_pn(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
+                               const uint8_t *plaintext, size_t plaintextlen,
+                               const uint8_t *key, size_t keylen,
+                               const uint8_t *nonce, size_t noncelen,
+                               void *user_data) {
+  (void)conn;
+  (void)dest;
+  (void)destlen;
+  (void)plaintext;
+  (void)key;
+  (void)keylen;
+  (void)nonce;
+  (void)noncelen;
+  (void)user_data;
   return (ssize_t)plaintextlen;
 }
 
@@ -111,11 +128,13 @@ size_t write_single_frame_pkt(ngtcp2_conn *conn, uint8_t *out, size_t outlen,
 
   memset(&ctx, 0, sizeof(ctx));
   ctx.encrypt = null_encrypt;
+  ctx.encrypt_pn = null_encrypt_pn;
   ctx.ckm = conn->rx_ckm;
+  ctx.aead_overhead = NGTCP2_FAKE_AEAD_OVERHEAD;
   ctx.user_data = conn;
 
-  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_03, dcid, NULL,
-                     pkt_num, NGTCP2_PROTO_VER_MAX, 0);
+  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_SHORT, dcid, NULL,
+                     pkt_num, 4, NGTCP2_PROTO_VER_MAX, 0);
 
   ngtcp2_ppe_init(&ppe, out, outlen, &ctx);
   rv = ngtcp2_ppe_encode_hd(&ppe, &hd);
@@ -138,11 +157,13 @@ size_t write_single_frame_pkt_without_conn_id(ngtcp2_conn *conn, uint8_t *out,
 
   memset(&ctx, 0, sizeof(ctx));
   ctx.encrypt = null_encrypt;
+  ctx.encrypt_pn = null_encrypt_pn;
   ctx.ckm = conn->rx_ckm;
+  ctx.aead_overhead = NGTCP2_FAKE_AEAD_OVERHEAD;
   ctx.user_data = conn;
 
-  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_03, NULL, NULL,
-                     pkt_num, NGTCP2_PROTO_VER_MAX, 0);
+  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_SHORT, NULL, NULL,
+                     pkt_num, 4, NGTCP2_PROTO_VER_MAX, 0);
 
   ngtcp2_ppe_init(&ppe, out, outlen, &ctx);
   rv = ngtcp2_ppe_encode_hd(&ppe, &hd);
@@ -168,11 +189,13 @@ size_t write_single_frame_handshake_pkt(ngtcp2_conn *conn, uint8_t *out,
 
   memset(&ctx, 0, sizeof(ctx));
   ctx.encrypt = null_encrypt;
+  ctx.encrypt_pn = null_encrypt_pn;
   ctx.ckm = conn->hs_rx_ckm;
+  ctx.aead_overhead = NGTCP2_HANDSHAKE_AEAD_OVERHEAD;
   ctx.user_data = conn;
 
   ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_LONG_FORM, pkt_type, dcid, scid,
-                     pkt_num, version, 0);
+                     pkt_num, 4, version, 0);
 
   ngtcp2_ppe_init(&ppe, out, outlen, &ctx);
   rv = ngtcp2_ppe_encode_hd(&ppe, &hd);
@@ -198,11 +221,13 @@ size_t write_handshake_pkt(ngtcp2_conn *conn, uint8_t *out, size_t outlen,
 
   memset(&ctx, 0, sizeof(ctx));
   ctx.encrypt = null_encrypt;
+  ctx.encrypt_pn = null_encrypt_pn;
   ctx.ckm = conn->hs_rx_ckm;
+  ctx.aead_overhead = NGTCP2_HANDSHAKE_AEAD_OVERHEAD;
   ctx.user_data = conn;
 
   ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_LONG_FORM, pkt_type, dcid, scid,
-                     pkt_num, version, 0);
+                     pkt_num, 4, version, 0);
 
   ngtcp2_ppe_init(&ppe, out, outlen, &ctx);
   rv = ngtcp2_ppe_encode_hd(&ppe, &hd);
@@ -274,3 +299,57 @@ void write_pkt_payloadlen(uint8_t *pkt, const ngtcp2_cid *dcid,
   ngtcp2_put_varint14(&pkt[1 + 4 + 1 + dcid->datalen + scid->datalen],
                       (uint16_t)payloadlen);
 }
+
+ssize_t pkt_decode_hd_long(ngtcp2_pkt_hd *dest, const uint8_t *pkt,
+                           size_t pktlen) {
+  const uint8_t *p;
+  size_t n;
+  ssize_t nread;
+
+  nread = ngtcp2_pkt_decode_hd_long(dest, pkt, pktlen);
+  if (nread < 0 || dest->type == NGTCP2_PKT_VERSION_NEGOTIATION) {
+    return nread;
+  }
+
+  if ((size_t)nread == pktlen) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  p = pkt + nread;
+
+  n = ngtcp2_get_pkt_num_len(p);
+  if (pktlen < (size_t)nread + n) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  dest->pkt_num = ngtcp2_get_pkt_num(&dest->pkt_numlen, p);
+
+  return nread + (ssize_t)n;
+}
+
+ssize_t pkt_decode_hd_short(ngtcp2_pkt_hd *dest, const uint8_t *pkt,
+                            size_t pktlen, size_t dcidlen) {
+  const uint8_t *p;
+  size_t n;
+  ssize_t nread;
+
+  nread = ngtcp2_pkt_decode_hd_short(dest, pkt, pktlen, dcidlen);
+  if (nread < 0) {
+    return nread;
+  }
+
+  if ((size_t)nread == pktlen) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  p = pkt + nread;
+
+  n = ngtcp2_get_pkt_num_len(p);
+  if (pktlen < (size_t)nread + n) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  dest->pkt_num = ngtcp2_get_pkt_num(&dest->pkt_numlen, p);
+
+  return nread + (ssize_t)n;
+}
diff --git a/tests/ngtcp2_test_helper.h b/tests/ngtcp2_test_helper.h
index 2d32475c..f1bad175 100644
--- a/tests/ngtcp2_test_helper.h
+++ b/tests/ngtcp2_test_helper.h
@@ -50,6 +50,14 @@
 #define NGTCP2_APP_ERR01 0xff01u
 #define NGTCP2_APP_ERR02 0xff02u
 
+/*
+ * NGTCP2_FAKE_AEAD_OVERHEAD is AEAD overhead used in unit tests.
+ * Because we use the same encryption/decryption function for both
+ * handshake and post handshake packets, we have to use AEAD overhead
+ * used in handshake packets.
+ */
+#define NGTCP2_FAKE_AEAD_OVERHEAD NGTCP2_HANDSHAKE_AEAD_OVERHEAD
+
 /*
  * ngtcp2_t_encode_stream_frame encodes STREAM frame into |out| with
  * the given parameters.  If NGTCP2_STREAM_LEN_BIT is set in |flags|,
@@ -146,4 +154,20 @@ uint64_t read_pkt_payloadlen(const uint8_t *pkt, const ngtcp2_cid *dcid,
 void write_pkt_payloadlen(uint8_t *pkt, const ngtcp2_cid *dcid,
                           const ngtcp2_cid *scid, uint64_t payloadlen);
 
+/*
+ * pkt_decode_hd_long decodes long packet header from |pkt| of length
+ * |pktlen|.  This function assumes that packte number field has been
+ * decrypted.
+ */
+ssize_t pkt_decode_hd_long(ngtcp2_pkt_hd *dest, const uint8_t *pkt,
+                           size_t pktlen);
+
+/*
+ * pkt_decode_hd_short decodes long packet header from |pkt| of length
+ * |pktlen|.  This function assumes that packte number field has been
+ * decrypted.
+ */
+ssize_t pkt_decode_hd_short(ngtcp2_pkt_hd *dest, const uint8_t *pkt,
+                            size_t pktlen, size_t dcidlen);
+
 #endif /* NGTCP2_TEST_HELPER_H */
-- 
GitLab