mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
## Summary This PR fixes multiple HTTP/2 protocol compliance issues that were causing stream errors with various HTTP/2 clients (Fauna, gRPC/Connect, etc.). fixes https://github.com/oven-sh/bun/issues/12544 fixes https://github.com/oven-sh/bun/issues/25589 ### Key Fixes **Window Size and Settings Handling** - Fix initial stream window size to use `DEFAULT_WINDOW_SIZE` until `SETTINGS_ACK` is received - Per RFC 7540 Section 6.5.1: The sender can only rely on settings being applied AFTER receiving `SETTINGS_ACK` - Properly adjust existing stream windows when `INITIAL_WINDOW_SIZE` setting changes (RFC 7540 Section 6.9.2) **Header List Size Enforcement** - Implement `maxHeaderListSize` checking per RFC 7540 Section 6.5.2 - Track cumulative header list size using HPACK entry overhead (32 bytes per RFC 7541 Section 4.1) - Reject streams with `ENHANCE_YOUR_CALM` when header list exceeds configured limit **Custom Settings Support** - Add validation for `customSettings` option (up to 10 custom settings, matching Node.js `MAX_ADDITIONAL_SETTINGS`) - Validate setting IDs are in range `[0, 0xFFFF]` per RFC 7540 - Validate setting values are in range `[0, 2^32-1]` **Settings Validation Improvements** - Use float comparison for settings validation to handle large values correctly (was using `toInt32()` which truncates) - Use proper `HTTP2_INVALID_SETTING_VALUE_RangeError` error codes for Node.js compatibility **BufferFallbackAllocator** - New allocator that tries a provided buffer first, falls back to heap: - Similar to `std.heap.stackFallback` but accepts external buffer slice - Used with `shared_request_buffer` (16KB threadlocal) for common cases - Falls back to `bun.default_allocator` for large headers ## Test Plan - [x] `bun bd` compiles successfully - [x] Node.js HTTP/2 tests pass: `bun bd test/js/node/test/parallel/test-http2-connect.js` - [x] New regression tests for frame size issues: `bun bd test test/regression/issue/25589.test.ts` - [x] HTTP/2 continuation tests: `bun bd test test/js/node/http2/node-http2-continuation.test.ts` --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Claude Bot <claude-bot@bun.sh> Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
86 lines
2.8 KiB
Zig
86 lines
2.8 KiB
Zig
/// An allocator that attempts to allocate from a provided buffer first,
|
|
/// falling back to another allocator when the buffer is exhausted.
|
|
/// Unlike `std.heap.StackFallbackAllocator`, this does not own the buffer.
|
|
const BufferFallbackAllocator = @This();
|
|
|
|
#fallback_allocator: Allocator,
|
|
#fixed_buffer_allocator: FixedBufferAllocator,
|
|
|
|
pub fn init(buffer: []u8, fallback_allocator: Allocator) BufferFallbackAllocator {
|
|
return .{
|
|
.#fallback_allocator = fallback_allocator,
|
|
.#fixed_buffer_allocator = FixedBufferAllocator.init(buffer),
|
|
};
|
|
}
|
|
|
|
pub fn allocator(self: *BufferFallbackAllocator) Allocator {
|
|
return .{
|
|
.ptr = self,
|
|
.vtable = &.{
|
|
.alloc = alloc,
|
|
.resize = resize,
|
|
.remap = remap,
|
|
.free = free,
|
|
},
|
|
};
|
|
}
|
|
|
|
fn alloc(ctx: *anyopaque, len: usize, alignment: std.mem.Alignment, ra: usize) ?[*]u8 {
|
|
const self: *BufferFallbackAllocator = @ptrCast(@alignCast(ctx));
|
|
return FixedBufferAllocator.alloc(
|
|
&self.#fixed_buffer_allocator,
|
|
len,
|
|
alignment,
|
|
ra,
|
|
) orelse self.#fallback_allocator.rawAlloc(len, alignment, ra);
|
|
}
|
|
|
|
fn resize(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, new_len: usize, ra: usize) bool {
|
|
const self: *BufferFallbackAllocator = @ptrCast(@alignCast(ctx));
|
|
if (self.#fixed_buffer_allocator.ownsPtr(buf.ptr)) {
|
|
return FixedBufferAllocator.resize(
|
|
&self.#fixed_buffer_allocator,
|
|
buf,
|
|
alignment,
|
|
new_len,
|
|
ra,
|
|
);
|
|
}
|
|
return self.#fallback_allocator.rawResize(buf, alignment, new_len, ra);
|
|
}
|
|
|
|
fn remap(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, new_len: usize, ra: usize) ?[*]u8 {
|
|
const self: *BufferFallbackAllocator = @ptrCast(@alignCast(ctx));
|
|
if (self.#fixed_buffer_allocator.ownsPtr(memory.ptr)) {
|
|
return FixedBufferAllocator.remap(
|
|
&self.#fixed_buffer_allocator,
|
|
memory,
|
|
alignment,
|
|
new_len,
|
|
ra,
|
|
);
|
|
}
|
|
return self.#fallback_allocator.rawRemap(memory, alignment, new_len, ra);
|
|
}
|
|
|
|
fn free(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, ra: usize) void {
|
|
const self: *BufferFallbackAllocator = @ptrCast(@alignCast(ctx));
|
|
if (self.#fixed_buffer_allocator.ownsPtr(buf.ptr)) {
|
|
return FixedBufferAllocator.free(
|
|
&self.#fixed_buffer_allocator,
|
|
buf,
|
|
alignment,
|
|
ra,
|
|
);
|
|
}
|
|
return self.#fallback_allocator.rawFree(buf, alignment, ra);
|
|
}
|
|
|
|
pub fn reset(self: *BufferFallbackAllocator) void {
|
|
self.#fixed_buffer_allocator.reset();
|
|
}
|
|
|
|
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const FixedBufferAllocator = std.heap.FixedBufferAllocator;
|