break more

This commit is contained in:
Ciro Spaciari
2025-05-29 17:47:58 -07:00
parent f1cf05cba6
commit 0e68af960d
2 changed files with 110 additions and 19 deletions

View File

@@ -108,26 +108,29 @@ const SettingsType = enum(u16) {
_, // we can have more unsupported extension settings types
};
inline fn u32FromBytes(src: []const u8) u32 {
var dst: u32 = 0;
@memcpy(@as(*[4]u8, @ptrCast(&dst)), src);
return @byteSwap(dst);
}
const UInt31WithReserved = packed struct(u32) {
reserved: bool = false,
uint31: u31 = 0,
pub fn from(value: u32) UInt31WithReserved {
return .{
.reserved = false,
.uint31 = @truncate(value),
};
const log = Output.scoped(.UInt31WithReserved, false);
pub inline fn from(value: u32) UInt31WithReserved {
return .{ .uint31 = @truncate(value & 0x7fffffff), .reserved = value & 0x80000000 != 0 };
}
pub fn toUInt32(value: UInt31WithReserved) u32 {
pub inline fn toUInt32(value: UInt31WithReserved) u32 {
return @bitCast(value);
}
pub inline fn fromBytes(src: []const u8) UInt31WithReserved {
var dst: u32 = 0;
@memcpy(@as(*[4]u8, @ptrCast(&dst)), src);
dst = @byteSwap(dst);
return @bitCast(dst);
const value: u32 = u32FromBytes(src);
return .{ .uint31 = @truncate(value & 0x7fffffff), .reserved = value & 0x80000000 != 0 };
}
pub inline fn write(this: UInt31WithReserved, comptime Writer: type, writer: Writer) bool {
@@ -662,7 +665,7 @@ pub const H2FrameParser = struct {
const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{});
pub const ref = RefCount.ref;
pub const deref = RefCount.deref;
const ENABLE_AUTO_CORK = false; // ENABLE CORK OPTIMIZATION
const ENABLE_AUTO_CORK = true; // ENABLE CORK OPTIMIZATION
const ENABLE_ALLOCATOR_POOL = true; // ENABLE HIVE ALLOCATOR OPTIMIZATION
const MAX_BUFFER_SIZE = 32768;
@@ -986,7 +989,7 @@ pub const H2FrameParser = struct {
this.remoteUsedWindowSize += able_to_send.len;
client.remoteUsedWindowSize += able_to_send.len;
log("dataFrame partial flushed {} {}", .{ able_to_send.len, frame.end_stream });
log("dataFrame partial flushed {} {} {} {} {} {} {}", .{ able_to_send.len, frame.end_stream, client.queuedDataSize, this.remoteUsedWindowSize, client.remoteUsedWindowSize, this.remoteWindowSize, client.remoteWindowSize });
const padding = this.getPadding(able_to_send.len, MAX_PAYLOAD_SIZE_WITHOUT_FRAME - 1);
const payload_size = able_to_send.len + (if (padding != 0) padding + 1 else 0);
@@ -1839,6 +1842,7 @@ pub const H2FrameParser = struct {
}
pub fn handleWindowUpdateFrame(this: *H2FrameParser, frame: FrameHeader, data: []const u8, stream: ?*Stream) usize {
log("handleWindowUpdateFrame {}", .{frame.streamIdentifier});
// must be always 4 bytes (https://datatracker.ietf.org/doc/html/rfc7540#section-6.9)
if (frame.length != 4) {
this.sendGoAway(frame.streamIdentifier, ErrorCode.FRAME_SIZE_ERROR, "Invalid dataframe frame size", this.lastStreamID, true);
@@ -1856,7 +1860,7 @@ pub const H2FrameParser = struct {
} else {
this.remoteWindowSize += windowSizeIncrement.uint31;
}
log("windowSizeIncrement stream {} value {}", .{ frame.streamIdentifier, windowSizeIncrement.uint31 });
log("windowSizeIncrement stream {} value {}", .{ frame.streamIdentifier, windowSizeIncrement });
return content.end;
}
// needs more data
@@ -2035,7 +2039,7 @@ pub const H2FrameParser = struct {
if (handleIncommingPayload(this, data, frame.streamIdentifier)) |content| {
const payload = content.data;
const error_code = UInt31WithReserved.fromBytes(payload[4..8]).toUInt32();
const error_code = u32FromBytes(payload[4..8]);
const chunk = this.handlers.binary_type.toJS(payload[8..], this.handlers.globalObject);
this.readBuffer.reset();
this.dispatchWith2Extra(.onGoAway, JSC.JSValue.jsNumber(error_code), JSC.JSValue.jsNumber(this.lastStreamID), chunk);
@@ -2157,7 +2161,7 @@ pub const H2FrameParser = struct {
if (handleIncommingPayload(this, data, frame.streamIdentifier)) |content| {
const payload = content.data;
const rst_code = UInt31WithReserved.fromBytes(payload).toUInt32();
const rst_code = u32FromBytes(payload);
stream.rstCode = rst_code;
this.readBuffer.reset();
stream.state = .CLOSED;
@@ -2368,17 +2372,19 @@ pub const H2FrameParser = struct {
this.dispatch(.onLocalSettings, this.localSettings.toJS(this.handlers.globalObject));
} else {
log("empty settings has remoteSettings? {}", .{this.remoteSettings != null});
if (this.remoteSettings == null) {
// ok empty settings so default settings
const remoteSettings: FullSettingsPayload = .{};
this.remoteSettings = remoteSettings;
defer this.incrementWindowSizeIfNeeded();
if (remoteSettings.initialWindowSize >= this.remoteUsedWindowSize) {
if (remoteSettings.initialWindowSize >= this.remoteWindowSize) {
defer _ = this.flushStreamQueue();
this.remoteWindowSize = remoteSettings.initialWindowSize;
var it = this.streams.valueIterator();
while (it.next()) |stream| {
if (remoteSettings.initialWindowSize >= stream.remoteUsedWindowSize) {
if (remoteSettings.initialWindowSize >= stream.remoteWindowSize) {
stream.remoteWindowSize = remoteSettings.initialWindowSize;
}
}
@@ -2404,12 +2410,12 @@ pub const H2FrameParser = struct {
this.remoteSettings = remoteSettings;
defer this.incrementWindowSizeIfNeeded();
log("remoteSettings.initialWindowSize: {} {} {}", .{ remoteSettings.initialWindowSize, this.remoteUsedWindowSize, this.remoteWindowSize });
if (remoteSettings.initialWindowSize >= this.remoteUsedWindowSize) {
if (remoteSettings.initialWindowSize >= this.remoteWindowSize) {
defer _ = this.flushStreamQueue();
this.remoteWindowSize = remoteSettings.initialWindowSize;
var it = this.streams.valueIterator();
while (it.next()) |stream| {
if (remoteSettings.initialWindowSize >= stream.remoteUsedWindowSize) {
if (remoteSettings.initialWindowSize >= stream.remoteWindowSize) {
stream.remoteWindowSize = remoteSettings.initialWindowSize;
}
}

View File

@@ -0,0 +1,85 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const http2 = require('http2');
const net = require('net');
const { Worker, parentPort } = require('worker_threads');
// Verify that creating a number of invalid HTTP/2 streams will eventually
// result in the peer closing the session.
// This test uses separate threads for client and server to avoid
// the two event loops intermixing, as we are writing in a busy loop here.
if (process.env.HAS_STARTED_WORKER) {
const server = http2.createServer({ maxSessionInvalidFrames: 100 });
server.on('stream', (stream) => {
stream.respond({
'content-type': 'text/plain',
':status': 200
});
stream.end('Hello, world!\n');
});
server.listen(0, () => parentPort.postMessage(server.address().port));
return;
}
process.env.HAS_STARTED_WORKER = 1;
const worker = new Worker(__filename).on('message', common.mustCall((port) => {
const h2header = Buffer.alloc(9);
const conn = net.connect({ port, allowHalfOpen: true });
conn.write('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n');
h2header[3] = 4; // Send a settings frame.
conn.write(Buffer.from(h2header));
let inbuf = Buffer.alloc(0);
let state = 'settingsHeader';
let settingsFrameLength;
conn.on('data', (chunk) => {
inbuf = Buffer.concat([inbuf, chunk]);
switch (state) {
case 'settingsHeader':
if (inbuf.length < 9) return;
settingsFrameLength = inbuf.readIntBE(0, 3);
inbuf = inbuf.slice(9);
state = 'readingSettings';
// Fallthrough
case 'readingSettings':
if (inbuf.length < settingsFrameLength) return;
inbuf = inbuf.slice(settingsFrameLength);
h2header[3] = 4; // Send a settings ACK.
h2header[4] = 1;
conn.write(Buffer.from(h2header));
state = 'ignoreInput';
writeRequests();
}
});
let gotError = false;
let streamId = 1;
function writeRequests() {
for (let i = 1; i < 10 && !gotError; i++) {
h2header[3] = 1; // HEADERS
h2header[4] = 0x5; // END_HEADERS|END_STREAM
h2header.writeIntBE(1, 0, 3); // Length: 1
h2header.writeIntBE(streamId, 5, 4); // Stream ID
streamId += 2;
// 0x88 = :status: 200
if (!conn.write(Buffer.concat([h2header, Buffer.from([0x88])]))) {
break;
}
}
if (!gotError)
setImmediate(writeRequests);
}
conn.once('error', common.mustCall(() => {
gotError = true;
worker.terminate();
conn.destroy();
}));
}));