diff --git a/Cargo.lock b/Cargo.lock index 35679f1e..3580ff96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -388,7 +388,7 @@ dependencies = [ [[package]] name = "quic" -version = "1.3.10" +version = "1.3.12" dependencies = [ "boring", "napi", diff --git a/Cargo.toml b/Cargo.toml index f288d5f2..c3895ce1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quic" -version = "1.3.10" +version = "1.3.12" authors = ["Roger Qiu "] license-file = "LICENSE" edition = "2021" diff --git a/package-lock.json b/package-lock.json index 052ba4bb..76f2f859 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@matrixai/quic", - "version": "1.3.10", + "version": "1.3.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@matrixai/quic", - "version": "1.3.10", + "version": "1.3.12", "license": "Apache-2.0", "dependencies": { "@matrixai/async-cancellable": "^1.1.1", @@ -57,11 +57,11 @@ "typescript": "^5.1.6" }, "optionalDependencies": { - "@matrixai/quic-darwin-arm64": "1.3.10", - "@matrixai/quic-darwin-universal": "1.3.10", - "@matrixai/quic-darwin-x64": "1.3.10", - "@matrixai/quic-linux-x64": "1.3.10", - "@matrixai/quic-win32-x64": "1.3.10" + "@matrixai/quic-darwin-arm64": "1.3.12", + "@matrixai/quic-darwin-universal": "1.3.12", + "@matrixai/quic-darwin-x64": "1.3.12", + "@matrixai/quic-linux-x64": "1.3.12", + "@matrixai/quic-win32-x64": "1.3.12" } }, "node_modules/@aashutoshrathi/word-wrap": { diff --git a/package.json b/package.json index 17273598..e12210af 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@matrixai/quic", - "version": "1.3.10", + "version": "1.3.12", "author": "Matrix AI", "contributors": [ { @@ -48,11 +48,11 @@ "ip-num": "^1.5.0" }, "optionalDependencies": { - "@matrixai/quic-darwin-arm64": "1.3.10", - "@matrixai/quic-darwin-universal": "1.3.10", - "@matrixai/quic-darwin-x64": "1.3.10", - "@matrixai/quic-linux-x64": "1.3.10", - "@matrixai/quic-win32-x64": "1.3.10" + "@matrixai/quic-darwin-arm64": "1.3.12", + "@matrixai/quic-darwin-universal": "1.3.12", + "@matrixai/quic-darwin-x64": "1.3.12", + "@matrixai/quic-linux-x64": "1.3.12", + "@matrixai/quic-win32-x64": "1.3.12" }, "devDependencies": { "@fast-check/jest": "^1.1.0", diff --git a/src/QUICConnection.ts b/src/QUICConnection.ts index 7e1008fe..9136c5ce 100644 --- a/src/QUICConnection.ts +++ b/src/QUICConnection.ts @@ -104,6 +104,26 @@ class QUICConnection { */ protected streamIdServerUni: StreamId = 0b11 as StreamId; + /** + * Tracks the highest StreamId that has a created QUICStream for clientBidi + */ + protected streamIdUsedClientBidi = -1 as StreamId; + + /** + * Tracks the highest StreamId that has a created QUICStream for serverBidi + */ + protected streamIdUsedServerBidi = -1 as StreamId; + + /** + * Tracks the highest StreamId that has a created QUICStream for clientUni + */ + protected streamIdUsedClientUni = -1 as StreamId; + + /** + * Tracks the highest StreamId that has a created QUICStream for clientUni + */ + protected streamIdUsedServerUni = -1 as StreamId; + /** * Quiche connection timer. This performs time delayed state transitions. */ @@ -967,6 +987,30 @@ class QUICConnection { } } + protected isStreamUsed(streamId: StreamId): boolean { + const type = 0b11 & streamId; + switch (type) { + case 0b00: + if (streamId <= this.streamIdUsedClientBidi) return true; + this.streamIdUsedClientBidi = streamId; + return false; + case 0b01: + if (streamId <= this.streamIdUsedServerBidi) return true; + this.streamIdUsedServerBidi = streamId; + return false; + case 0b10: + if (streamId <= this.streamIdUsedClientUni) return true; + this.streamIdUsedClientUni = streamId; + return false; + case 0b11: + if (streamId <= this.streamIdUsedServerUni) return true; + this.streamIdUsedServerUni = streamId; + return false; + default: + utils.never('got an unexpected ID type'); + } + } + protected processStreams() { for (const streamId of this.conn.readable() as Iterable) { let quicStream = this.streamMap.get(streamId); @@ -985,7 +1029,9 @@ class QUICConnection { ); continue; } - + if (this.isStreamUsed(streamId)) { + utils.never('We should never repeat streamIds when creating streams'); + } quicStream = QUICStream.createQUICStream({ initiated: 'peer', streamId, @@ -1029,47 +1075,40 @@ class QUICConnection { ); continue; } - try { - this.conn.streamSend(streamId, Buffer.alloc(0), false); - utils.never( - 'We never expect the stream to be writable if it was created during the writable iterator', - ); - } catch (e) { - // If we got `FinalSize` during the writable iterator then we cleaned up an errant stream - if (e.message === 'FinalSize') continue; - if (utils.isStreamStopped(e) !== false) { - // In this case it was a stream that was created but errored out. We want to create a new stream for this one case. - quicStream = QUICStream.createQUICStream({ - initiated: 'peer', - streamId, - config: this.config, - connection: this, - codeToReason: this.codeToReason, - reasonToCode: this.reasonToCode, - logger: this.logger.getChild(`${QUICStream.name} ${streamId}`), - }); - this.streamMap.set(quicStream.streamId, quicStream); - quicStream.addEventListener( - events.EventQUICStreamSend.name, - this.handleEventQUICStreamSend, - ); - quicStream.addEventListener( - events.EventQUICStreamDestroyed.name, - this.handleEventQUICStreamDestroyed, - { once: true }, - ); - quicStream.addEventListener( - EventAll.name, - this.handleEventQUICStream, - ); - this.dispatchEvent( - new events.EventQUICConnectionStream({ detail: quicStream }), - ); - quicStream.write(); - continue; + if (this.isStreamUsed(streamId)) { + try { + this.conn.streamSend(streamId, new Uint8Array(), false); + } catch (e) { + // Both `StreamStopped()` and `FinalSize` errors means that the stream has ended and we cleaned up state + if (utils.isStreamStopped(e) !== false) continue; + if (e.message === 'FinalSize') continue; + throw e; } - utils.never(`Expected to throw "FinalSize", got ${e.message}`); + utils.never('We never expect a duplicate stream to be readable'); } + quicStream = QUICStream.createQUICStream({ + initiated: 'peer', + streamId, + config: this.config, + connection: this, + codeToReason: this.codeToReason, + reasonToCode: this.reasonToCode, + logger: this.logger.getChild(`${QUICStream.name} ${streamId}`), + }); + this.streamMap.set(quicStream.streamId, quicStream); + quicStream.addEventListener( + events.EventQUICStreamSend.name, + this.handleEventQUICStreamSend, + ); + quicStream.addEventListener( + events.EventQUICStreamDestroyed.name, + this.handleEventQUICStreamDestroyed, + { once: true }, + ); + quicStream.addEventListener(EventAll.name, this.handleEventQUICStream); + this.dispatchEvent( + new events.EventQUICConnectionStream({ detail: quicStream }), + ); } quicStream.write(); } @@ -1178,6 +1217,9 @@ class QUICConnection { } else if (this.type === 'server' && type === 'uni') { streamId = this.streamIdServerUni; } + if (this.isStreamUsed(streamId!)) { + utils.never('We should never repeat streamIds when creating streams'); + } const quicStream = QUICStream.createQUICStream({ initiated: 'local', streamId: streamId!, diff --git a/tests/QUICStream.test.ts b/tests/QUICStream.test.ts index 313a8bbf..1c3f151e 100644 --- a/tests/QUICStream.test.ts +++ b/tests/QUICStream.test.ts @@ -11,7 +11,7 @@ import * as testsUtils from './utils'; import { generateTLSConfig, sleep } from './utils'; describe(QUICStream.name, () => { - const logger = new Logger(`${QUICStream.name} Test`, LogLevel.INFO, [ + const logger = new Logger(`${QUICStream.name} Test`, LogLevel.WARN, [ new StreamHandler( formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, ),