add test for rebuilding inner CH
diff --git a/lib/picotls.c b/lib/picotls.c
index c6654cf..3a6eaa8 100644
--- a/lib/picotls.c
+++ b/lib/picotls.c
@@ -3717,6 +3717,56 @@
     return ret;
 }
 
+static int rebuild_ch_inner_extensions(ptls_buffer_t *buf, const uint8_t **src, const uint8_t *const end, const uint8_t *outer_ext,
+                                       const uint8_t *outer_ext_end)
+{
+    int ret;
+
+    ptls_buffer_push_block(buf, 2, {
+        ptls_decode_open_block(*src, end, 2, {
+            while (*src != end) {
+                uint16_t exttype;
+                if ((ret = ptls_decode16(&exttype, src, end)) != 0)
+                    goto Exit;
+                ptls_decode_open_block(*src, end, 2, {
+                    if (exttype == PTLS_EXTENSION_TYPE_ECH_OUTER_EXTENSIONS) {
+                        ptls_decode_open_block(*src, end, 1, {
+                            do {
+                                uint16_t reftype;
+                                uint16_t outertype;
+                                uint16_t outersize;
+                                if ((ret = ptls_decode16(&reftype, src, end)) != 0)
+                                    goto Exit;
+                                while (1) {
+                                    if ((ret = ptls_decode16(&outertype, &outer_ext, outer_ext_end)) != 0 ||
+                                        (ret = ptls_decode16(&outersize, &outer_ext, outer_ext_end)) != 0)
+                                        goto Exit;
+                                    assert(outer_ext_end - outer_ext >= outersize);
+                                    if (outertype == reftype)
+                                        break;
+                                    outer_ext += outersize;
+                                }
+                                buffer_push_extension(buf, reftype, {
+                                    ptls_buffer_pushv(buf, outer_ext, outersize);
+                                    outer_ext += outersize;
+                                });
+                            } while (*src != end);
+                        });
+                    } else {
+                        buffer_push_extension(buf, exttype, {
+                            ptls_buffer_pushv(buf, *src, end - *src);
+                            *src = end;
+                        });
+                    }
+                });
+            }
+        });
+    });
+
+Exit:
+    return ret;
+}
+
 static int rebuild_ch_inner(ptls_buffer_t *buf, const uint8_t *src, const uint8_t *const end,
                             struct st_ptls_client_hello_t *outer_ch, const uint8_t *outer_ext, const uint8_t *outer_ext_end)
 {
@@ -3760,46 +3810,8 @@
         COPY_BLOCK(1);
 
         /* extensions */
-        ptls_buffer_push_block(buf, 2, {
-            ptls_decode_open_block(src, end, 2, {
-                while (src != end) {
-                    uint16_t exttype;
-                    if ((ret = ptls_decode16(&exttype, &src, end)) != 0)
-                        goto Exit;
-                    ptls_decode_open_block(src, end, 2, {
-                        if (exttype == PTLS_EXTENSION_TYPE_ECH_OUTER_EXTENSIONS) {
-                            ptls_decode_open_block(src, end, 1, {
-                                do {
-                                    uint16_t reftype;
-                                    uint16_t outertype;
-                                    uint16_t outersize;
-                                    if ((ret = ptls_decode16(&reftype, &src, end)) != 0)
-                                        goto Exit;
-                                    while (1) {
-                                        if ((ret = ptls_decode16(&outertype, &outer_ext, outer_ext_end)) != 0 ||
-                                            (ret = ptls_decode16(&outersize, &outer_ext, outer_ext_end)) != 0)
-                                            goto Exit;
-                                        assert(outer_ext_end - outer_ext >= outersize);
-                                        if (outertype == reftype)
-                                            break;
-                                        outer_ext += outersize;
-                                    }
-                                    buffer_push_extension(buf, reftype, {
-                                        ptls_buffer_pushv(buf, outer_ext, outersize);
-                                        outer_ext += outersize;
-                                    });
-                                } while (src != end);
-                            });
-                        } else {
-                            buffer_push_extension(buf, exttype, {
-                                ptls_buffer_pushv(buf, src, end - src);
-                                src = end;
-                            });
-                        }
-                    });
-                }
-            });
-        });
+        if ((ret = rebuild_ch_inner_extensions(buf, &src, end, outer_ext, outer_ext_end)) != 0)
+            goto Exit;
     });
 
     /* padding must be all zero */
