initial commit

This commit is contained in:
i2p
2026-08-27 21:09:14 +00:00
commit a5b6d59437
12681 changed files with 3253832 additions and 0 deletions
@@ -0,0 +1,81 @@
--- a/crypto/mem.cc
+++ b/crypto/mem.cc
@@ -100,9 +100,20 @@
// primitives used must tolerate every other synchronization primitive linked
// into the process, including pthreads locks. Failing to meet these constraints
// may result in deadlocks, crashes, or memory corruption.
+#if defined(BORINGSSL_REQUIRE_MEMORY_HOOKS)
+// Bun: the embedder guarantees these three are always defined. WEAK_SYMBOL_FUNC
+// is gated on __ELF__, so on Mach-O and COFF it would expand to a static null
+// pointer and the hook path would be folded away at compile time.
+extern "C" {
+void *OPENSSL_memory_alloc(size_t size);
+void OPENSSL_memory_free(void *ptr);
+size_t OPENSSL_memory_get_size(void *ptr);
+}
+#else
WEAK_SYMBOL_FUNC(void *, OPENSSL_memory_alloc, (size_t size))
WEAK_SYMBOL_FUNC(void, OPENSSL_memory_free, (void *ptr))
WEAK_SYMBOL_FUNC(size_t, OPENSSL_memory_get_size, (void *ptr))
+#endif
#if defined(BORINGSSL_MALLOC_FAILURE_TESTING)
static StaticMutex malloc_failure_lock;
@@ -186,6 +197,15 @@
goto err;
}
+#if defined(BORINGSSL_REQUIRE_MEMORY_HOOKS)
+ {
+ void *ptr2 = OPENSSL_memory_alloc(size);
+ if (ptr2 == nullptr && size != 0) {
+ goto err;
+ }
+ return ptr2;
+ }
+#else
if (OPENSSL_memory_alloc != nullptr) {
assert(OPENSSL_memory_free != nullptr);
assert(OPENSSL_memory_get_size != nullptr);
@@ -195,6 +215,7 @@
}
return ptr2;
}
+#endif
if (size + OPENSSL_MALLOC_PREFIX < size) {
goto err;
@@ -238,10 +259,15 @@
return;
}
+#if defined(BORINGSSL_REQUIRE_MEMORY_HOOKS)
+ OPENSSL_memory_free(orig_ptr);
+ return;
+#else
if (OPENSSL_memory_free != nullptr) {
OPENSSL_memory_free(orig_ptr);
return;
}
+#endif
void *ptr = ((uint8_t *)orig_ptr) - OPENSSL_MALLOC_PREFIX;
__asan_unpoison_memory_region(ptr, OPENSSL_MALLOC_PREFIX);
@@ -268,6 +294,9 @@
}
size_t old_size;
+#if defined(BORINGSSL_REQUIRE_MEMORY_HOOKS)
+ old_size = OPENSSL_memory_get_size(orig_ptr);
+#else
if (OPENSSL_memory_get_size != nullptr) {
old_size = OPENSSL_memory_get_size(orig_ptr);
} else {
@@ -276,6 +305,7 @@
old_size = *(size_t *)ptr;
__asan_poison_memory_region(ptr, OPENSSL_MALLOC_PREFIX);
}
+#endif
void *ret = OPENSSL_malloc(new_size);
if (ret == nullptr) {
@@ -0,0 +1,36 @@
Accept DNS name compression in RDATA on parse.
c-ares 1.34.8 started rejecting compression pointers inside RDATA for RR
types that RFC 3597 says must not use them (SRV, NAPTR, etc.). That is
correct for writers, but a large share of deployed resolvers and caches
(older BIND, dnsmasq, mDNSResponder on macOS, assorted corporate
forwarders) still compress SRV targets on the wire, so dns.resolveSrv()
fails with EBADRESP against those servers.
c-ares already refuses to emit compression for these types
(ares_dns_rec_allow_name_comp gates the writer). This patch keeps the
parser lenient the way it was before 1.34.8 so existing servers keep
working, while the writer remains strict.
--- a/src/lib/record/ares_dns_parse.c
+++ b/src/lib/record/ares_dns_parse.c
@@ -46,14 +46,12 @@
{
ares_status_t status;
char *name = NULL;
- /* Only RR types defined in RFC1035 may use name compression within their
- * RDATA (RFC3597). Reject compression pointers for any other type (e.g.
- * SRV per RFC2782) to match the write-side policy and avoid following
- * pointers that a non-understanding nameserver could not have rewritten. */
- ares_bool_t allow_compression =
- ares_dns_rec_allow_name_comp(ares_dns_rr_get_type(rr));
- status = ares_dns_name_parse(buf, &name, is_hostname, allow_compression);
+ /* Bun: accept compression in RDATA on parse regardless of RR type.
+ * RFC 3597 says writers must not compress here and the c-ares writer
+ * already enforces that, but plenty of real resolvers still emit
+ * compressed SRV/NAPTR targets, so stay lenient when reading. */
+ status = ares_dns_name_parse(buf, &name, is_hostname, ARES_TRUE);
if (status != ARES_SUCCESS) {
return status;
}
+11
View File
@@ -0,0 +1,11 @@
--- a/src/hdr_histogram.c
+++ b/src/hdr_histogram.c
@@ -144,7 +144,7 @@ static int64_t counts_get_normalised(const struct hdr_histogram* h, int32_t inde
static int32_t count_leading_zeros_64(int64_t value)
{
#if defined(_MSC_VER) && !(defined(__clang__) && (defined(_M_ARM) || defined(_M_ARM64)))
- uint32_t leading_zero = 0;
+ unsigned long leading_zero = 0;
#if defined(_WIN64)
_BitScanReverse64(&leading_zero, value);
#else
+23
View File
@@ -0,0 +1,23 @@
--- a/hwy/base.h
+++ b/hwy/base.h
@@ -351,8 +351,7 @@ HWY_DLLEXPORT HWY_NORETURN void HWY_FORMAT(3, 4)
#endif // HWY_HEADER_ONLY
-#define HWY_WARN(format, ...) \
- ::hwy::Warn(__FILE__, __LINE__, format, ##__VA_ARGS__)
+#define HWY_WARN(format, ...)
#define HWY_ABORT(format, ...) \
::hwy::Abort(__FILE__, __LINE__, format, ##__VA_ARGS__)
--- a/hwy/contrib/thread_pool/topology.cc
+++ b/hwy/contrib/thread_pool/topology.cc
@@ -165,7 +165,7 @@ void ForeachBit(size_t num_groups, const GROUP_AFFINITY* affinity,
size_t lp = group * 64 + Num0BitsBelowLS1Bit_Nonzero64(bits);
bits &= bits - 1; // clear LSB
if (HWY_UNLIKELY(lp >= lps.size())) {
- Warn(__FILE__, line, "Clamping lp %zu to lps.size() %zu, groups %zu\n",
+ HWY_WARN("Clamping lp %zu to lps.size() %zu, groups %zu\n",
lp, lps.size(), num_groups);
lp = lps.size() - 1;
}
@@ -0,0 +1,81 @@
--- a/libarchive/archive_string.c
+++ b/libarchive/archive_string.c
@@ -1551,10 +1551,10 @@ my_atoi(const char *p)
}
/*
- * Return ANSI Code Page of current locale set by setlocale().
+ * Return ANSI Code Page of current locale set by setlocale() (uncached).
*/
static unsigned
-get_current_codepage(void)
+get_current_codepage_uncached(void)
{
char *locale, *p;
unsigned cp;
@@ -1574,6 +1574,28 @@ get_current_codepage(void)
return (cp);
}
+/*
+ * Bun: cache the codepage after the first call. The process never changes
+ * its locale at runtime, but `bun install` creates a charset converter per
+ * extracted archive on many worker threads at once, and hammering the UCRT's
+ * setlocale(LC_CTYPE, NULL) query from all of them concurrently is both
+ * wasted work and a needless trip through the CRT locale machinery (and its
+ * heap) on every archive. Compute once; later callers read the cached value.
+ */
+static unsigned
+get_current_codepage(void)
+{
+ static volatile LONG cached_cp = -1;
+ LONG cp = cached_cp;
+
+ if (cp == -1) {
+ cp = (LONG)get_current_codepage_uncached();
+ InterlockedCompareExchange(&cached_cp, cp, -1);
+ cp = cached_cp;
+ }
+ return ((unsigned)cp);
+}
+
/*
* Translation table between Locale Name and ACP/OEMCP.
*/
@@ -1626,10 +1648,10 @@ static struct {
};
/*
- * Return OEM Code Page of current locale set by setlocale().
+ * Return OEM Code Page of current locale set by setlocale() (uncached).
*/
static unsigned
-get_current_oemcp(void)
+get_current_oemcp_uncached(void)
{
int i;
char *locale, *p;
@@ -1652,6 +1674,23 @@ get_current_oemcp(void)
}
return (GetOEMCP());
}
+
+/*
+ * Bun: cached for the same reason as get_current_codepage() above.
+ */
+static unsigned
+get_current_oemcp(void)
+{
+ static volatile LONG cached_ocp = -1;
+ LONG ocp = cached_ocp;
+
+ if (ocp == -1) {
+ ocp = (LONG)get_current_oemcp_uncached();
+ InterlockedCompareExchange(&cached_ocp, ocp, -1);
+ ocp = cached_ocp;
+ }
+ return ((unsigned)ocp);
+}
#else
/*
@@ -0,0 +1,65 @@
--- a/libarchive/archive_write_add_filter_gzip.c
+++ b/libarchive/archive_write_add_filter_gzip.c
@@ -59,12 +59,13 @@
int compression_level;
int timestamp;
char *original_filename;
+ unsigned char os;
#ifdef HAVE_ZLIB_H
z_stream stream;
int64_t total_in;
unsigned char *compressed;
size_t compressed_buffer_size;
- unsigned long crc;
+ uint32_t crc;
#else
struct archive_write_program_data *pdata;
#endif
@@ -108,6 +109,7 @@
return (ARCHIVE_FATAL);
}
f->data = data;
+ data->os = 3; /* default Unix */
f->open = &archive_compressor_gzip_open;
f->options = &archive_compressor_gzip_options;
f->close = &archive_compressor_gzip_close;
@@ -180,6 +182,30 @@
if (data->original_filename == NULL)
return (ARCHIVE_WARN);
}
+ return (ARCHIVE_OK);
+ }
+
+ if (strcmp(key, "os") == 0) {
+ if (value == NULL)
+ return (ARCHIVE_WARN);
+
+ if (strcmp(value, "FAT") == 0) data->os = 0;
+ else if (strcmp(value, "Amiga") == 0) data->os = 1;
+ else if (strcmp(value, "VMS") == 0 || strcmp(value, "OpenVMS") == 0) data->os = 2;
+ else if (strcmp(value, "Unix") == 0) data->os = 3;
+ else if (strcmp(value, "VM") == 0 || strcmp(value, "VM/CMS") == 0) data->os = 4;
+ else if (strcmp(value, "Atari TOS") == 0) data->os = 5;
+ else if (strcmp(value, "HPFS") == 0) data->os = 6;
+ else if (strcmp(value, "Macintosh") == 0) data->os = 7;
+ else if (strcmp(value, "Z-System") == 0) data->os = 8;
+ else if (strcmp(value, "CP/M") == 0) data->os = 9;
+ else if (strcmp(value, "TOPS-20") == 0) data->os = 10;
+ else if (strcmp(value, "NTFS") == 0) data->os = 11;
+ else if (strcmp(value, "QDOS") == 0) data->os = 12;
+ else if (strcmp(value, "Acorn RISCOS") == 0) data->os = 13;
+ else if (strcmp(value, "Unknown") == 0) data->os = 255;
+ else return (ARCHIVE_WARN);
+
return (ARCHIVE_OK);
}
@@ -245,7 +271,7 @@
} else {
data->compressed[8] = 0;
}
- data->compressed[9] = 3; /* OS=Unix */
+ data->compressed[9] = data->os;
data->stream.next_out += 10;
data->stream.avail_out -= 10;
+779
View File
@@ -0,0 +1,779 @@
--- a/libarchive/archive_read_private.h
+++ b/libarchive/archive_read_private.h
@@ -182,2 +182,14 @@
+ /*
+ * BUN PATCH: set by the format's read_header callback when it
+ * returns ARCHIVE_RETRY from a non-blocking source and wants
+ * the next call to skip archive_entry_clear() so that
+ * attributes already populated by consumed extension headers
+ * (pax, GNU long name/link, etc.) survive the resume. Format
+ * readers that return ARCHIVE_RETRY without setting this —
+ * e.g. upstream tar's damaged-block skip — get the original
+ * reset-everything semantics on re-entry.
+ */
+ int read_header_in_progress;
+
/* Nodes and offsets of compressed data block */
--- a/libarchive/archive_read.c
+++ b/libarchive/archive_read.c
@@ -616,25 +616,58 @@
- archive_entry_clear(entry);
- archive_clear_error(&a->archive);
-
/*
- * If client didn't consume entire data, skip any remainder
- * (This is especially important for GNU incremental directories.)
+ * BUN PATCH: when resuming after an ARCHIVE_RETRY from a
+ * non-blocking source, the format reader has already
+ * populated `entry` from consumed extension headers (pax,
+ * GNU long name/link). Clearing it here would discard that
+ * work, and since no data has been read for the pending
+ * entry there is nothing to skip. Skip straight to the
+ * format reader which will pick up where it left off.
*/
- if (a->archive.state == ARCHIVE_STATE_DATA) {
- r1 = archive_read_data_skip(&a->archive);
- if (r1 == ARCHIVE_EOF)
- archive_set_error(&a->archive, EIO,
- "Premature end-of-file");
- if (r1 == ARCHIVE_EOF || r1 == ARCHIVE_FATAL) {
- a->archive.state = ARCHIVE_STATE_FATAL;
- return (ARCHIVE_FATAL);
+ if (!a->read_header_in_progress) {
+ archive_entry_clear(entry);
+ archive_clear_error(&a->archive);
+
+ /*
+ * If client didn't consume entire data, skip any remainder
+ * (This is especially important for GNU incremental directories.)
+ */
+ if (a->archive.state == ARCHIVE_STATE_DATA) {
+ r1 = archive_read_data_skip(&a->archive);
+ /*
+ * BUN PATCH: the skip may run out of buffered
+ * bytes when the source is non-blocking. The
+ * format's skip handler has already recorded how
+ * much remains, so the next next_header() call
+ * will re-enter here (state is still DATA) and
+ * continue the skip.
+ */
+ if (r1 == ARCHIVE_RETRY)
+ return (ARCHIVE_RETRY);
+ if (r1 == ARCHIVE_EOF)
+ archive_set_error(&a->archive, EIO,
+ "Premature end-of-file");
+ if (r1 == ARCHIVE_EOF || r1 == ARCHIVE_FATAL) {
+ a->archive.state = ARCHIVE_STATE_FATAL;
+ return (ARCHIVE_FATAL);
+ }
}
- }
- /* Record start-of-header offset in uncompressed stream. */
- a->header_position = a->filter->position;
+ /* Record start-of-header offset in uncompressed stream. */
+ a->header_position = a->filter->position;
- ++_a->file_count;
+ ++_a->file_count;
+ }
r2 = (a->format->read_header)(a, entry);
+ /*
+ * BUN PATCH: the format reader sets read_header_in_progress
+ * itself when a non-blocking source yields mid-header (see
+ * `bun_retry` in the tar reader). Clear it on any terminal
+ * result so the next call runs the full reset above. An
+ * ARCHIVE_RETRY that did not set the flag — upstream tar's
+ * pre-existing damaged-block skip — therefore behaves exactly
+ * as upstream: the next call re-runs archive_entry_clear /
+ * archive_clear_error.
+ */
+ if (r2 != ARCHIVE_RETRY)
+ a->read_header_in_progress = 0;
@@ -1384,2 +1417,29 @@
&filter->client_buff);
+ /*
+ * BUN PATCH: non-blocking read support.
+ *
+ * A client reader that is being fed incrementally
+ * (e.g. tarball bytes arriving from the network
+ * during `bun install`) returns ARCHIVE_RETRY when
+ * no data is currently available. Unlike a real
+ * error this must not poison the filter: whatever
+ * has already been copied into `filter->buffer`
+ * stays put so the next call with the same `min`
+ * resumes exactly where we left off, and the caller
+ * can yield its thread and try again once more
+ * input has arrived.
+ *
+ * Callers that do not opt in (pass avail == NULL or
+ * do not check for ARCHIVE_RETRY) will see NULL and
+ * treat it as truncated input, which matches the
+ * pre-patch behaviour for non-streaming sources.
+ */
+ if (bytes_read == ARCHIVE_RETRY) {
+ filter->client_total = filter->client_avail = 0;
+ filter->client_next =
+ filter->client_buff = NULL;
+ if (avail != NULL)
+ *avail = ARCHIVE_RETRY;
+ return (NULL);
+ }
if (bytes_read < 0) { /* Read error. */
@@ -1579,3 +1639,10 @@
filter->client_buff = NULL;
- filter->fatal = 1;
+ /*
+ * BUN PATCH: see the matching comment in
+ * __archive_read_filter_ahead. A non-blocking
+ * reader returning ARCHIVE_RETRY must not mark
+ * the filter fatal.
+ */
+ if (bytes_read != ARCHIVE_RETRY)
+ filter->fatal = 1;
return (bytes_read);
--- a/libarchive/archive_read_support_filter_gzip.c
+++ b/libarchive/archive_read_support_filter_gzip.c
@@ -63,2 +63,9 @@
char eof; /* True = found end of compressed data. */
+ /*
+ * BUN PATCH: set after Z_STREAM_END so that a retry which
+ * interrupts consume_trailer() does not re-enter
+ * consume_header() (which would try to parse deflate bytes as
+ * a gzip header).
+ */
+ char trailer_pending;
};
@@ -148,2 +155,10 @@
p = __archive_read_filter_ahead(filter, len, &avail);
+ /*
+ * BUN PATCH: propagate non-blocking retry. Nothing has been
+ * consumed yet, so calling peek_at_header again once more
+ * input is available will re-read the same header bytes from
+ * filter->buffer.
+ */
+ if (p == NULL && avail == ARCHIVE_RETRY)
+ return (ARCHIVE_RETRY);
if (p == NULL || avail == 0)
@@ -172,3 +187,3 @@
if (p == NULL)
- return (0);
+ return (avail == ARCHIVE_RETRY ? ARCHIVE_RETRY : 0);
len += ((int)p[len + 1] << 8) | (int)p[len];
@@ -192,3 +207,3 @@
if (p == NULL)
- return (0);
+ return (avail == ARCHIVE_RETRY ? ARCHIVE_RETRY : 0);
} while (p[len - 1] != 0);
@@ -216,3 +231,3 @@
if (p == NULL)
- return (0);
+ return (avail == ARCHIVE_RETRY ? ARCHIVE_RETRY : 0);
} while (p[len - 1] != 0);
@@ -224,3 +239,3 @@
if (p == NULL)
- return (0);
+ return (avail == ARCHIVE_RETRY ? ARCHIVE_RETRY : 0);
#if 0
@@ -251,3 +266,12 @@
- if (peek_at_header(filter, &bits_checked, NULL))
+ /*
+ * BUN PATCH: peek_at_header() may now return ARCHIVE_RETRY
+ * from a non-blocking source. Bidding has no retry channel,
+ * so only accept a positive header length as a match.
+ * Streaming callers (bun install) always deliver at least
+ * one HTTP chunk — well over the ~10-byte gzip header —
+ * before archive_read_open() is invoked, so this branch is
+ * defensive rather than expected.
+ */
+ if (peek_at_header(filter, &bits_checked, NULL) > 0)
return (bits_checked);
@@ -343,4 +367,3 @@
struct private_data *state;
- ssize_t avail;
- size_t len;
+ ssize_t len;
int ret;
@@ -351,2 +374,10 @@
len = peek_at_header(self->upstream, NULL, state);
+ /*
+ * BUN PATCH: peek_at_header consumes nothing, so on
+ * ARCHIVE_RETRY the caller can simply try again later and the
+ * partial header bytes already copied into the upstream
+ * filter's buffer will still be there.
+ */
+ if (len == ARCHIVE_RETRY)
+ return (ARCHIVE_RETRY);
if (len == 0)
@@ -358,6 +389,12 @@
- /* Initialize compression library. */
- state->stream.next_in = (unsigned char *)(uintptr_t)
- __archive_read_filter_ahead(self->upstream, 1, &avail);
- state->stream.avail_in = (uInt)avail;
+ /*
+ * Initialize compression library. next_in / avail_in are
+ * unused by inflateInit2 with a negative windowBits (raw
+ * deflate); the main read loop primes them before each
+ * inflate() call, so we do not need an extra read-ahead here
+ * that could itself return ARCHIVE_RETRY after the header has
+ * already been consumed.
+ */
+ state->stream.next_in = NULL;
+ state->stream.avail_in = 0;
ret = inflateInit2(&(state->stream),
@@ -406,11 +443,21 @@
- state->in_stream = 0;
- switch (inflateEnd(&(state->stream))) {
- case Z_OK:
- break;
- default:
- archive_set_error(&self->archive->archive,
- ARCHIVE_ERRNO_MISC,
- "Failed to clean up gzip decompressor");
- return (ARCHIVE_FATAL);
+ /*
+ * BUN PATCH: inflateEnd()/in_stream=0 are only safe to run
+ * once. If a previous consume_trailer() attempt was
+ * interrupted by ARCHIVE_RETRY while reading the 8-byte
+ * footer, `trailer_pending` stays set and we skip straight to
+ * the read-ahead below.
+ */
+ if (!state->trailer_pending) {
+ state->in_stream = 0;
+ switch (inflateEnd(&(state->stream))) {
+ case Z_OK:
+ break;
+ default:
+ archive_set_error(&self->archive->archive,
+ ARCHIVE_ERRNO_MISC,
+ "Failed to clean up gzip decompressor");
+ return (ARCHIVE_FATAL);
+ }
+ state->trailer_pending = 1;
}
@@ -419,2 +466,4 @@
p = __archive_read_filter_ahead(self->upstream, 8, &avail);
+ if (p == NULL && avail == ARCHIVE_RETRY)
+ return (ARCHIVE_RETRY);
if (p == NULL || avail == 0)
@@ -426,2 +475,3 @@
__archive_read_filter_consume(self->upstream, 8);
+ state->trailer_pending = 0;
@@ -446,2 +496,14 @@
while (state->stream.avail_out > 0 && !state->eof) {
+ /*
+ * BUN PATCH: finish any pending trailer first. This can
+ * only be set when a previous gzip_filter_read() call hit
+ * ARCHIVE_RETRY inside consume_trailer().
+ */
+ if (state->trailer_pending) {
+ ret = consume_trailer(self);
+ if (ret == ARCHIVE_RETRY)
+ goto bun_retry;
+ if (ret < ARCHIVE_OK)
+ return (ret);
+ }
/* If we're not in a stream, read a header
@@ -454,2 +516,4 @@
}
+ if (ret == ARCHIVE_RETRY)
+ goto bun_retry;
if (ret < ARCHIVE_OK)
@@ -464,2 +528,10 @@
if (state->stream.next_in == NULL) {
+ /*
+ * BUN PATCH: upstream has no bytes right now but
+ * isn't done. zlib's inflate state is untouched,
+ * so propagate the retry (or return whatever we
+ * have already decompressed this call).
+ */
+ if (avail_in == ARCHIVE_RETRY)
+ goto bun_retry;
archive_set_error(&self->archive->archive,
@@ -490,2 +562,4 @@
ret = consume_trailer(self);
+ if (ret == ARCHIVE_RETRY)
+ goto bun_retry;
if (ret < ARCHIVE_OK)
@@ -510,2 +584,21 @@
return (decompressed);
+
+bun_retry:
+ /*
+ * BUN PATCH: upstream ran dry mid-call. If we managed to
+ * decompress anything before that happened, hand it back
+ * now; the tar layer will consume it and the next call will
+ * start with an empty out_block and immediately retry.
+ * Otherwise propagate ARCHIVE_RETRY so the tar layer (and
+ * ultimately the Zig extract loop) can yield this worker and
+ * reschedule when more bytes arrive.
+ */
+ decompressed = state->stream.next_out - state->out_block;
+ if (decompressed > 0) {
+ state->total_out += decompressed;
+ *p = state->out_block;
+ return (decompressed);
+ }
+ *p = NULL;
+ return (ARCHIVE_RETRY);
}
--- a/libarchive/archive_read_support_format_tar.c
+++ b/libarchive/archive_read_support_format_tar.c
@@ -155,2 +155,19 @@
int read_concatenated_archives;
+
+ /*
+ * BUN PATCH: resume state for non-blocking sources.
+ *
+ * `tar_read_header` walks a variable-length sequence of
+ * extension headers (pax 'x'/'g', GNU 'L'/'K', etc.) before
+ * the real ustar header. When the underlying reader returns
+ * ARCHIVE_RETRY part-way through that walk, we must remember
+ * which extensions have already been parsed so the next call
+ * picks up at the right header instead of re-running
+ * `tar_reset_header_state` and re-bidding bytes that were
+ * already consumed.
+ */
+ int header_in_progress;
+ int32_t header_seen;
+ int header_eof_fatal;
+ int header_err;
};
@@ -231,2 +248,4 @@
static int64_t tar_atol8(const char *, size_t);
+static int tar_consume_retryable(struct archive_read *, struct tar *,
+ int64_t);
static int tar_read_header(struct archive_read *, struct tar *,
@@ -475,2 +494,49 @@
+/*
+ * BUN PATCH: consume up to `request` bytes in a way that is safe to
+ * resume from a non-blocking source. Each iteration uses
+ * __archive_read_ahead(1) to discover how many bytes are currently
+ * buffered and then __archive_read_consume()s exactly that much, so
+ * the consume never has to invoke the client reader itself. On
+ * ARCHIVE_RETRY the remaining amount is written back into
+ * `tar->entry_bytes_remaining` / `tar->entry_padding` so the next
+ * call (via archive_read_next_header → read_data_skip, or another
+ * read_data_block) continues from the right offset.
+ */
+static int
+tar_consume_retryable(struct archive_read *a, struct tar *tar,
+ int64_t request)
+{
+ while (request > 0) {
+ ssize_t avail = 0;
+ const void *p = __archive_read_ahead(a, 1, &avail);
+ if (p == NULL) {
+ if (avail == ARCHIVE_RETRY) {
+ /* Persist what is still owed so the
+ * next resume asks for the remainder
+ * rather than the original total. */
+ if (request >= tar->entry_padding) {
+ tar->entry_bytes_remaining =
+ request - tar->entry_padding;
+ } else {
+ tar->entry_bytes_remaining = 0;
+ tar->entry_padding = request;
+ }
+ return (ARCHIVE_RETRY);
+ }
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_MISC,
+ "Truncated tar archive"
+ " detected while skipping data");
+ return (ARCHIVE_FATAL);
+ }
+ if (avail > request)
+ avail = (ssize_t)request;
+ if (__archive_read_consume(a, avail) != avail)
+ return (ARCHIVE_FATAL);
+ request -= avail;
+ }
+ return (ARCHIVE_OK);
+}
+
/* utility function- this exists to centralize the logic of tracking
@@ -535,25 +601,35 @@
- /* Assign default device/inode values. */
- archive_entry_set_dev(entry, 1 + default_dev); /* Don't use zero. */
- archive_entry_set_ino(entry, ++default_inode); /* Don't use zero. */
- /* Limit generated st_ino number to 16 bits. */
- if (default_inode >= 0xffff) {
- ++default_dev;
- default_inode = 0;
- }
-
tar = (struct tar *)(a->format->data);
- tar->entry_offset = 0;
- gnu_clear_sparse_list(tar);
- tar->size_fields = 0; /* We don't have any size info yet */
- /* Setup default string conversion. */
- tar->sconv = tar->opt_sconv;
- if (tar->sconv == NULL) {
- if (!tar->init_default_conversion) {
- tar->sconv_default =
- archive_string_default_conversion_for_read(&(a->archive));
- tar->init_default_conversion = 1;
+ /*
+ * BUN PATCH: when resuming after an ARCHIVE_RETRY from a
+ * non-blocking source, skip the fresh-entry initialisation
+ * below. Extension headers parsed before the retry have
+ * already written into `entry` and `tar->entry_*`; repeating
+ * these resets would discard that work.
+ */
+ if (!tar->header_in_progress) {
+ /* Assign default device/inode values. */
+ archive_entry_set_dev(entry, 1 + default_dev); /* Don't use zero. */
+ archive_entry_set_ino(entry, ++default_inode); /* Don't use zero. */
+ /* Limit generated st_ino number to 16 bits. */
+ if (default_inode >= 0xffff) {
+ ++default_dev;
+ default_inode = 0;
+ }
+
+ tar->entry_offset = 0;
+ gnu_clear_sparse_list(tar);
+ tar->size_fields = 0; /* We don't have any size info yet */
+
+ /* Setup default string conversion. */
+ tar->sconv = tar->opt_sconv;
+ if (tar->sconv == NULL) {
+ if (!tar->init_default_conversion) {
+ tar->sconv_default =
+ archive_string_default_conversion_for_read(&(a->archive));
+ tar->init_default_conversion = 1;
+ }
+ tar->sconv = tar->sconv_default;
}
- tar->sconv = tar->sconv_default;
}
@@ -562,2 +638,13 @@
+ /*
+ * BUN PATCH: a non-blocking yield leaves `header_in_progress`
+ * set; in that case `*unconsumed == 0` (nothing pending that
+ * isn't already flushed) so the caller can yield and re-enter
+ * later. Upstream's damaged-block ARCHIVE_RETRY clears the
+ * flag and falls through to the original post-read handling
+ * below (which is what upstream did).
+ */
+ if (r == ARCHIVE_RETRY && tar->header_in_progress)
+ return (ARCHIVE_RETRY);
+
tar_flush_unconsumed(a, &unconsumed);
@@ -637,5 +724,16 @@
- if (__archive_read_consume(a, request) != request)
+ /*
+ * BUN PATCH: make the end-of-entry padding
+ * consume resumable. `tar_consume_retryable`
+ * decrements `entry_bytes_remaining` /
+ * `entry_padding` as bytes become available so a
+ * subsequent call picks up with the remainder.
+ */
+ int cr = tar_consume_retryable(a, tar, request);
+ if (cr == ARCHIVE_RETRY)
+ return (ARCHIVE_RETRY);
+ if (cr != ARCHIVE_OK)
return (ARCHIVE_FATAL);
tar->entry_padding = 0;
+ tar->entry_bytes_remaining = 0;
*buff = NULL;
@@ -648,2 +746,11 @@
if (*buff == NULL) {
+ /*
+ * BUN PATCH: propagate non-blocking retry. Every
+ * counter we would have touched
+ * (`entry_bytes_remaining`, `sparse_list`, etc.)
+ * is still at its pre-call value, so the caller
+ * can yield and re-enter read_data_block later.
+ */
+ if (bytes_read == ARCHIVE_RETRY)
+ return (ARCHIVE_RETRY);
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
@@ -676,2 +783,3 @@
int64_t request;
+ int cr;
struct tar* tar;
@@ -683,3 +791,14 @@
- if (__archive_read_consume(a, request) != request)
+ /*
+ * BUN PATCH: use the retryable consume so a non-blocking
+ * source can yield mid-skip. Progress is recorded in
+ * `entry_bytes_remaining` / `entry_padding` /
+ * `entry_bytes_unconsumed`, so archive_read_next_header's
+ * implicit skip on re-entry resumes with the remainder.
+ */
+ tar->entry_bytes_unconsumed = 0;
+ cr = tar_consume_retryable(a, tar, request);
+ if (cr == ARCHIVE_RETRY)
+ return (ARCHIVE_RETRY);
+ if (cr != ARCHIVE_OK)
return (ARCHIVE_FATAL);
@@ -687,3 +806,2 @@
tar->entry_bytes_remaining = 0;
- tar->entry_bytes_unconsumed = 0;
tar->entry_padding = 0;
@@ -721,4 +839,4 @@
ssize_t bytes;
- int err = ARCHIVE_OK, err2;
- int eof_fatal = 0; /* EOF is okay at some points... */
+ int err, err2;
+ int eof_fatal; /* EOF is okay at some points... */
const char *h;
@@ -728,3 +846,3 @@
/* Bitmask of what header types we've seen. */
- int32_t seen_headers = 0;
+ int32_t seen_headers;
static const int32_t seen_A_header = 1;
@@ -737,3 +855,20 @@
- tar_reset_header_state(tar);
+ /*
+ * BUN PATCH: `header_in_progress` persists the header loop's
+ * locals across an ARCHIVE_RETRY so a non-blocking source can
+ * resume mid-sequence. On a fresh entry we initialise them
+ * exactly as before; on resume we restore and skip the reset
+ * that would otherwise wipe pax/GNU long-name data already
+ * parsed into `tar->entry_*`.
+ */
+ if (!tar->header_in_progress) {
+ tar_reset_header_state(tar);
+ tar->header_in_progress = 1;
+ tar->header_seen = 0;
+ tar->header_eof_fatal = 0;
+ tar->header_err = ARCHIVE_OK;
+ }
+ seen_headers = tar->header_seen;
+ eof_fatal = tar->header_eof_fatal;
+ err = tar->header_err;
@@ -745,2 +880,13 @@
+/*
+ * BUN PATCH: every terminal exit from this function (anything other
+ * than the non-blocking retry path) must clear `header_in_progress`
+ * so the next entry starts with fresh `seen_headers` / header state.
+ * The macro keeps the existing `return (X)` sites readable.
+ */
+#define TAR_HEADER_RETURN(rc) do { \
+ tar->header_in_progress = 0; \
+ return (rc); \
+ } while (0)
+
/*
@@ -757,3 +903,3 @@
if (tar_flush_unconsumed(a, unconsumed) != ARCHIVE_OK) {
- return (ARCHIVE_FATAL);
+ TAR_HEADER_RETURN(ARCHIVE_FATAL);
}
@@ -762,2 +908,13 @@
h = __archive_read_ahead(a, 512, &bytes);
+ if (h == NULL && bytes == ARCHIVE_RETRY) {
+ /*
+ * BUN PATCH: non-blocking source ran
+ * out mid-header. Nothing has been
+ * added to `*unconsumed` yet this
+ * iteration, so save loop state and
+ * let the caller try again once more
+ * input arrives.
+ */
+ goto bun_retry;
+ }
if (bytes == 0) { /* EOF at a block boundary. */
@@ -769,5 +926,5 @@
"Damaged tar archive (end-of-archive within a sequence of headers)");
- return (ARCHIVE_FATAL);
+ TAR_HEADER_RETURN(ARCHIVE_FATAL);
} else {
- return (ARCHIVE_EOF);
+ TAR_HEADER_RETURN(ARCHIVE_EOF);
}
@@ -779,3 +936,3 @@
" detected while reading next header");
- return (ARCHIVE_FATAL);
+ TAR_HEADER_RETURN(ARCHIVE_FATAL);
}
@@ -799,3 +956,3 @@
archive_clear_error(&a->archive);
- return (ARCHIVE_EOF);
+ TAR_HEADER_RETURN(ARCHIVE_EOF);
}
@@ -805,3 +962,3 @@
if (tar_flush_unconsumed(a, unconsumed) != ARCHIVE_OK) {
- return (ARCHIVE_FATAL);
+ TAR_HEADER_RETURN(ARCHIVE_FATAL);
}
@@ -812,5 +969,18 @@
if (eof_fatal) {
- return (ARCHIVE_FATAL);
+ TAR_HEADER_RETURN(ARCHIVE_FATAL);
} else {
- return (ARCHIVE_RETRY);
+ /*
+ * BUN PATCH: upstream's
+ * "damaged block, skip and retry
+ * the next one" — NOT a
+ * non-blocking yield. The next
+ * call must re-run the full
+ * state reset (archive_entry_clear,
+ * tar_reset_header_state) exactly
+ * as upstream, so clear both
+ * in-progress flags instead of
+ * routing through bun_retry.
+ */
+ a->read_header_in_progress = 0;
+ TAR_HEADER_RETURN(ARCHIVE_RETRY);
}
@@ -822,2 +992,63 @@
header = (const struct archive_entry_header_ustar *)h;
+
+ /*
+ * BUN PATCH: extension headers ('A','g','K','L','V',
+ * 'x','X') are followed by a payload whose length is
+ * encoded in this block's `size` field. The handlers
+ * below flush `*unconsumed` (this 512-byte block) and
+ * then __archive_read_ahead() the payload; if that
+ * ahead were to return ARCHIVE_RETRY after the flush,
+ * the block would already be consumed and the resume
+ * would read payload bytes as a header.
+ *
+ * To keep the handlers unchanged, pre-buffer the
+ * header+payload here *before* the flush. On retry we
+ * roll back `*unconsumed` so nothing is consumed and
+ * the next attempt re-reads this exact block from the
+ * filter's buffer.
+ */
+ switch (header->typeflag[0]) {
+ case 'A': case 'g': case 'K': case 'L':
+ case 'V': case 'X': case 'x': {
+ int64_t ext_size = tar_atol(header->size,
+ sizeof(header->size));
+ /* Bound the prebuffer before rounding
+ * (tar_atol saturates to INT64_MAX on
+ * overflow, so adding 511 first would be UB).
+ * A corrupted stream can produce garbage that
+ * decodes as an extension header with a
+ * multi-GB `size`; retrying `ahead(512+huge)`
+ * every chunk would never make progress. Real
+ * pax/GNU extension payloads are path-length
+ * bounded (a few KB), so anything beyond this
+ * is handed to the handler below, which will
+ * fail cleanly on its own
+ * `__archive_read_ahead`. */
+ if (ext_size < 0 || ext_size > (int64_t)(1u << 20))
+ break;
+ int64_t ext_padded = (ext_size + 511) & ~511;
+ if (__archive_read_ahead(a,
+ (size_t)(512 + ext_padded), &bytes) == NULL &&
+ bytes == ARCHIVE_RETRY) {
+ *unconsumed -= 512;
+ goto bun_retry;
+ }
+ /* Re-establish `h`: the filter may have
+ * memmoved its buffer to satisfy the larger
+ * request. A fatal error from the larger
+ * read (alloc failure, downstream I/O error)
+ * leaves `filter->fatal` set, so this read
+ * can return NULL even though the original
+ * 512 bytes are still buffered; bail cleanly
+ * rather than dereferencing `header` below. */
+ h = __archive_read_ahead(a, 512, NULL);
+ if (h == NULL)
+ TAR_HEADER_RETURN(ARCHIVE_FATAL);
+ header = (const struct archive_entry_header_ustar *)h;
+ break;
+ }
+ default:
+ break;
+ }
+
switch(header->typeflag[0]) {
@@ -827,3 +1058,3 @@
"Redundant 'A' header");
- return (ARCHIVE_FATAL);
+ TAR_HEADER_RETURN(ARCHIVE_FATAL);
}
@@ -838,3 +1069,3 @@
"Redundant 'g' header");
- return (ARCHIVE_FATAL);
+ TAR_HEADER_RETURN(ARCHIVE_FATAL);
}
@@ -882,3 +1113,3 @@
"Redundant 'X'/'x' header");
- return (ARCHIVE_FATAL);
+ TAR_HEADER_RETURN(ARCHIVE_FATAL);
}
@@ -894,3 +1125,3 @@
"Redundant 'x' header");
- return (ARCHIVE_FATAL);
+ TAR_HEADER_RETURN(ARCHIVE_FATAL);
}
@@ -921,3 +1152,3 @@
if (err < ARCHIVE_WARN) {
- return (ARCHIVE_FATAL);
+ TAR_HEADER_RETURN(ARCHIVE_FATAL);
}
@@ -940,3 +1171,3 @@
if (err2 < ARCHIVE_WARN) {
- return (ARCHIVE_FATAL);
+ TAR_HEADER_RETURN(ARCHIVE_FATAL);
}
@@ -955,3 +1186,3 @@
"Non-regular file cannot be sparse");
- return (ARCHIVE_WARN);
+ TAR_HEADER_RETURN(ARCHIVE_WARN);
} else if (tar->sparse_gnu_major == 0 &&
@@ -968,3 +1199,3 @@
if (bytes_read < 0)
- return ((int)bytes_read);
+ TAR_HEADER_RETURN((int)bytes_read);
tar->entry_bytes_remaining -= bytes_read;
@@ -974,6 +1205,6 @@
"Unrecognized GNU sparse file format");
- return (ARCHIVE_WARN);
+ TAR_HEADER_RETURN(ARCHIVE_WARN);
}
}
- return (err);
+ TAR_HEADER_RETURN(err);
}
@@ -983,3 +1214,3 @@
if (err == ARCHIVE_FATAL)
- return (err);
+ TAR_HEADER_RETURN(err);
@@ -993,2 +1224,22 @@
}
+
+bun_retry:
+ /*
+ * BUN PATCH: persist loop state so the next tar_read_header()
+ * call resumes exactly here. At this point `*unconsumed == 0`
+ * (either nothing was added this iteration, or we rolled the
+ * 512-byte header back above), so
+ * `archive_read_format_tar_read_header` can return
+ * ARCHIVE_RETRY without flushing. Setting
+ * `read_header_in_progress` here (rather than having
+ * archive_read_next_header2 set it on every RETRY) is what
+ * lets upstream's damaged-block RETRY keep its original
+ * reset-everything semantics.
+ */
+ tar->header_seen = seen_headers;
+ tar->header_eof_fatal = eof_fatal;
+ tar->header_err = err;
+ a->read_header_in_progress = 1;
+ return (ARCHIVE_RETRY);
+#undef TAR_HEADER_RETURN
}
+35
View File
@@ -0,0 +1,35 @@
--- a/src/turbojpeg.c
+++ b/src/turbojpeg.c
@@ -1220,12 +1220,14 @@
#define BITS_IN_JSAMPLE 8
#include "turbojpeg-mp.c"
#undef BITS_IN_JSAMPLE
+#ifndef BUN_8BIT_ONLY
#define BITS_IN_JSAMPLE 12
#include "turbojpeg-mp.c"
#undef BITS_IN_JSAMPLE
#define BITS_IN_JSAMPLE 16
#include "turbojpeg-mp.c"
#undef BITS_IN_JSAMPLE
+#endif
/* TurboJPEG 1.2+ */
DLLEXPORT int tjCompress2(tjhandle handle, const unsigned char *srcBuf,
--- a/src/turbojpeg-mp.c
+++ b/src/turbojpeg-mp.c
@@ -280,6 +280,7 @@
/*************************** Packed-Pixel Image I/O **************************/
+#ifndef BUN_8BIT_ONLY
#if BITS_IN_JSAMPLE != 16 || defined(C_LOSSLESS_SUPPORTED)
/* TurboJPEG 3.0+ */
@@ -561,6 +562,7 @@
#endif
}
+#endif /* BUN_8BIT_ONLY */
#undef _JSAMPLE
+64
View File
@@ -0,0 +1,64 @@
/* libjpeg-turbo's master_selection() in jcmaster.c/jdmaster.c dispatches at
* RUNTIME on cinfo->data_precision and calls j12init_* / j16init_* directly,
* with no compile-time gate. Upstream satisfies those by recompiling a subset
* of sources twice more with -DBITS_IN_JSAMPLE=12/16; we only ship 8-bit, so
* provide ERREXIT stubs instead and avoid ~60 extra TUs. The branches that
* reach these are already preceded by a precision check that ERREXITs for
* anything Bun.Image would feed in, so these stubs are belt-and-braces.
*
* tj3LoadImage8/tj3SaveImage8 are gated out by 8bit-only.patch but the legacy
* v2 tjLoadImage/tjSaveImage shims still reference them; stub those too.
*/
#define JPEG_INTERNALS
#include "jinclude.h"
#include "jpeglib.h"
#include "jerror.h"
#define DIE_C(name) \
void name(j_compress_ptr cinfo) { ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision); }
#define DIE_C2(name) \
void name(j_compress_ptr cinfo, boolean b) { (void)b; ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision); }
#define DIE_D(name) \
void name(j_decompress_ptr cinfo) { ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision); }
#define DIE_D2(name) \
void name(j_decompress_ptr cinfo, boolean b) { (void)b; ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision); }
DIE_C2(j12init_c_coef_controller)
DIE_C2(j12init_c_diff_controller)
DIE_C2(j12init_c_main_controller)
DIE_C2(j12init_c_prep_controller)
DIE_C(j12init_color_converter)
DIE_C(j12init_downsampler)
DIE_C(j12init_forward_dct)
DIE_C(j12init_lossless_compressor)
DIE_C2(j16init_c_diff_controller)
DIE_C2(j16init_c_main_controller)
DIE_C2(j16init_c_prep_controller)
DIE_C(j16init_color_converter)
DIE_C(j16init_downsampler)
DIE_C(j16init_lossless_compressor)
DIE_D(j12init_color_deconverter)
DIE_D2(j12init_d_coef_controller)
DIE_D2(j12init_d_diff_controller)
DIE_D2(j12init_d_main_controller)
DIE_D2(j12init_d_post_controller)
DIE_D(j12init_inverse_dct)
DIE_D(j12init_lossless_decompressor)
DIE_D(j12init_merged_upsampler)
DIE_D(j12init_upsampler)
DIE_D(j12init_1pass_quantizer)
DIE_D(j12init_2pass_quantizer)
DIE_D(j16init_color_deconverter)
DIE_D2(j16init_d_diff_controller)
DIE_D2(j16init_d_main_controller)
DIE_D2(j16init_d_post_controller)
DIE_D(j16init_lossless_decompressor)
DIE_D(j16init_merged_upsampler)
DIE_D(j16init_upsampler)
unsigned char *tj3LoadImage8(void *h, const char *f, int *w, int a, int *ht, int *pf)
{ (void)h; (void)f; (void)w; (void)a; (void)ht; (void)pf; return 0; }
int tj3SaveImage8(void *h, const char *f, const unsigned char *b, int w, int p, int ht, int pf)
{ (void)h; (void)f; (void)b; (void)w; (void)p; (void)ht; (void)pf; return -1; }
@@ -0,0 +1,51 @@
--- a/src/win/poll.c
+++ b/src/win/poll.c
@@ -115,9 +115,24 @@
AFD_POLL_DISCONNECT | AFD_POLL_ACCEPT | AFD_POLL_ABORT;
} else {
if (handle->events & UV_DISCONNECT) {
- afd_poll_info->Handles[0].Events |= AFD_POLL_DISCONNECT;
+ /* A peer abort (RST) must wake a watcher that asked for disconnect
+ * even when it is not reading: AFD only reports subscribed events, so
+ * without ABORT here a reset against a write-only poll is never
+ * delivered and teardown waits forever (see the reporting hunk). */
+ afd_poll_info->Handles[0].Events |= AFD_POLL_DISCONNECT |
+ AFD_POLL_ABORT;
}
}
+ if (handle->events & UV_PRIORITIZED) {
+ /* ABORT-only subscription. A graceful FIN leaves AFD_POLL_DISCONNECT
+ * signaled forever (AFD is level-triggered), so a watcher that already
+ * consumed the FIN cannot keep DISCONNECT armed without completing
+ * instantly in a loop - yet it still needs the peer's later RST.
+ * UV_PRIORITIZED, unused on Windows, subscribes exactly that remaining
+ * signal; the reporting below surfaces it as UV_PRIORITIZED so the
+ * requested-events mask keeps it. */
+ afd_poll_info->Handles[0].Events |= AFD_POLL_ABORT;
+ }
if (handle->events & UV_WRITABLE) {
afd_poll_info->Handles[0].Events |= AFD_POLL_SEND | AFD_POLL_CONNECT_FAIL;
}
@@ -168,9 +183,19 @@
if ((afd_poll_info->Handles[0].Events & (AFD_POLL_RECEIVE |
AFD_POLL_DISCONNECT | AFD_POLL_ACCEPT | AFD_POLL_ABORT)) != 0) {
events |= UV_READABLE;
- if ((afd_poll_info->Handles[0].Events & AFD_POLL_DISCONNECT) != 0) {
- events |= UV_DISCONNECT;
- }
+ }
+ if ((afd_poll_info->Handles[0].Events &
+ (AFD_POLL_DISCONNECT | AFD_POLL_ABORT)) != 0) {
+ /* Graceful FIN and abortive RST both mean the peer is gone. Report
+ * DISCONNECT for either, outside the READABLE block, so it survives
+ * the requested-events mask when only UV_DISCONNECT is armed. */
+ events |= UV_DISCONNECT;
+ }
+ if ((afd_poll_info->Handles[0].Events & AFD_POLL_ABORT) != 0) {
+ /* The ABORT-only subscription reports as UV_PRIORITIZED: a watcher
+ * armed with UV_PRIORITIZED alone has UV_DISCONNECT stripped by the
+ * requested-events mask below. */
+ events |= UV_PRIORITIZED;
}
if ((afd_poll_info->Handles[0].Events & (AFD_POLL_SEND |
AFD_POLL_CONNECT_FAIL)) != 0) {
@@ -0,0 +1,36 @@
--- a/src/win/poll.c
+++ b/src/win/poll.c
@@ -186,11 +186,33 @@
uv__handle_stop(handle);
}
+ /* Re-arm the AFD poll **before** invoking poll_cb so an AFD ioctl is
+ * pending while user code runs. AFD is level-triggered (ReactOS
+ * AfdSelect: `Events & FCB->PollState` checked on IRP arrival), so a
+ * RST that lands during poll_cb is caught by the freshly-submitted req.
+ * Re-arming *after* poll_cb (the previous order) leaves a gap: an
+ * in-process loopback peer that RSTs from inside the callback —
+ * e.g. a JS handler that drainMicrotasks() into a same-process
+ * fetch().abort() — can land between completion and re-submission,
+ * and although AFD_POLL_ABORT is in PollState, no ioctl is pending
+ * to observe it until after the callback returns. wepoll does the
+ * same re-arm-before-return (port_wait → port__update_events_if_polling
+ * before LeaveCriticalSection). The dual-req + mask_events bookkeeping
+ * already handles poll_cb calling uv_poll_start/stop mid-flight. */
+ if ((handle->events & ~(handle->submitted_events_1 |
+ handle->submitted_events_2)) != 0) {
+ uv__fast_poll_submit_poll_req(loop, handle);
+ }
+
if (events != 0) {
handle->poll_cb(handle, 0, events);
}
}
+ /* poll_cb may have called uv_poll_start (covered above by the dual-req
+ * submitting the other slot) or uv_poll_stop / uv_close. Only the
+ * close-endgame check needs to run post-callback. A re-submit here is
+ * still needed for the `events == 0` path (filtered-out completion). */
if ((handle->events & ~(handle->submitted_events_1 |
handle->submitted_events_2)) != 0) {
uv__fast_poll_submit_poll_req(loop, handle);
+146
View File
@@ -0,0 +1,146 @@
--- a/lshpack.c 2026-05-02 06:41:46.290110113 +0000
+++ b/lshpack.c 2026-05-02 06:42:07.599902350 +0000
@@ -53,6 +53,103 @@
#include "huff-tables.h"
+#if LS_HPACK_USE_LARGE_TABLES && LS_HPACK_BSS_LARGE_TABLES
+/* Bun: hencs[65536] (512 KB) and hdecs[65536] (256 KB) are pure functions of
+ * encode_table[257]. Declare them in .bss instead of .rodata and fill once on
+ * first lshpack/lsqpack init — ~250 us, identical hot-path lookup afterwards.
+ * Non-static so lsqpack.c can extern the same arrays instead of carrying its
+ * own copy. */
+struct henc { unsigned lens; uint32_t code; };
+struct hdec { uint8_t lens; uint8_t out[3]; };
+struct henc hencs[65536];
+struct hdec hdecs[65536];
+static int hpack_huff_tables_ready;
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define HENC_I(i,j) (((j)<<8)|(i))
+#else
+#define HENC_I(i,j) (((i)<<8)|(j))
+#endif
+
+void
+lshpack_huff_tables_init (void)
+{
+ /* Init is idempotent (every thread writes the same values), so a race only
+ * does redundant work — but the release/acquire pair ensures the table
+ * stores are visible before any later encode/decode load on another
+ * thread sees ready==1. */
+ if (__atomic_load_n(&hpack_huff_tables_ready, __ATOMIC_ACQUIRE))
+ return;
+
+ /* hencs: combine two encode_table entries. {64,0} marks pairs whose
+ * combined width overflows 32 bits (handled by the slow loop). */
+ for (unsigned i = 0; i < 256; ++i)
+ {
+ const unsigned bi = encode_table[i].bits;
+ const uint32_t ci = encode_table[i].code;
+ for (unsigned j = 0; j < 256; ++j)
+ {
+ const unsigned bj = encode_table[j].bits;
+ struct henc *h = &hencs[HENC_I(i, j)];
+ if (bi + bj <= 32)
+ {
+ h->lens = bi + bj;
+ h->code = (ci << bj) | encode_table[j].code;
+ }
+ else
+ {
+ h->lens = 64;
+ h->code = 0;
+ }
+ }
+ }
+
+ /* hdecs: for each 16-bit prefix, decode up to 3 symbols using a 15-bit
+ * direct LUT built from encode_table (codes <= 15 bits cover the common
+ * ASCII set; longer codes fall through to the slow path with lens==0). */
+ static struct { uint8_t bits, sym; } lut[1 << 15];
+ for (unsigned sym = 0; sym < 256; ++sym)
+ {
+ const unsigned b = encode_table[sym].bits;
+ if (b > 15)
+ continue;
+ const unsigned base = encode_table[sym].code << (15 - b);
+ for (unsigned k = 0; k < (1u << (15 - b)); ++k)
+ {
+ lut[base + k].bits = (uint8_t) b;
+ lut[base + k].sym = (uint8_t) sym;
+ }
+ }
+ for (unsigned idx = 0; idx < 65536; ++idx)
+ {
+ unsigned bits_left = 16, n = 0;
+ uint8_t out[3] = {0,0,0};
+ while (bits_left >= 5 && n < 3)
+ {
+ const unsigned rem = idx & ((1u << bits_left) - 1);
+ const unsigned key = bits_left >= 15
+ ? (rem >> (bits_left - 15))
+ : (rem << (15 - bits_left));
+ const unsigned b = lut[key].bits;
+ if (!b || b > bits_left)
+ break;
+ out[n++] = lut[key].sym;
+ bits_left -= b;
+ }
+ if (n)
+ {
+ hdecs[idx].lens = (uint8_t)(((16 - bits_left) << 2) | n);
+ hdecs[idx].out[0] = out[0];
+ hdecs[idx].out[1] = out[1];
+ hdecs[idx].out[2] = out[2];
+ }
+ /* else: leave zeroed (.bss) — lens==0 triggers slow path */
+ }
+
+ __atomic_store_n(&hpack_huff_tables_ready, 1, __ATOMIC_RELEASE);
+}
+#endif
+
#define HPACK_STATIC_TABLE_SIZE 61
#define INITIAL_DYNAMIC_TABLE_SIZE 4096
@@ -291,6 +384,9 @@
int
lshpack_enc_init (struct lshpack_enc *enc)
{
+#if LS_HPACK_USE_LARGE_TABLES && LS_HPACK_BSS_LARGE_TABLES
+ lshpack_huff_tables_init();
+#endif
struct lshpack_double_enc_head *buckets;
unsigned nbits = 2;
unsigned i;
@@ -1270,6 +1366,9 @@
void
lshpack_dec_init (struct lshpack_dec *dec)
{
+#if LS_HPACK_USE_LARGE_TABLES && LS_HPACK_BSS_LARGE_TABLES
+ lshpack_huff_tables_init();
+#endif
memset(dec, 0, sizeof(*dec));
dec->hpd_max_capacity = INITIAL_DYNAMIC_TABLE_SIZE;
dec->hpd_cur_max_capacity = INITIAL_DYNAMIC_TABLE_SIZE;
--- a/huff-tables.h 2026-05-02 06:41:46.300110018 +0000
+++ b/huff-tables.h 2026-05-02 06:42:07.509903227 +0000
@@ -266,7 +266,7 @@
};
-#if LS_HPACK_USE_LARGE_TABLES
+#if LS_HPACK_USE_LARGE_TABLES && !LS_HPACK_BSS_LARGE_TABLES
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define I(i,j) ((j<<8)|i)
#else
@@ -70703,7 +70703,7 @@
};
-#if LS_HPACK_USE_LARGE_TABLES
+#if LS_HPACK_USE_LARGE_TABLES && !LS_HPACK_BSS_LARGE_TABLES
static const struct hdec { uint8_t lens; uint8_t out[3]; } hdecs[] =
{
/*
+49
View File
@@ -0,0 +1,49 @@
--- a/lsqpack.c 2026-05-02 06:42:53.039458414 +0000
+++ b/lsqpack.c 2026-05-02 06:43:11.469277877 +0000
@@ -69,6 +69,15 @@
#include <xxhash.h>
#endif
+#if LS_QPACK_USE_LARGE_TABLES && LS_HPACK_BSS_LARGE_TABLES
+/* Bun: share lshpack's .bss-backed hencs/hdecs (same RFC 7541 Huffman code)
+ * instead of carrying a second 768 KB copy in .rodata. */
+struct henc { unsigned lens; uint32_t code; };
+struct hdec { uint8_t lens; uint8_t out[3]; };
+extern struct henc hencs[65536];
+extern struct hdec hdecs[65536];
+extern void lshpack_huff_tables_init(void);
+#endif
#include "huff-tables.h"
#define MIN(a, b) ((a) < (b) ? (a) : (b))
@@ -502,6 +511,9 @@
unsigned max_risked_streams, enum lsqpack_enc_opts enc_opts,
unsigned char *tsu_buf, size_t *tsu_buf_sz)
{
+#if LS_QPACK_USE_LARGE_TABLES && LS_HPACK_BSS_LARGE_TABLES
+ lshpack_huff_tables_init();
+#endif
struct lsqpack_double_enc_head *buckets;
unsigned char *p;
unsigned nbits = 2;
@@ -2782,6 +2794,9 @@
unsigned dyn_table_size, unsigned max_risked_streams,
const struct lsqpack_dec_hset_if *dh_if, enum lsqpack_dec_opts opts)
{
+#if LS_QPACK_USE_LARGE_TABLES && LS_HPACK_BSS_LARGE_TABLES
+ lshpack_huff_tables_init();
+#endif
unsigned i;
memset(dec, 0, sizeof(*dec));
dec->qpd_opts = opts;
--- a/huff-tables.h 2026-05-02 06:42:53.039458414 +0000
+++ b/huff-tables.h 2026-05-02 06:43:11.459277975 +0000
@@ -5141,7 +5141,7 @@
},
};
-#if LS_QPACK_USE_LARGE_TABLES
+#if LS_QPACK_USE_LARGE_TABLES && !LS_HPACK_BSS_LARGE_TABLES
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define I(i,j) ((j<<8)|i)
#else
+17
View File
@@ -0,0 +1,17 @@
--- a/src/liblsquic/lsquic_full_conn_ietf.c
+++ b/src/liblsquic/lsquic_full_conn_ietf.c
@@ -9258,6 +9258,14 @@
LSQ_INFO("abort error: is_app: %d; error code: %u; error str: %s",
is_app, error_code, err_str);
ABORT_QUIETLY(is_app, error_code, "%s", err_str);
+ /* bun: ABORT_QUIETLY only raises IFC_ERROR; it does not make the
+ * connection tickable. immediate_close() -- which turns that error into
+ * the CONNECTION_CLOSE frame -- only runs from the connection's tick, so
+ * without this the frame waits for whatever ticks the connection next
+ * (an idle or retransmission alarm, potentially seconds away) even though
+ * the application asked to abort now. ci_close() marks it for the same
+ * reason. */
+ lsquic_engine_add_conn_to_tickable(conn->ifc_enpub, lconn);
}
+18
View File
@@ -0,0 +1,18 @@
--- a/src/liblsquic/lsquic_enc_sess_ietf.c
+++ b/src/liblsquic/lsquic_enc_sess_ietf.c
@@ -1331,14 +1331,7 @@
#endif
if (!server_name)
{
- if (enc_sess->esi_enpub->enp_flags & ENPUB_HTTP)
- {
- LSQ_DEBUG("SNI is not set, but is required in HTTP/3: "
- "fail certificate lookup");
- return 0;
- }
- else
- LSQ_DEBUG("cert lookup: server name is not set");
+ LSQ_DEBUG("cert lookup: server name is not set");
}
path = enc_sess->esi_conn->cn_if->ci_get_path(enc_sess->esi_conn, NULL);
+69
View File
@@ -0,0 +1,69 @@
--- a/src/liblsquic/lsquic_engine.c 2026-07-07 16:55:39
+++ b/src/liblsquic/lsquic_engine.c 2026-07-07 16:55:39
@@ -2906,6 +2906,22 @@
while (++iov < end);
return size;
+}
+
+
+/* Give back the packets already staged into the current, not-yet-finalized
+ * out_spec. A coalescing bail-out (encrypt failure, OOM, copy failure) only
+ * returned the packet it failed on; the ones batched before it had already
+ * been removed from the scheduled queue, so nothing sent them and nothing
+ * freed them. Reverse order, to match send_batch()'s ordering rule.
+ */
+static void
+return_staged_packets (lsquic_conn_t *conn,
+ struct lsquic_packet_out **first,
+ struct lsquic_packet_out **past_last)
+{
+ while (past_last > first)
+ conn->cn_if->ci_packet_not_sent(conn, *--past_last);
}
@@ -2916,7 +2932,7 @@
{
unsigned n, w, n_sent, n_batches_sent;
lsquic_packet_out_t *packet_out;
- struct lsquic_packet_out **packet;
+ struct lsquic_packet_out **packet, **packet_spec;
lsquic_conn_t *conn;
struct out_batch *const batch = &engine->out_batch;
struct iovec *iov, *packet_iov;
@@ -2950,6 +2966,7 @@
continue;
}
batch->outs[n].iov = packet_iov = iov;
+ packet_spec = packet;
next_coa:
if (!(packet_out->po_flags & (PO_ENCRYPTED|PO_NOENCRYPT)))
{
@@ -2959,10 +2976,16 @@
case ENCPA_NOMEM:
/* Send what we have and wait for a more opportune moment */
conn->cn_if->ci_packet_not_sent(conn, packet_out);
+ return_staged_packets(conn, packet_spec, packet);
+ packet = packet_spec;
+ iov = packet_iov;
goto end_for;
case ENCPA_BADCRYPT:
/* This is pretty bad: close connection immediately */
conn->cn_if->ci_packet_not_sent(conn, packet_out);
+ return_staged_packets(conn, packet_spec, packet);
+ packet = packet_spec;
+ iov = packet_iov;
LSQ_INFOC("conn %"CID_FMT" has unsendable packets",
CID_BITS(lsquic_conn_log_cid(conn)));
if (!(conn->cn_flags & LSCONN_EVANESCENT))
@@ -2982,6 +3005,9 @@
{
/* Copy can only fail if packet could not be allocated */
conn->cn_if->ci_packet_not_sent(conn, packet_out);
+ return_staged_packets(conn, packet_spec, packet);
+ packet = packet_spec;
+ iov = packet_iov;
goto end_for;
}
}
+93
View File
@@ -0,0 +1,93 @@
--- a/src/liblsquic/lsquic_full_conn_ietf.c
+++ b/src/liblsquic/lsquic_full_conn_ietf.c
@@ -4669,4 +4669,11 @@
*/
- packet_out = lsquic_send_ctl_new_packet_out(&conn->ifc_send_ctl, 0, PNS_APP,
+ enum packnum_space pns; /* bun: highest PNS with keys (RFC 9000 s10.2.3) */
+ if (conn->ifc_conn.cn_flags & LSCONN_HANDSHAKE_DONE)
+ pns = PNS_APP;
+ else if (conn->ifc_flags & IFC_IGNORE_INIT)
+ pns = PNS_HSK;
+ else
+ pns = PNS_INIT;
+ packet_out = lsquic_send_ctl_new_packet_out(&conn->ifc_send_ctl, 0, pns,
CUR_NPATH(conn));
if (!packet_out)
--- a/src/liblsquic/lsquic_mini_conn_ietf.h
+++ b/src/liblsquic/lsquic_mini_conn_ietf.h
@@ -91,6 +91,10 @@
lsquic_time_t imc_largest_recvd[IMICO_N_PNS];
struct lsquic_rtt_stats imc_rtt_stats;
enum trans_error_code imc_error_code;
+ /* bun: the peer's CONNECTION_CLOSE code (62-bit; the enum above cannot
+ * hold it), valid when IMC_CLOSE_RECVD is set and IMC_PARSE_FAILED is
+ * not. */
+ uint64_t imc_close_code;
unsigned imc_bytes_in;
unsigned imc_bytes_out;
unsigned short imc_crypto_frames_sz;
--- a/src/liblsquic/lsquic_mini_conn_ietf.c
+++ b/src/liblsquic/lsquic_mini_conn_ietf.c
@@ -1346,6 +1346,9 @@
}
EV_LOG_CONNECTION_CLOSE_FRAME_IN(LSQUIC_LOG_CONN_ID, error_code,
(int) reason_len, (const char *) p + reason_off);
+ /* bun: keep the peer's code so on_mini_conn_failed can report a peer
+ * close instead of inventing a handshake failure. */
+ conn->imc_close_code = error_code;
LSQ_INFO("Received CONNECTION_CLOSE frame (%s-level code: %"PRIu64"; "
"reason: %.*s)", app_error ? "application" : "transport",
error_code, (int) reason_len, (const char *) p + reason_off);
--- a/src/liblsquic/lsquic_engine.c
+++ b/src/liblsquic/lsquic_engine.c
@@ -982,6 +982,12 @@
error_code = 0x100 + imc->imc_tls_alert;
else if (imc->imc_flags & IMC_BAD_TRANS_PARAMS)
error_code = TEC_TRANSPORT_PARAMETER_ERROR;
+ else if ((imc->imc_flags & (IMC_CLOSE_RECVD|IMC_PARSE_FAILED))
+ == IMC_CLOSE_RECVD)
+ /* bun: the peer closed during the handshake. QUIC codes fit in
+ * 62 bits, so bit 63 marks "peer's own CONNECTION_CLOSE" and the
+ * low bits carry its code. */
+ error_code = (1ULL << 63) | imc->imc_close_code;
else
error_code = TEC_NO_ERROR;
if (0 == lsquic_conn_get_sockaddr(conn, &local_sa, &peer_sa))
--- a/src/liblsquic/lsquic_enc_sess_ietf.c
+++ b/src/liblsquic/lsquic_enc_sess_ietf.c
@@ -2368,8 +2368,21 @@
}
enc_level = hety2el[packet_in->pi_header_type];
+ /* bun: an Initial in the version used before a compatible version switch
+ * (RFC 9368) -- e.g. the peer's CONNECTION_CLOSE sent before it processed
+ * our packets -- needs the keys iquic_esfi_switch_version saved; the
+ * current slot holds the new version's. The wire version field picks the
+ * key set deterministically. */
+ struct hsk_crypto *vn_hsk;
+ if (enc_level == ENC_LEV_INIT && enc_sess->esi_vn_save
+ && packet_in->pi_version != enc_sess->esi_conn->cn_version)
+ vn_hsk = enc_sess->esi_vn_save;
+ else
+ vn_hsk = NULL;
if (enc_level == ENC_LEV_APP)
hp = &enc_sess->esi_hp;
+ else if (vn_hsk)
+ hp = &vn_hsk->hp;
else if (enc_sess->esi_hsk_crypto)
hp = &enc_sess->esi_hsk_crypto[ enc_level ].hp;
else
@@ -2455,8 +2468,11 @@
else
{
key_phase = 0;
- assert(enc_sess->esi_hsk_crypto);
- pair = &enc_sess->esi_hsk_crypto[ enc_level ].pair;
+ assert(enc_sess->esi_hsk_crypto || vn_hsk);
+ if (vn_hsk)
+ pair = &vn_hsk->pair;
+ else
+ pair = &enc_sess->esi_hsk_crypto[ enc_level ].pair;
crypto_ctx = &pair->ykp_ctx[ 0 ];
if (UNLIKELY(0 == (crypto_ctx->yk_flags & YK_INITED)))
{
+111
View File
@@ -0,0 +1,111 @@
--- a/src/liblsquic/lsquic_gquic_stubs.c
+++ b/src/liblsquic/lsquic_gquic_stubs.c
@@ -0,0 +1,108 @@
+/* Bun: gQUIC (Google QUIC) is unreachable — Bun.serve and the H3 client only
+ * negotiate IETF QUIC versions (see packages/bun-usockets/src/quic.c, which
+ * masks es_versions with LSQUIC_IETF_VERSIONS). The real gQUIC sources pull
+ * in ~130 KB of common_cert_set tables plus the legacy handshake/parser. We
+ * compile this stub instead so the few unconditional references from
+ * lsquic_engine.c / lsquic_global.c / lsquic_pr_queue.c / lsquic_stream.c
+ * still link, while LTO drops everything behind them. None of these are
+ * reached at runtime when no gQUIC version bit is set. */
+
+#include <errno.h>
+#include <stddef.h>
+#include <sys/queue.h>
+
+#include "lsquic.h"
+#include "lsquic_types.h"
+#include "lsquic_int_types.h"
+#include "lsquic_packet_common.h"
+#include "lsquic_packet_gquic.h"
+#include "lsquic_packet_in.h"
+#include "lsquic_parse_common.h"
+#include "lsquic_parse.h"
+#include "lsquic_enc_sess.h"
+#include "lsquic_headers_stream.h"
+
+struct lsquic_engine_public;
+
+static int gquic_stub_global_init (int flags) { (void) flags; return 0; }
+static void gquic_stub_global_cleanup (void) { }
+
+#ifdef NDEBUG
+const
+#endif
+struct enc_session_funcs_common lsquic_enc_session_common_gquic_1 = {
+ .esf_global_init = gquic_stub_global_init,
+ .esf_global_cleanup = gquic_stub_global_cleanup,
+};
+
+#ifdef NDEBUG
+const
+#endif
+struct enc_session_funcs_common lsquic_enc_session_common_gquic_2 = {
+ .esf_global_init = gquic_stub_global_init,
+ .esf_global_cleanup = gquic_stub_global_cleanup,
+};
+
+#ifdef NDEBUG
+const
+#endif
+struct enc_session_funcs_gquic lsquic_enc_session_gquic_gquic_1;
+
+/* lsquic_pr_queue.c pre-builds a gQUIC reset packet at server init via
+ * select_pf_by_ver(LSQVER_043)->pf_generate_simple_prst. Return a one-byte
+ * placeholder so the assert(len > 0)/len < 0 checks pass; the buffer is only
+ * sent for PACKET_REQ_PUBRES which is never queued without a gQUIC version. */
+static ssize_t gquic_stub_generate_simple_prst (const lsquic_cid_t *cid,
+ unsigned char *buf, size_t sz)
+{
+ (void) cid;
+ if (sz) buf[0] = 0;
+ return sz ? 1 : 0;
+}
+
+const struct parse_funcs lsquic_parse_funcs_gquic_Q043 = {
+ .pf_generate_simple_prst = gquic_stub_generate_simple_prst,
+};
+const struct parse_funcs lsquic_parse_funcs_gquic_Q046;
+const struct parse_funcs lsquic_parse_funcs_gquic_Q050;
+
+int lsquic_Q050_parse_packet_in_long_begin (struct lsquic_packet_in *p,
+ size_t l, int s, unsigned c, struct packin_parse_state *st)
+{ (void)p;(void)l;(void)s;(void)c;(void)st; return -1; }
+
+struct lsquic_conn *
+lsquic_gquic_full_conn_client_new (struct lsquic_engine_public *e,
+ unsigned v, unsigned f, const char *h, unsigned short m, int i,
+ const unsigned char *r, size_t rl)
+{ (void)e;(void)v;(void)f;(void)h;(void)m;(void)i;(void)r;(void)rl;
+ errno = EPROTONOSUPPORT; return NULL; }
+
+struct lsquic_conn *
+lsquic_gquic_full_conn_server_new (struct lsquic_engine_public *e,
+ unsigned f, struct lsquic_conn *m)
+{ (void)e;(void)f;(void)m; errno = EPROTONOSUPPORT; return NULL; }
+
+lsquic_conn_t *
+lsquic_mini_conn_new (struct lsquic_engine_public *e,
+ const struct lsquic_packet_in *p, enum lsquic_version v)
+{ (void)e;(void)p;(void)v; return NULL; }
+
+int lsquic_headers_stream_send_headers (struct headers_stream *hs,
+ lsquic_stream_id_t id, const struct lsquic_http_headers *h, int eos,
+ unsigned w)
+{ (void)hs;(void)id;(void)h;(void)eos;(void)w; return -1; }
+
+int lsquic_headers_stream_send_priority (struct headers_stream *hs,
+ lsquic_stream_id_t id, int ex, lsquic_stream_id_t dep, unsigned w)
+{ (void)hs;(void)id;(void)ex;(void)dep;(void)w; return -1; }
+
+struct lsquic_stream *
+lsquic_headers_stream_get_stream (const struct headers_stream *hs)
+{ (void)hs; return NULL; }
+
+/* lsquic_engine.c calls these unconditionally; the real impls allocate gQUIC
+ * X25519/TLS state. With no gQUIC versions enabled they're never used. */
+int lsquic_init_gquic_crypto (struct lsquic_engine_public *enpub)
+{ (void)enpub; return 0; }
+void lsquic_cleanup_gquic_crypto (struct lsquic_engine_public *enpub)
+{ (void)enpub; }
+58
View File
@@ -0,0 +1,58 @@
--- a/src/liblsquic/lsquic_hash.h
+++ b/src/liblsquic/lsquic_hash.h
@@ -69,6 +69,19 @@
struct lsquic_hash_elem *
lsquic_hash_next (struct lsquic_hash *);
+/* lsquic_hash_first()/_next() keep their cursor INSIDE the hash, so a second
+ * walk started while one is in progress resets the first one's position. Use
+ * these when a nested walk of the same hash is possible: the caller holds the
+ * cursor, so nesting is safe. */
+struct lsquic_hash_elem *
+lsquic_hash_first_nested (struct lsquic_hash *);
+
+static inline struct lsquic_hash_elem *
+lsquic_hash_next_nested (struct lsquic_hash_elem *el)
+{
+ return TAILQ_NEXT(el, qhe_next_all);
+}
+
unsigned
lsquic_hash_count (struct lsquic_hash *);
--- a/src/liblsquic/lsquic_hash.c
+++ b/src/liblsquic/lsquic_hash.c
@@ -209,6 +209,13 @@
}
+struct lsquic_hash_elem *
+lsquic_hash_first_nested (struct lsquic_hash *hash)
+{
+ return TAILQ_FIRST(&hash->qh_all);
+}
+
+
void
lsquic_hash_reset_iter (struct lsquic_hash *hash)
{
--- a/src/liblsquic/lsquic_send_ctl.c
+++ b/src/liblsquic/lsquic_send_ctl.c
@@ -2984,9 +2984,15 @@
if (!(ctl->sc_conn_pub->cp_flags & CP_HAVE_PRIO))
return BPT_HIGHEST_PRIO;
+ /* Nest-safe walk: ietf_full_conn_ci_close() iterates all_streams with the
+ * hash's shared cursor and resets each bidi stream; when the send
+ * controller cannot send immediately that RESET_STREAM takes the buffered
+ * path and lands here. lsquic_hash_first() would reset the cursor the
+ * close loop is walking, restarting it over a hash whose elements it is
+ * destroying. */
all_streams = ctl->sc_conn_pub->all_streams;
- for (el = lsquic_hash_first(all_streams); el;
- el = lsquic_hash_next(all_streams))
+ for (el = lsquic_hash_first_nested(all_streams); el;
+ el = lsquic_hash_next_nested(el))
{
other_stream = lsquic_hashelem_getdata(el);
if (other_stream != stream
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,34 @@
send_batch: requeue every packet in an unsent coalesced datagram
`off` is `unsigned`, so when the first unsent spec in a batch is at
index 0 and coalesces multiple packets, `&batch->packets[off - 1]`
indexes with UINT_MAX and `end` lands far past the array. The
`--packet_out > end` condition is then false after the first iteration
and only the last packet of the coalesced group is returned to the
connection; the earlier ones (typically the INIT ACK and the HSK
CRYPTO carrying the client Finished) are silently dropped and never
retransmitted, so the peer never completes the handshake.
Rewriting the bounds as [off, off+count) avoids the underflow while
preserving the reverse iteration order that send_ctl_sched_prepend
relies on.
--- a/src/liblsquic/lsquic_engine.c
+++ b/src/liblsquic/lsquic_engine.c
@@ -2739,12 +2739,12 @@
off = batch->pack_off[i];
count = batch->outs[i].iovlen;
assert(count > 0);
- packet_out = &batch->packets[off + count - 1];
- end = &batch->packets[off - 1];
+ packet_out = &batch->packets[off + count];
+ end = &batch->packets[off];
do
batch->conns[i]->cn_if->ci_packet_not_sent(batch->conns[i],
- *packet_out);
- while (--packet_out > end);
+ *--packet_out);
+ while (packet_out > end);
if (!(batch->conns[i]->cn_flags & (LSCONN_COI_ACTIVE|LSCONN_EVANESCENT)))
coi_reactivate(sb_ctx->conns_iter, batch->conns[i]);
}
+42
View File
@@ -0,0 +1,42 @@
--- a/src/liblsquic/lsquic_conn_public.h 2026-04-20 23:16:15.000000000 +0000
+++ b/src/liblsquic/lsquic_conn_public.h 2026-04-27 08:51:00.711139257 +0000
@@ -49,6 +49,7 @@
} u;
enum {
CP_STREAM_UNBLOCKED = 1 << 0, /* Set when a stream becomes unblocked */
+ CP_HAVE_PRIO = 1 << 1, /* Set once any stream gets a non-default priority */
} cp_flags;
struct lsquic_send_ctl *send_ctl;
#if LSQUIC_CONN_STATS
--- a/src/liblsquic/lsquic_send_ctl.c 2026-04-20 23:16:15.000000000 +0000
+++ b/src/liblsquic/lsquic_send_ctl.c 2026-04-27 08:51:00.711139257 +0000
@@ -2963,6 +2963,11 @@
struct lsquic_hash_elem *el;
struct lsquic_hash *all_streams;
+ /* When no stream on this connection has ever been assigned a
+ * non-default priority, the loop below cannot find a lower one. */
+ if (!(ctl->sc_conn_pub->cp_flags & CP_HAVE_PRIO))
+ return BPT_HIGHEST_PRIO;
+
all_streams = ctl->sc_conn_pub->all_streams;
for (el = lsquic_hash_first(all_streams); el;
el = lsquic_hash_next(all_streams))
--- a/src/liblsquic/lsquic_stream.c 2026-04-20 23:16:15.000000000 +0000
+++ b/src/liblsquic/lsquic_stream.c 2026-04-27 08:51:00.711139257 +0000
@@ -4677,6 +4677,7 @@
{
if (0 == lsquic_stream_set_priority_internal(stream, priority))
{
+ stream->conn_pub->cp_flags |= CP_HAVE_PRIO;
if (stream->sm_bflags & SMBF_IETF)
{
if (stream->sm_bflags & SMBF_HTTP_PRIO)
@@ -5589,6 +5590,7 @@
return -1;
}
stream->sm_priority = ehp->urgency;
+ stream->conn_pub->cp_flags |= CP_HAVE_PRIO;
if (ehp->incremental)
stream->sm_bflags |= SMBF_INCREMENTAL;
else
+204
View File
@@ -0,0 +1,204 @@
diff -urN a/src/liblsquic/lsquic_versions_to_string.c b/src/liblsquic/lsquic_versions_to_string.c
--- a/src/liblsquic/lsquic_versions_to_string.c 1970-01-01 00:00:00.000000000 +0000
+++ b/src/liblsquic/lsquic_versions_to_string.c 2026-04-27 01:30:38.609686072 +0000
@@ -0,0 +1,200 @@
+/*
+ * Auto-generated by gen-verstrs.pl - committed for DirectBuild
+ */
+
+#include <assert.h>
+#include <string.h>
+
+#include "lsquic.h"
+
+struct lsquic_engine;
+
+static const char *const versions_to_string[ 1 << N_LSQVER ] = {
+ [0] = "",
+ [(1<<LSQVER_043)] = "43",
+ [(1<<LSQVER_046)] = "46",
+ [(1<<LSQVER_043)|(1<<LSQVER_046)] = "43,46",
+};
+
+
+const char *
+lsquic_get_alt_svc_versions (unsigned versions)
+{
+ /* Limit to versions in versions_to_string: */
+ versions &= ((1<<LSQVER_043)|(1<<LSQVER_046));
+ return versions_to_string[ versions ];
+}
+
+static const struct {
+ unsigned versions;
+ const char *h3_alpns[8];
+} vers_2_h3_alnps[] = {
+ { 0, { NULL }},
+ {(1<<LSQVER_043), { "h3-Q043", NULL }},
+ {(1<<LSQVER_046), { "h3-Q046", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046), { "h3-Q043", "h3-Q046", NULL }},
+ {(1<<LSQVER_050), { "h3-Q050", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_050), { "h3-Q043", "h3-Q050", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_050), { "h3-Q046", "h3-Q050", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_050), { "h3-Q043", "h3-Q046", "h3-Q050", NULL }},
+ {(1<<LSQVER_ID27), { "h3-27", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_ID27), { "h3-Q043", "h3-27", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_ID27), { "h3-Q046", "h3-27", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_ID27), { "h3-Q043", "h3-Q046", "h3-27", NULL }},
+ {(1<<LSQVER_050)|(1<<LSQVER_ID27), { "h3-Q050", "h3-27", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_050)|(1<<LSQVER_ID27), { "h3-Q043", "h3-Q050", "h3-27", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID27), { "h3-Q046", "h3-Q050", "h3-27", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID27), { "h3-Q043", "h3-Q046", "h3-Q050", "h3-27", NULL }},
+ {(1<<LSQVER_ID29), { "h3-29", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_ID29), { "h3-Q043", "h3-29", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_ID29), { "h3-Q046", "h3-29", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_ID29), { "h3-Q043", "h3-Q046", "h3-29", NULL }},
+ {(1<<LSQVER_050)|(1<<LSQVER_ID29), { "h3-Q050", "h3-29", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_050)|(1<<LSQVER_ID29), { "h3-Q043", "h3-Q050", "h3-29", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID29), { "h3-Q046", "h3-Q050", "h3-29", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID29), { "h3-Q043", "h3-Q046", "h3-Q050", "h3-29", NULL }},
+ {(1<<LSQVER_ID27)|(1<<LSQVER_ID29), { "h3-27", "h3-29", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29), { "h3-Q043", "h3-27", "h3-29", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29), { "h3-Q046", "h3-27", "h3-29", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29), { "h3-Q043", "h3-Q046", "h3-27", "h3-29", NULL }},
+ {(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29), { "h3-Q050", "h3-27", "h3-29", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29), { "h3-Q043", "h3-Q050", "h3-27", "h3-29", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29), { "h3-Q046", "h3-Q050", "h3-27", "h3-29", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29), { "h3-Q043", "h3-Q046", "h3-Q050", "h3-27", "h3-29", NULL }},
+ {(1<<LSQVER_I001), { "h3", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_I001), { "h3-Q043", "h3", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_I001), { "h3-Q046", "h3", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_I001), { "h3-Q043", "h3-Q046", "h3", NULL }},
+ {(1<<LSQVER_050)|(1<<LSQVER_I001), { "h3-Q050", "h3", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_050)|(1<<LSQVER_I001), { "h3-Q043", "h3-Q050", "h3", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_I001), { "h3-Q046", "h3-Q050", "h3", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_I001), { "h3-Q043", "h3-Q046", "h3-Q050", "h3", NULL }},
+ {(1<<LSQVER_ID27)|(1<<LSQVER_I001), { "h3-27", "h3", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_ID27)|(1<<LSQVER_I001), { "h3-Q043", "h3-27", "h3", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_ID27)|(1<<LSQVER_I001), { "h3-Q046", "h3-27", "h3", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_ID27)|(1<<LSQVER_I001), { "h3-Q043", "h3-Q046", "h3-27", "h3", NULL }},
+ {(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_I001), { "h3-Q050", "h3-27", "h3", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_I001), { "h3-Q043", "h3-Q050", "h3-27", "h3", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_I001), { "h3-Q046", "h3-Q050", "h3-27", "h3", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_I001), { "h3-Q043", "h3-Q046", "h3-Q050", "h3-27", "h3", NULL }},
+ {(1<<LSQVER_ID29)|(1<<LSQVER_I001), { "h3-29", "h3", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_ID29)|(1<<LSQVER_I001), { "h3-Q043", "h3-29", "h3", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_ID29)|(1<<LSQVER_I001), { "h3-Q046", "h3-29", "h3", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_ID29)|(1<<LSQVER_I001), { "h3-Q043", "h3-Q046", "h3-29", "h3", NULL }},
+ {(1<<LSQVER_050)|(1<<LSQVER_ID29)|(1<<LSQVER_I001), { "h3-Q050", "h3-29", "h3", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_050)|(1<<LSQVER_ID29)|(1<<LSQVER_I001), { "h3-Q043", "h3-Q050", "h3-29", "h3", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID29)|(1<<LSQVER_I001), { "h3-Q046", "h3-Q050", "h3-29", "h3", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID29)|(1<<LSQVER_I001), { "h3-Q043", "h3-Q046", "h3-Q050", "h3-29", "h3", NULL }},
+ {(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I001), { "h3-27", "h3-29", "h3", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I001), { "h3-Q043", "h3-27", "h3-29", "h3", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I001), { "h3-Q046", "h3-27", "h3-29", "h3", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I001), { "h3-Q043", "h3-Q046", "h3-27", "h3-29", "h3", NULL }},
+ {(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I001), { "h3-Q050", "h3-27", "h3-29", "h3", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I001), { "h3-Q043", "h3-Q050", "h3-27", "h3-29", "h3", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I001), { "h3-Q046", "h3-Q050", "h3-27", "h3-29", "h3", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I001), { "h3-Q043", "h3-Q046", "h3-Q050", "h3-27", "h3-29", "h3", NULL }},
+ {(1<<LSQVER_I002), { "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_I002), { "h3-Q043", "", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_I002), { "h3-Q046", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q046", "", NULL }},
+ {(1<<LSQVER_050)|(1<<LSQVER_I002), { "h3-Q050", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_050)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q050", "", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_I002), { "h3-Q046", "h3-Q050", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q046", "h3-Q050", "", NULL }},
+ {(1<<LSQVER_ID27)|(1<<LSQVER_I002), { "h3-27", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_ID27)|(1<<LSQVER_I002), { "h3-Q043", "h3-27", "", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_ID27)|(1<<LSQVER_I002), { "h3-Q046", "h3-27", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_ID27)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q046", "h3-27", "", NULL }},
+ {(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_I002), { "h3-Q050", "h3-27", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q050", "h3-27", "", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_I002), { "h3-Q046", "h3-Q050", "h3-27", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q046", "h3-Q050", "h3-27", "", NULL }},
+ {(1<<LSQVER_ID29)|(1<<LSQVER_I002), { "h3-29", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_ID29)|(1<<LSQVER_I002), { "h3-Q043", "h3-29", "", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_ID29)|(1<<LSQVER_I002), { "h3-Q046", "h3-29", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_ID29)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q046", "h3-29", "", NULL }},
+ {(1<<LSQVER_050)|(1<<LSQVER_ID29)|(1<<LSQVER_I002), { "h3-Q050", "h3-29", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_050)|(1<<LSQVER_ID29)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q050", "h3-29", "", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID29)|(1<<LSQVER_I002), { "h3-Q046", "h3-Q050", "h3-29", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID29)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q046", "h3-Q050", "h3-29", "", NULL }},
+ {(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I002), { "h3-27", "h3-29", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I002), { "h3-Q043", "h3-27", "h3-29", "", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I002), { "h3-Q046", "h3-27", "h3-29", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q046", "h3-27", "h3-29", "", NULL }},
+ {(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I002), { "h3-Q050", "h3-27", "h3-29", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q050", "h3-27", "h3-29", "", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I002), { "h3-Q046", "h3-Q050", "h3-27", "h3-29", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q046", "h3-Q050", "h3-27", "h3-29", "", NULL }},
+ {(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q043", "h3", "", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q046", "h3", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q046", "h3", "", NULL }},
+ {(1<<LSQVER_050)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q050", "h3", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_050)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q050", "h3", "", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q046", "h3-Q050", "h3", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q046", "h3-Q050", "h3", "", NULL }},
+ {(1<<LSQVER_ID27)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-27", "h3", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_ID27)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q043", "h3-27", "h3", "", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_ID27)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q046", "h3-27", "h3", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_ID27)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q046", "h3-27", "h3", "", NULL }},
+ {(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q050", "h3-27", "h3", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q050", "h3-27", "h3", "", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q046", "h3-Q050", "h3-27", "h3", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q046", "h3-Q050", "h3-27", "h3", "", NULL }},
+ {(1<<LSQVER_ID29)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-29", "h3", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_ID29)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q043", "h3-29", "h3", "", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_ID29)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q046", "h3-29", "h3", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_ID29)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q046", "h3-29", "h3", "", NULL }},
+ {(1<<LSQVER_050)|(1<<LSQVER_ID29)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q050", "h3-29", "h3", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_050)|(1<<LSQVER_ID29)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q050", "h3-29", "h3", "", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID29)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q046", "h3-Q050", "h3-29", "h3", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID29)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q046", "h3-Q050", "h3-29", "h3", "", NULL }},
+ {(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-27", "h3-29", "h3", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q043", "h3-27", "h3-29", "h3", "", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q046", "h3-27", "h3-29", "h3", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q046", "h3-27", "h3-29", "h3", "", NULL }},
+ {(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q050", "h3-27", "h3-29", "h3", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q050", "h3-27", "h3-29", "h3", "", NULL }},
+ {(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q046", "h3-Q050", "h3-27", "h3-29", "h3", "", NULL }},
+ {(1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I001)|(1<<LSQVER_I002), { "h3-Q043", "h3-Q046", "h3-Q050", "h3-27", "h3-29", "h3", "", NULL }},
+};
+
+const char *const *
+lsquic_get_h3_alpns (unsigned versions)
+{
+ unsigned i;
+
+ versions &= ((1<<LSQVER_043)|(1<<LSQVER_046)|(1<<LSQVER_050)|(1<<LSQVER_ID27)|(1<<LSQVER_ID29)|(1<<LSQVER_I001)|(1<<LSQVER_I002));
+
+ for (i = 0; i < sizeof(vers_2_h3_alnps) / sizeof(vers_2_h3_alnps[0]); ++i)
+ if (versions == vers_2_h3_alnps[i].versions)
+ return vers_2_h3_alnps[i].h3_alpns;
+
+ assert(0);
+ return vers_2_h3_alnps[0].h3_alpns;
+}
+
+enum lsquic_version
+lsquic_alpn2ver (const char *alpn, size_t len)
+{
+ static const struct el {
+ size_t len;
+ char alpn[10];
+ enum lsquic_version version;
+ } map[] = {
+ {sizeof("h3-Q043")-1,"h3-Q043", LSQVER_043},
+ {sizeof("h3-Q046")-1,"h3-Q046", LSQVER_046},
+ {sizeof("h3-Q050")-1,"h3-Q050", LSQVER_050},
+ {sizeof("h3-27")-1,"h3-27", LSQVER_ID27},
+ {sizeof("h3-29")-1,"h3-29", LSQVER_ID29},
+ {sizeof("h3")-1,"h3", LSQVER_I001},
+ };
+ const struct el *el;
+
+ if (alpn)
+ for (el = map; el < map + sizeof(map) / sizeof(map[0]); ++el)
+ if (el->len == len && 0 == strncmp(el->alpn, alpn, len))
+ return el->version;
+
+ return -1;
+}
+12
View File
@@ -0,0 +1,12 @@
--- tcc.h
+++ tcc.h
@@ -23,7 +23,9 @@
#define _GNU_SOURCE
#define _DARWIN_C_SOURCE
+#if __has_include("config.h")
#include "config.h"
+#endif
#include <stdarg.h>
#include <stdlib.h>
+55
View File
@@ -0,0 +1,55 @@
clang-cl on Windows ARM64 defines _MSC_VER but is not real MSVC: it ships
clang's own <arm_neon.h>/<arm_acle.h>. MSVC's <arm64_neon.h> defines
vld1q_u16_x4/vld1q_u8_x4/vst1q_u16_x4 as macros, which macro-expand the
polyfill function definitions at neon_intrins.h:40-63 into garbage. MSVC's
<intrin.h> lacks the ARM ACLE CRC intrinsics (__crc32b etc.) that clang's
<arm_acle.h> provides.
Gate the MSVC-specific includes on !defined(__clang__) so clang-cl takes the
standard ACLE/NEON path. Also fix the matching cmake feature-detection probes
so HAVE_ARMV8_INTRIN and NEON_HAS_LD4 succeed under clang-cl, which sets
ARM_CRC32_INTRIN/ARM_NEON_HASLD4 and skips the polyfills entirely.
Upstream: not yet reported (zlib-ng CI does not cover clang-cl ARM64).
--- a/arch/arm/neon_intrins.h
+++ b/arch/arm/neon_intrins.h
@@ -1,7 +1,7 @@
#ifndef ARM_NEON_INTRINS_H
#define ARM_NEON_INTRINS_H
-#if defined(_MSC_VER) && (defined(_M_ARM64) || defined(_M_ARM64EC))
+#if defined(_MSC_VER) && !defined(__clang__) && (defined(_M_ARM64) || defined(_M_ARM64EC))
/* arm64_neon.h is MSVC specific */
# include <arm64_neon.h>
#else
--- a/arch/arm/acle_intrins.h
+++ b/arch/arm/acle_intrins.h
@@ -2,7 +2,7 @@
#define ARM_ACLE_INTRINS_H
#include <stdint.h>
-#ifdef _MSC_VER
+#if defined(_MSC_VER) && !defined(__clang__)
# include <intrin.h>
#elif defined(HAVE_ARM_ACLE_H)
# include <arm_acle.h>
--- a/cmake/detect-intrinsics.cmake
+++ b/cmake/detect-intrinsics.cmake
@@ -42,7 +42,7 @@ macro(check_armv8_compiler_flag)
)
# Check whether compiler supports ARMv8 intrinsics
check_c_source_compiles(
- "#if defined(_MSC_VER)
+ "#if defined(_MSC_VER) && !defined(__clang__)
#include <intrin.h>
#else
#include <arm_acle.h>
@@ -285,7 +285,7 @@ macro(check_neon_ld4_intrinsics)
# Check whether compiler supports loading 4 neon vecs into a register range
set(CMAKE_REQUIRED_FLAGS "${NEONFLAG} ${NATIVEFLAG} ${ZNOLTOFLAG}")
check_c_source_compiles(
- "#if defined(_MSC_VER) && (defined(_M_ARM64) || defined(_M_ARM64EC))
+ "#if defined(_MSC_VER) && !defined(__clang__) && (defined(_M_ARM64) || defined(_M_ARM64EC))
# include <arm64_neon.h>
#else
# include <arm_neon.h>