diff --git a/lib/internal/quic/quic.js b/lib/internal/quic/quic.js index d163311dc182..b870dce35f70 100644 --- a/lib/internal/quic/quic.js +++ b/lib/internal/quic/quic.js @@ -1243,7 +1243,7 @@ function invokeOnerror(fn, error) { } function validateBody(body) { - if (body === undefined) return body; + if (body === undefined || body === null) return body; // ArrayBuffers, SharedArrayBuffers, and ArrayBufferViews are passed // through to the C++ layer which copies the bytes into its own // BackingStore. Callers can therefore safely reuse or mutate their @@ -1280,6 +1280,7 @@ function validateBody(body) { 'ArrayBufferView', 'Blob', 'FileHandle', + 'null', ], body); } diff --git a/src/quic/streams.cc b/src/quic/streams.cc index 1354ad54c47f..af4e5f7591ca 100644 --- a/src/quic/streams.cc +++ b/src/quic/streams.cc @@ -290,11 +290,14 @@ std::unique_ptr CreateEntryFromBuffer( Maybe> Stream::GetDataQueueFromSource( Environment* env, Local value) { - DCHECK_IMPLIES(!value->IsUndefined(), value->IsObject()); + DCHECK(value->IsUndefined() || value->IsNull() || value->IsObject()); std::vector> entries; if (value->IsUndefined()) { // Return an empty DataQueue. return Just(std::shared_ptr()); + } else if (value->IsNull()) { + // Return an empty, completed data source. + return Just(DataQueue::CreateIdempotent(std::move(entries))); } else if (value->IsArrayBuffer()) { auto buffer = value.As(); auto length = buffer->ByteLength(); diff --git a/test/parallel/test-quic-stream-zero-length.mjs b/test/parallel/test-quic-stream-zero-length.mjs index 1c745ffc264b..32f7bc524404 100644 --- a/test/parallel/test-quic-stream-zero-length.mjs +++ b/test/parallel/test-quic-stream-zero-length.mjs @@ -15,27 +15,57 @@ if (!hasQuic) { const { listen, connect } = await import('../common/quic.mjs'); const { bytes } = await import('stream/iter'); -const serverDone = Promise.withResolvers(); - -const serverEndpoint = await listen(mustCall((serverSession) => { - serverSession.onstream = mustCall(async (stream) => { - const received = await bytes(stream); - assert.strictEqual(received.byteLength, 0); - stream.writer.endSync(); - await stream.closed; - serverSession.close(); - serverDone.resolve(); - }); -})); - -const clientSession = await connect(serverEndpoint.address); -await clientSession.opened; - -const stream = await clientSession.createBidirectionalStream(); -stream.setBody(null); - -await dump(stream); - -await Promise.all([stream.closed, serverDone.promise]); -await clientSession.close(); -await serverEndpoint.close(); +{ + const serverDone = Promise.withResolvers(); + + const serverEndpoint = await listen(mustCall((serverSession) => { + serverSession.onstream = mustCall(async (stream) => { + const received = await bytes(stream); + assert.strictEqual(received.byteLength, 0); + stream.writer.endSync(); + await stream.closed; + serverSession.close(); + serverDone.resolve(); + }); + })); + + const clientSession = await connect(serverEndpoint.address); + await clientSession.opened; + + const stream = await clientSession.createBidirectionalStream(); + stream.setBody(null); + + await dump(stream); + + await Promise.all([stream.closed, serverDone.promise]); + await clientSession.close(); + await serverEndpoint.close(); +} + +// Stream via createBidirectionalStream({ body: null }). +{ + const serverDone = Promise.withResolvers(); + + const serverEndpoint = await listen(mustCall((serverSession) => { + serverSession.onstream = mustCall(async (stream) => { + const received = await bytes(stream); + assert.strictEqual(received.byteLength, 0); + stream.writer.endSync(); + await stream.closed; + serverSession.close(); + serverDone.resolve(); + }); + })); + + const clientSession = await connect(serverEndpoint.address); + await clientSession.opened; + + const stream = + await clientSession.createBidirectionalStream({ body: null }); + + await dump(stream); + + await Promise.all([stream.closed, serverDone.promise]); + await clientSession.close(); + await serverEndpoint.close(); +}