diff --git a/include/picotls.h b/include/picotls.h
index e6864ac10..921a72324 100644
--- a/include/picotls.h
+++ b/include/picotls.h
@@ -22,6 +22,10 @@
 #ifndef picotls_h
 #define picotls_h
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 #include <assert.h>
 #include <inttypes.h>
 #include <sys/types.h>
@@ -766,7 +770,7 @@ inline ptls_iovec_t ptls_iovec_init(const void *p, size_t len)
 inline void ptls_buffer_init(ptls_buffer_t *buf, void *smallbuf, size_t smallbuf_size)
 {
     assert(smallbuf != NULL);
-    buf->base = smallbuf;
+    buf->base = (uint8_t *)smallbuf;
     buf->off = 0;
     buf->capacity = smallbuf_size;
     buf->is_allocated = 0;
@@ -807,4 +811,8 @@ inline size_t ptls_aead_decrypt(ptls_aead_context_t *ctx, void *output, const vo
 
 int ptls_load_certificates(ptls_context_t *ctx, char *cert_pem_file);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif
diff --git a/lib/picotls.c b/lib/picotls.c
index c6eb81517..1facda18c 100644
--- a/lib/picotls.c
+++ b/lib/picotls.c
@@ -231,6 +231,7 @@ struct st_ptls_client_hello_t {
         ptls_iovec_t tbs;
         ptls_iovec_t ch1_hash;
         ptls_iovec_t signature;
+        unsigned sent_key_share : 1;
     } cookie;
     struct {
         const uint8_t *hash_end;
@@ -468,40 +469,74 @@ static int aead_decrypt(struct st_ptls_traffic_protection_t *ctx, void *output,
     return 0;
 }
 
+#define buffer_push_record(buf, type, block)                                                                                       \
+    do {                                                                                                                           \
+        ptls_buffer_push((buf), (type), PTLS_RECORD_VERSION_MAJOR, PTLS_RECORD_VERSION_MINOR);                                     \
+        ptls_buffer_push_block((buf), 2, block);                                                                                   \
+    } while (0)
+
+static int buffer_push_encrypted_records(ptls_buffer_t *buf, uint8_t type, const uint8_t *src, size_t len,
+                                         struct st_ptls_traffic_protection_t *enc)
+{
+    int ret = 0;
+
+    while (len != 0) {
+        size_t chunk_size = len;
+        if (chunk_size > PTLS_MAX_PLAINTEXT_RECORD_SIZE)
+            chunk_size = PTLS_MAX_PLAINTEXT_RECORD_SIZE;
+        buffer_push_record(buf, PTLS_CONTENT_TYPE_APPDATA, {
+            if ((ret = ptls_buffer_reserve(buf, chunk_size + enc->aead->algo->tag_size + 1)) != 0)
+                goto Exit;
+            buf->off += aead_encrypt(enc, buf->base + buf->off, src, chunk_size, type);
+        });
+        src += chunk_size;
+        len -= chunk_size;
+    }
+
+Exit:
+    return ret;
+}
+
 static int buffer_encrypt_record(ptls_buffer_t *buf, size_t rec_start, struct st_ptls_traffic_protection_t *enc)
 {
-    uint8_t encrypted[PTLS_MAX_ENCRYPTED_RECORD_SIZE];
-    size_t enclen, bodylen = buf->off - rec_start - 5;
+    size_t bodylen = buf->off - rec_start - 5;
+    uint8_t *tmpbuf, type = buf->base[rec_start];
     int ret;
 
-    assert(bodylen <= PTLS_MAX_PLAINTEXT_RECORD_SIZE);
+    /* fast path: do in-place encryption if only one record needs to be emitted */
+    if (bodylen <= PTLS_MAX_PLAINTEXT_RECORD_SIZE) {
+        size_t overhead = 1 + enc->aead->algo->tag_size;
+        if ((ret = ptls_buffer_reserve(buf, overhead)) != 0)
+            return ret;
+        size_t encrypted_len = aead_encrypt(enc, buf->base + rec_start + 5, buf->base + rec_start + 5, bodylen, type);
+        assert(encrypted_len == bodylen + overhead);
+        buf->off += overhead;
+        buf->base[rec_start] = PTLS_CONTENT_TYPE_APPDATA;
+        buf->base[rec_start + 3] = (encrypted_len >> 8) & 0xff;
+        buf->base[rec_start + 4] = encrypted_len & 0xff;
+        return 0;
+    }
 
-    enclen = aead_encrypt(enc, encrypted, buf->base + rec_start + 5, bodylen, buf->base[rec_start]);
+    /* move plaintext to temporary buffer */
+    if ((tmpbuf = malloc(bodylen)) == NULL) {
+        ret = PTLS_ERROR_NO_MEMORY;
+        goto Exit;
+    }
+    memcpy(tmpbuf, buf->base + rec_start + 5, bodylen);
+    ptls_clear_memory(buf->base + rec_start, bodylen + 5);
     buf->off = rec_start;
-    ptls_buffer_push(buf, PTLS_CONTENT_TYPE_APPDATA, 3, 1);
-    ptls_buffer_push16(buf, (uint16_t)enclen);
-    ptls_buffer_pushv(buf, encrypted, enclen);
+
+    /* push encrypted records */
+    ret = buffer_push_encrypted_records(buf, type, tmpbuf, bodylen, enc);
 
 Exit:
+    if (tmpbuf != NULL) {
+        ptls_clear_memory(tmpbuf, bodylen);
+        free(tmpbuf);
+    }
     return ret;
 }
 
-#define buffer_push_record(buf, type, block)                                                                                       \
-    do {                                                                                                                           \
-        ptls_buffer_push((buf), (type), PTLS_RECORD_VERSION_MAJOR, PTLS_RECORD_VERSION_MINOR);                                     \
-        ptls_buffer_push_block((buf), 2, block);                                                                                   \
-    } while (0)
-
-#define buffer_encrypt(buf, enc, block)                                                                                            \
-    do {                                                                                                                           \
-        size_t rec_start = (buf)->off;                                                                                             \
-        do {                                                                                                                       \
-            block                                                                                                                  \
-        } while (0);                                                                                                               \
-        if ((ret = buffer_encrypt_record((buf), rec_start, (enc))) != 0)                                                           \
-            goto Exit;                                                                                                             \
-    } while (0);
-
 #define buffer_push_handshake_core(buf, key_sched, type, mess_start, block)                                                        \
     do {                                                                                                                           \
         ptls_buffer_push((buf), (type));                                                                                           \
@@ -511,14 +546,19 @@ static int buffer_encrypt_record(ptls_buffer_t *buf, size_t rec_start, struct st
             } while (0);                                                                                                           \
         });                                                                                                                        \
         if ((key_sched) != NULL)                                                                                                   \
-            key_schedule_update_hash((key_sched), (buf)->base + mess_start, (buf)->off - (mess_start));                            \
+            key_schedule_update_hash((key_sched), (buf)->base + (mess_start), (buf)->off - (mess_start));                          \
     } while (0)
 
-#define buffer_push_handshake(buf, key_sched, type, block)                                                                         \
-    buffer_push_record((buf), PTLS_CONTENT_TYPE_HANDSHAKE, {                                                                       \
-        size_t mess_start = (buf)->off;                                                                                            \
-        buffer_push_handshake_core((buf), (key_sched), (type), mess_start, block);                                                 \
-    })
+#define buffer_push_handshake(buf, key_sched, enc, type, block)                                                                    \
+    do {                                                                                                                           \
+        size_t rec_start = (buf)->off;                                                                                             \
+        buffer_push_record((buf), PTLS_CONTENT_TYPE_HANDSHAKE,                                                                     \
+                           { buffer_push_handshake_core((buf), (key_sched), (type), rec_start + 5, block); });                     \
+        if ((enc) != NULL) {                                                                                                       \
+            if ((ret = buffer_encrypt_record((buf), rec_start, (enc))) != 0)                                                       \
+                goto Exit;                                                                                                         \
+        }                                                                                                                          \
+    } while (0)
 
 #define buffer_calc_handshake_hash(buf, key_sched, type, block)                                                                    \
     do {                                                                                                                           \
@@ -1004,14 +1044,12 @@ static int send_finished(ptls_t *tls, ptls_buffer_t *sendbuf)
 {
     int ret;
 
-    buffer_encrypt(sendbuf, &tls->traffic_protection.enc, {
-        buffer_push_handshake(sendbuf, tls->key_schedule, PTLS_HANDSHAKE_TYPE_FINISHED, {
-            if ((ret = ptls_buffer_reserve(sendbuf, tls->key_schedule->algo->digest_size)) != 0)
-                goto Exit;
-            if ((ret = calc_verify_data(sendbuf->base + sendbuf->off, tls->key_schedule, tls->traffic_protection.enc.secret)) != 0)
-                goto Exit;
-            sendbuf->off += tls->key_schedule->algo->digest_size;
-        });
+    buffer_push_handshake(sendbuf, tls->key_schedule, &tls->traffic_protection.enc, PTLS_HANDSHAKE_TYPE_FINISHED, {
+        if ((ret = ptls_buffer_reserve(sendbuf, tls->key_schedule->algo->digest_size)) != 0)
+            goto Exit;
+        if ((ret = calc_verify_data(sendbuf->base + sendbuf->off, tls->key_schedule, tls->traffic_protection.enc.secret)) != 0)
+            goto Exit;
+        sendbuf->off += tls->key_schedule->algo->digest_size;
     });
 
 Exit:
@@ -1050,21 +1088,19 @@ static int send_session_ticket(ptls_t *tls, ptls_buffer_t *sendbuf)
         goto Exit;
 
     /* encrypt and send */
-    buffer_encrypt(sendbuf, &tls->traffic_protection.enc, {
-        buffer_push_handshake(sendbuf, tls->key_schedule, PTLS_HANDSHAKE_TYPE_NEW_SESSION_TICKET, {
-            ptls_buffer_push32(sendbuf, tls->ctx->ticket_lifetime);
-            ptls_buffer_push32(sendbuf, ticket_age_add);
-            ptls_buffer_push_block(sendbuf, 1, {});
-            ptls_buffer_push_block(sendbuf, 2, {
-                if ((ret = tls->ctx->encrypt_ticket->cb(tls->ctx->encrypt_ticket, tls, 1, sendbuf,
-                                                        ptls_iovec_init(session_id.base, session_id.off))) != 0)
-                    goto Exit;
-            });
-            ptls_buffer_push_block(sendbuf, 2, {
-                if (tls->ctx->max_early_data_size != 0)
-                    buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_EARLY_DATA,
-                                          { ptls_buffer_push32(sendbuf, tls->ctx->max_early_data_size); });
-            });
+    buffer_push_handshake(sendbuf, tls->key_schedule, &tls->traffic_protection.enc, PTLS_HANDSHAKE_TYPE_NEW_SESSION_TICKET, {
+        ptls_buffer_push32(sendbuf, tls->ctx->ticket_lifetime);
+        ptls_buffer_push32(sendbuf, ticket_age_add);
+        ptls_buffer_push_block(sendbuf, 1, {});
+        ptls_buffer_push_block(sendbuf, 2, {
+            if ((ret = tls->ctx->encrypt_ticket->cb(tls->ctx->encrypt_ticket, tls, 1, sendbuf,
+                                                    ptls_iovec_init(session_id.base, session_id.off))) != 0)
+                goto Exit;
+        });
+        ptls_buffer_push_block(sendbuf, 2, {
+            if (tls->ctx->max_early_data_size != 0)
+                buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_EARLY_DATA,
+                                      { ptls_buffer_push32(sendbuf, tls->ctx->max_early_data_size); });
         });
     });
 
@@ -1138,7 +1174,7 @@ static int send_client_hello(ptls_t *tls, ptls_buffer_t *sendbuf, ptls_handshake
     }
 
     msghash_off = sendbuf->off + 5;
-    buffer_push_handshake(sendbuf, NULL, PTLS_HANDSHAKE_TYPE_CLIENT_HELLO, {
+    buffer_push_handshake(sendbuf, NULL, NULL, PTLS_HANDSHAKE_TYPE_CLIENT_HELLO, {
         /* legacy_version */
         ptls_buffer_push16(sendbuf, 0x0303);
         /* random_bytes */
@@ -1693,8 +1729,8 @@ static int client_handle_finished(ptls_t *tls, ptls_buffer_t *sendbuf, ptls_iove
     if (tls->early_data != NULL) {
         assert(tls->traffic_protection.enc.aead != NULL);
         if (!tls->skip_early_data) {
-            buffer_encrypt(sendbuf, &tls->traffic_protection.enc,
-                           { buffer_push_handshake(sendbuf, tls->key_schedule, PTLS_HANDSHAKE_TYPE_END_OF_EARLY_DATA, {}); });
+            buffer_push_handshake(sendbuf, tls->key_schedule, &tls->traffic_protection.enc, PTLS_HANDSHAKE_TYPE_END_OF_EARLY_DATA,
+                                  {});
         }
         if ((ret = retire_early_data_secret(tls, 1)) != 0)
             goto Exit;
@@ -1974,10 +2010,25 @@ static int decode_client_hello(ptls_t *tls, struct st_ptls_client_hello_t *ch, c
             ptls_decode_block(src, end, 2, {
                 ch->cookie.tbs.base = (void *)src;
                 ptls_decode_open_block(src, end, 2, {
-                    ptls_decode_block(src, end, 1, {
+                    ptls_decode_open_block(src, end, 1, {
                         ch->cookie.ch1_hash = ptls_iovec_init(src, end - src);
                         src = end;
                     });
+                    if (src == end) {
+                        ret = PTLS_ALERT_DECODE_ERROR;
+                        goto Exit;
+                    }
+                    switch (*src++) {
+                    case 0:
+                        assert(!ch->cookie.sent_key_share);
+                        break;
+                    case 1:
+                        ch->cookie.sent_key_share = 1;
+                        break;
+                    default:
+                        ret = PTLS_ALERT_DECODE_ERROR;
+                        goto Exit;
+                    }
                 });
                 ch->cookie.tbs.len = src - ch->cookie.tbs.base;
                 ptls_decode_block(src, end, 1, {
@@ -2219,17 +2270,22 @@ static int calc_cookie_signature(ptls_t *tls, ptls_handshake_properties_t *prope
 static int server_handle_hello(ptls_t *tls, ptls_buffer_t *sendbuf, ptls_iovec_t message, ptls_handshake_properties_t *properties)
 {
 #define EMIT_HRR(sched, negotiated_group, additional_extensions)                                                                   \
-    buffer_push_handshake(sendbuf, sched, PTLS_HANDSHAKE_TYPE_HELLO_RETRY_REQUEST, {                                               \
-        ptls_buffer_push16(sendbuf, PTLS_PROTOCOL_VERSION_DRAFT21);                                                                \
-        ptls_buffer_push16(sendbuf, tls->cipher_suite->id);                                                                        \
-        ptls_buffer_push_block(sendbuf, 2, {                                                                                       \
-            buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_KEY_SHARE,                                                          \
-                                  { ptls_buffer_push16(sendbuf, (negotiated_group)->id); });                                       \
-            {                                                                                                                      \
-                additional_extensions                                                                                              \
-            }                                                                                                                      \
+    do {                                                                                                                           \
+        ptls_key_exchange_algorithm_t *_negotiated_group = (negotiated_group);                                                     \
+        buffer_push_handshake(sendbuf, (sched), NULL, PTLS_HANDSHAKE_TYPE_HELLO_RETRY_REQUEST, {                                   \
+            ptls_buffer_push16(sendbuf, PTLS_PROTOCOL_VERSION_DRAFT21);                                                            \
+            ptls_buffer_push16(sendbuf, tls->cipher_suite->id);                                                                    \
+            ptls_buffer_push_block(sendbuf, 2, {                                                                                   \
+                if (_negotiated_group != NULL) {                                                                                   \
+                    buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_KEY_SHARE,                                                  \
+                                          { ptls_buffer_push16(sendbuf, _negotiated_group->id); });                                \
+                }                                                                                                                  \
+                {                                                                                                                  \
+                    additional_extensions                                                                                          \
+                }                                                                                                                  \
+            });                                                                                                                    \
         });                                                                                                                        \
-    })
+    } while (0)
 
     struct st_ptls_client_hello_t ch = {NULL,  {NULL}, 0,          {NULL},   {NULL}, {NULL},
                                         {{0}}, {NULL}, {{{NULL}}}, {{NULL}}, {NULL}, {{UINT16_MAX}}};
@@ -2318,7 +2374,7 @@ static int server_handle_hello(ptls_t *tls, ptls_buffer_t *sendbuf, ptls_iovec_t
             key_schedule_extract(tls->key_schedule, ptls_iovec_init(NULL, 0));
             /* ... reusing sendbuf to rebuild HRR for hash calculation */
             size_t hrr_start = sendbuf->off;
-            EMIT_HRR(tls->key_schedule, key_share.algorithm, {
+            EMIT_HRR(tls->key_schedule, ch.cookie.sent_key_share ? key_share.algorithm : NULL, {
                 buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_COOKIE,
                                       { ptls_buffer_pushv(sendbuf, ch.cookie.all.base, ch.cookie.all.len); });
             });
@@ -2340,7 +2396,7 @@ static int server_handle_hello(ptls_t *tls, ptls_buffer_t *sendbuf, ptls_iovec_t
                 key_schedule_extract(tls->key_schedule, ptls_iovec_init(NULL, 0));
             }
             /* emit HelloRetryRequest */
-            EMIT_HRR(enforce_cookie_use ? NULL : tls->key_schedule, negotiated_group, {
+            EMIT_HRR(enforce_cookie_use ? NULL : tls->key_schedule, key_share.algorithm != NULL ? NULL : negotiated_group, {
                 if (enforce_cookie_use) {
                     buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_COOKIE, {
                         ptls_buffer_push_block(sendbuf, 2, {
@@ -2355,6 +2411,8 @@ static int server_handle_hello(ptls_t *tls, ptls_buffer_t *sendbuf, ptls_iovec_t
                                     key_schedule_extract_ch1hash(tls->key_schedule, sendbuf->base + sendbuf->off);
                                     sendbuf->off += sz;
                                 });
+                                /* second is if we have sent key_share extension */
+                                ptls_buffer_push(sendbuf, key_share.algorithm == NULL);
                                 /* we can add more data here */
                             });
                             size_t tbs_len = sendbuf->off - tbs_start;
@@ -2449,7 +2507,7 @@ static int server_handle_hello(ptls_t *tls, ptls_buffer_t *sendbuf, ptls_iovec_t
     }
 
     /* send ServerHello */
-    buffer_push_handshake(sendbuf, tls->key_schedule, PTLS_HANDSHAKE_TYPE_SERVER_HELLO, {
+    buffer_push_handshake(sendbuf, tls->key_schedule, NULL, PTLS_HANDSHAKE_TYPE_SERVER_HELLO, {
         ptls_buffer_push16(sendbuf, PTLS_PROTOCOL_VERSION_DRAFT21);
         if ((ret = ptls_buffer_reserve(sendbuf, PTLS_HELLO_RANDOM_SIZE)) != 0)
             goto Exit;
@@ -2486,78 +2544,72 @@ static int server_handle_hello(ptls_t *tls, ptls_buffer_t *sendbuf, ptls_iovec_t
     }
 
     /* send EncryptedExtensions */
-    buffer_encrypt(sendbuf, &tls->traffic_protection.enc, {
-        buffer_push_handshake(sendbuf, tls->key_schedule, PTLS_HANDSHAKE_TYPE_ENCRYPTED_EXTENSIONS, {
-            ptls_buffer_push_block(sendbuf, 2, {
-                if (tls->server_name != NULL) {
-                    /* In this event, the server SHALL include an extension of type "server_name" in the (extended) server
-                     * hello. The "extension_data" field of this extension SHALL be empty. (RFC 6066 section 3) */
-                    buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_NAME, {});
-                }
-                if (tls->negotiated_protocol != NULL) {
-                    buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ALPN, {
-                        ptls_buffer_push_block(sendbuf, 2, {
-                            ptls_buffer_push_block(sendbuf, 1, {
-                                ptls_buffer_pushv(sendbuf, tls->negotiated_protocol, strlen(tls->negotiated_protocol));
-                            });
+    buffer_push_handshake(sendbuf, tls->key_schedule, &tls->traffic_protection.enc, PTLS_HANDSHAKE_TYPE_ENCRYPTED_EXTENSIONS, {
+        ptls_buffer_push_block(sendbuf, 2, {
+            if (tls->server_name != NULL) {
+                /* In this event, the server SHALL include an extension of type "server_name" in the (extended) server
+                 * hello. The "extension_data" field of this extension SHALL be empty. (RFC 6066 section 3) */
+                buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_NAME, {});
+            }
+            if (tls->negotiated_protocol != NULL) {
+                buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ALPN, {
+                    ptls_buffer_push_block(sendbuf, 2, {
+                        ptls_buffer_push_block(sendbuf, 1, {
+                            ptls_buffer_pushv(sendbuf, tls->negotiated_protocol, strlen(tls->negotiated_protocol));
                         });
                     });
-                }
-                if (tls->early_data != NULL && tls->traffic_protection.dec.aead != NULL)
-                    buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_EARLY_DATA, {});
-                if ((ret = push_additional_extensions(properties, sendbuf)) != 0)
-                    goto Exit;
-            });
+                });
+            }
+            if (tls->early_data != NULL && tls->traffic_protection.dec.aead != NULL)
+                buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_EARLY_DATA, {});
+            if ((ret = push_additional_extensions(properties, sendbuf)) != 0)
+                goto Exit;
         });
     });
 
     if (mode == HANDSHAKE_MODE_FULL) {
         /* send Certificate */
-        buffer_encrypt(sendbuf, &tls->traffic_protection.enc, {
-            buffer_push_handshake(sendbuf, tls->key_schedule, PTLS_HANDSHAKE_TYPE_CERTIFICATE, {
-                ptls_buffer_push(sendbuf, 0);
-                ptls_buffer_push_block(sendbuf, 3, {
-                    size_t i;
-                    for (i = 0; i != tls->ctx->certificates.count; ++i) {
-                        ptls_buffer_push_block(sendbuf, 3, {
-                            ptls_buffer_pushv(sendbuf, tls->ctx->certificates.list[i].base, tls->ctx->certificates.list[i].len);
-                        });
-                        ptls_buffer_push_block(sendbuf, 2, {
-                            /* emit OCSP stapling only when requested and when the callback successfully returns one */
-                            if (ch.status_request && i == 0 && tls->ctx->staple_ocsp != NULL) {
-                                size_t reset_off_to = sendbuf->off;
-                                buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_STATUS_REQUEST, {
-                                    ptls_buffer_push(sendbuf, 1); /* status_type == ocsp */
-                                    ptls_buffer_push_block(sendbuf, 3, {
-                                        if ((ret = tls->ctx->staple_ocsp->cb(tls->ctx->staple_ocsp, tls, sendbuf, i)) == 0)
-                                            reset_off_to = 0;
-                                    });
+        buffer_push_handshake(sendbuf, tls->key_schedule, &tls->traffic_protection.enc, PTLS_HANDSHAKE_TYPE_CERTIFICATE, {
+            ptls_buffer_push(sendbuf, 0);
+            ptls_buffer_push_block(sendbuf, 3, {
+                size_t i;
+                for (i = 0; i != tls->ctx->certificates.count; ++i) {
+                    ptls_buffer_push_block(sendbuf, 3, {
+                        ptls_buffer_pushv(sendbuf, tls->ctx->certificates.list[i].base, tls->ctx->certificates.list[i].len);
+                    });
+                    ptls_buffer_push_block(sendbuf, 2, {
+                        /* emit OCSP stapling only when requested and when the callback successfully returns one */
+                        if (ch.status_request && i == 0 && tls->ctx->staple_ocsp != NULL) {
+                            size_t reset_off_to = sendbuf->off;
+                            buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_STATUS_REQUEST, {
+                                ptls_buffer_push(sendbuf, 1); /* status_type == ocsp */
+                                ptls_buffer_push_block(sendbuf, 3, {
+                                    if ((ret = tls->ctx->staple_ocsp->cb(tls->ctx->staple_ocsp, tls, sendbuf, i)) == 0)
+                                        reset_off_to = 0;
                                 });
-                                if (reset_off_to != 0)
-                                    sendbuf->off = reset_off_to;
-                            }
-                        });
-                    }
-                });
+                            });
+                            if (reset_off_to != 0)
+                                sendbuf->off = reset_off_to;
+                        }
+                    });
+                }
             });
         });
         /* build and send CertificateVerify */
-        buffer_encrypt(sendbuf, &tls->traffic_protection.enc, {
-            buffer_push_handshake(sendbuf, tls->key_schedule, PTLS_HANDSHAKE_TYPE_CERTIFICATE_VERIFY, {
-                size_t algo_off = sendbuf->off;
-                ptls_buffer_push16(sendbuf, 0); /* filled in later */
-                ptls_buffer_push_block(sendbuf, 2, {
-                    uint16_t algo;
-                    uint8_t data[PTLS_MAX_CERTIFICATE_VERIFY_SIGNDATA_SIZE];
-                    size_t datalen =
-                        build_certificate_verify_signdata(data, tls->key_schedule, PTLS_SERVER_CERTIFICATE_VERIFY_CONTEXT_STRING);
-                    if ((ret = tls->ctx->sign_certificate->cb(tls->ctx->sign_certificate, tls, &algo, sendbuf,
-                                                              ptls_iovec_init(data, datalen), ch.signature_algorithms.list,
-                                                              ch.signature_algorithms.count)) != 0)
-                        goto Exit;
-                    sendbuf->base[algo_off] = (uint8_t)(algo >> 8);
-                    sendbuf->base[algo_off + 1] = (uint8_t)algo;
-                });
+        buffer_push_handshake(sendbuf, tls->key_schedule, &tls->traffic_protection.enc, PTLS_HANDSHAKE_TYPE_CERTIFICATE_VERIFY, {
+            size_t algo_off = sendbuf->off;
+            ptls_buffer_push16(sendbuf, 0); /* filled in later */
+            ptls_buffer_push_block(sendbuf, 2, {
+                uint16_t algo;
+                uint8_t data[PTLS_MAX_CERTIFICATE_VERIFY_SIGNDATA_SIZE];
+                size_t datalen =
+                    build_certificate_verify_signdata(data, tls->key_schedule, PTLS_SERVER_CERTIFICATE_VERIFY_CONTEXT_STRING);
+                if ((ret = tls->ctx->sign_certificate->cb(tls->ctx->sign_certificate, tls, &algo, sendbuf,
+                                                          ptls_iovec_init(data, datalen), ch.signature_algorithms.list,
+                                                          ch.signature_algorithms.count)) != 0)
+                    goto Exit;
+                sendbuf->base[algo_off] = (uint8_t)(algo >> 8);
+                sendbuf->base[algo_off + 1] = (uint8_t)algo;
             });
         });
     }
@@ -2991,6 +3043,7 @@ static int handle_handshake_record(ptls_t *tls, int (*cb)(ptls_t *tls, ptls_buff
             memmove(tls->recvbuf.mess.base, src, src_end - src);
         }
         tls->recvbuf.mess.off = src_end - src;
+        ret = PTLS_ERROR_IN_PROGRESS;
     } else {
         ptls_buffer_dispose(&tls->recvbuf.mess);
     }
@@ -3161,28 +3214,10 @@ int ptls_receive(ptls_t *tls, ptls_buffer_t *decryptbuf, const void *_input, siz
     return ret;
 }
 
-int ptls_send(ptls_t *tls, ptls_buffer_t *sendbuf, const void *_input, size_t inlen)
+int ptls_send(ptls_t *tls, ptls_buffer_t *sendbuf, const void *input, size_t inlen)
 {
-    const uint8_t *input = (const uint8_t *)_input;
-    size_t pt_size;
-    int ret = 0;
-
     assert(tls->traffic_protection.enc.aead != NULL);
-
-    for (; inlen != 0; input += pt_size, inlen -= pt_size) {
-        pt_size = inlen;
-        if (pt_size > PTLS_MAX_PLAINTEXT_RECORD_SIZE)
-            pt_size = PTLS_MAX_PLAINTEXT_RECORD_SIZE;
-        buffer_push_record(sendbuf, PTLS_CONTENT_TYPE_APPDATA, {
-            if ((ret = ptls_buffer_reserve(sendbuf, pt_size + tls->traffic_protection.enc.aead->algo->tag_size + 1)) != 0)
-                goto Exit;
-            sendbuf->off +=
-                aead_encrypt(&tls->traffic_protection.enc, sendbuf->base + sendbuf->off, input, pt_size, PTLS_CONTENT_TYPE_APPDATA);
-        });
-    }
-
-Exit:
-    return ret;
+    return buffer_push_encrypted_records(sendbuf, PTLS_CONTENT_TYPE_APPDATA, input, inlen, &tls->traffic_protection.enc);
 }
 
 size_t ptls_get_record_overhead(ptls_t *tls)
diff --git a/t/cli.c b/t/cli.c
index 06a586873..775fd8efc 100644
--- a/t/cli.c
+++ b/t/cli.c
@@ -226,7 +226,7 @@ static int run_server(struct sockaddr *sa, socklen_t salen, ptls_context_t *ctx,
 {
     int listen_fd, conn_fd, on = 1;
 
-    if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
+    if ((listen_fd = socket(sa->sa_family, SOCK_STREAM, 0)) == -1) {
         perror("socket(2) failed");
         return 1;
     }
@@ -275,6 +275,8 @@ static void usage(const char *cmd)
     printf("Usage: %s [options] host port\n"
            "\n"
            "Options:\n"
+           "  -4                   force IPv4\n"
+           "  -6                   force IPv6\n"
            "  -c certificate-file\n"
            "  -k key-file          specifies the credentials to be used for running the\n"
            "                       server. If omitted, the command runs as a client.\n"
@@ -305,9 +307,16 @@ int main(int argc, char **argv)
     int use_early_data = 0, ch;
     struct sockaddr_storage sa;
     socklen_t salen;
+    int family = 0;
 
-    while ((ch = getopt(argc, argv, "c:k:es:l:vh")) != -1) {
+    while ((ch = getopt(argc, argv, "46c:k:es:l:vh")) != -1) {
         switch (ch) {
+        case '4':
+            family = AF_INET;
+            break;
+        case '6':
+            family = AF_INET6;
+            break;
         case 'c':
             load_certificate_chain(&ctx, optarg);
             break;
@@ -354,7 +363,7 @@ int main(int argc, char **argv)
     host = (--argc, *argv++);
     port = (--argc, *argv++);
 
-    if (resolve_address((struct sockaddr *)&sa, &salen, host, port, SOCK_STREAM, IPPROTO_TCP) != 0)
+    if (resolve_address((struct sockaddr *)&sa, &salen, host, port, family, SOCK_STREAM, IPPROTO_TCP) != 0)
         exit(1);
 
     if (ctx.certificates.count != 0) {
diff --git a/t/picotls.c b/t/picotls.c
index 1e49408aa..31bb6d1ed 100644
--- a/t/picotls.c
+++ b/t/picotls.c
@@ -221,14 +221,14 @@ static void test_fragmented_message(void)
     SET_RECORD("\x01\x00\x00\x03"
                "a");
     ret = handle_handshake_record(&tls, test_fragmented_message_record, NULL, &rec, NULL);
-    ok(ret == 0);
+    ok(ret == PTLS_ERROR_IN_PROGRESS);
     ok(tls.recvbuf.mess.base != NULL);
     ok(test_fragmented_message_queue.count == 0);
     SET_RECORD("bc\x02\x00\x00\x02"
                "de"
                "\x03");
     ret = handle_handshake_record(&tls, test_fragmented_message_record, NULL, &rec, NULL);
-    ok(ret == 0);
+    ok(ret == PTLS_ERROR_IN_PROGRESS);
     ok(test_fragmented_message_queue.count == 2);
     ok(test_fragmented_message_queue.vec[0].len == 7);
     ok(memcmp(test_fragmented_message_queue.vec[0].buf, "\x01\x00\x00\x03"
diff --git a/t/util.h b/t/util.h
index eff0e4f8b..47010fa5d 100644
--- a/t/util.h
+++ b/t/util.h
@@ -231,12 +231,13 @@ static inline void setup_session_cache(ptls_context_t *ctx)
     ctx->encrypt_ticket = &sc.super;
 }
 
-static inline int resolve_address(struct sockaddr *sa, socklen_t *salen, const char *host, const char *port, int type, int proto)
+static inline int resolve_address(struct sockaddr *sa, socklen_t *salen, const char *host, const char *port, int family, int type, int proto)
 {
     struct addrinfo hints, *res;
     int err;
 
     memset(&hints, 0, sizeof(hints));
+    hints.ai_family = family;
     hints.ai_socktype = type;
     hints.ai_protocol = proto;
     hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV | AI_PASSIVE;