diff --git a/t/picotls.c b/t/picotls.c
index f2ee970..083c9d0 100644
--- a/t/picotls.c
+++ b/t/picotls.c
@@ -538,9 +538,65 @@
     }
 }
 
+static void test_rebuild_ch_inner(void)
+{
+    ptls_buffer_t buf;
+    ptls_buffer_init(&buf, "", 0);
+
+#define TEST(_expected_err)                                                                                                        \
+    do {                                                                                                                           \
+        const uint8_t *src = encoded_inner;                                                                                        \
+        buf.off = 0;                                                                                                               \
+        ok(rebuild_ch_inner_extensions(&buf, &src, encoded_inner + sizeof(encoded_inner), outer, outer + sizeof(outer)) ==         \
+           _expected_err);                                                                                                         \
+        if (_expected_err == 0) {                                                                                                  \
+            ok(src == encoded_inner + sizeof(encoded_inner));                                                                      \
+            ok(buf.off == sizeof(expected));                                                                                       \
+            ok(memcmp(buf.base, expected, sizeof(expected)) == 0);                                                                 \
+        }                                                                                                                          \
+    } while (0)
+
+    { /* replace none */
+        static const uint8_t encoded_inner[] = {0x00, 0x09, 0x12, 0x34, 0x00, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f}, outer[] = {},
+                             expected[] = {0x00, 0x09, 0x12, 0x34, 0x00, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f};
+        TEST(0);
+    }
+
+    { /* replace one */
+        static const uint8_t encoded_inner[] = {0x00, 0x07, 0xfd, 0x00, 0x00, 0x03, 0x02, 0x00, 0x01},
+                             outer[] = {0x00, 0x01, 0x00, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f},
+                             expected[] = {0x00, 0x09, 0x00, 0x01, 0x00, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f};
+        TEST(0);
+    }
+
+    { /* replace multi */
+        static const uint8_t encoded_inner[] = {0x00, 0x13, 0x00, 0x01, 0x00, 0x01, 0x31, 0xfd, 0x00, 0x00, 0x05,
+                                                0x04, 0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x01, 0x35},
+                             outer[] = {0x00, 0x01, 0x00, 0x01, 0x41, 0x00, 0x02, 0x00, 0x01, 0x42, 0x00, 0x03, 0x00,
+                                        0x01, 0x43, 0x00, 0x04, 0x00, 0x01, 0x44, 0x00, 0x05, 0x00, 0x01, 0x45},
+                             expected[] = {0x00, 0x14, 0x00, 0x01, 0x00, 0x01, 0x31, 0x00, 0x02, 0x00, 0x01,
+                                           0x42, 0x00, 0x04, 0x00, 0x01, 0x44, 0x00, 0x05, 0x00, 0x01, 0x35};
+        TEST(0);
+    }
+
+    { /* outer extension not found */
+        static const uint8_t encoded_inner[] = {0x00, 0x13, 0x00, 0x01, 0x00, 0x01, 0x31, 0xfd, 0x00, 0x00, 0x05,
+                                                0x04, 0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x01, 0x35},
+                             outer[] = {0x00, 0x01, 0x00, 0x01, 0x41, 0x00, 0x02, 0x00, 0x01, 0x42, 0x00, 0x03, 0x00,
+                                        0x01, 0x43},
+                             expected[] = {0x00, 0x14, 0x00, 0x01, 0x00, 0x01, 0x31, 0x00, 0x02, 0x00, 0x01,
+                                           0x42, 0x00, 0x04, 0x00, 0x01, 0x44, 0x00, 0x05, 0x00, 0x01, 0x35};
+        TEST(PTLS_ALERT_ILLEGAL_PARAMETER);
+    }
+
+#undef TEST
+    ptls_buffer_dispose(&buf);
+}
+
 static void test_ech(void)
 {
     subtest("decode-config", test_ech_decode_config);
+    subtest("rebuild_ch_inner", test_rebuild_ch_inner);
 }
 
 static struct {