├── .gitignore ├── W3CTRMANIFEST ├── w3c.json ├── examples ├── fake-quic-transport-test.html ├── quic-ws-test.html ├── quic-ws-test.js ├── fake-quic-transport-test.js ├── quic-ws.js └── fake-quic-transport.js ├── LICENSE.md ├── CODE_OF_CONDUCT.md ├── Makefile ├── .travis.yml ├── CONTRIBUTING.md ├── README.md ├── includes ├── style.css ├── respec-config-webtransport.js └── respec-config.js ├── index.html └── cs.html /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .DS_Store 3 | build 4 | support 5 | webrtc-respec-ci 6 | -------------------------------------------------------------------------------- /W3CTRMANIFEST: -------------------------------------------------------------------------------- 1 | index.html?specStatus=WD;shortName=webrtc-quic respec 2 | images/quicstream-state-revised.svg 3 | includes/style.css 4 | -------------------------------------------------------------------------------- /w3c.json: -------------------------------------------------------------------------------- 1 | { 2 | "group": 70061 3 | , "contacts": ["dontcallmedom", "caribouW3"] 4 | , "repo-type": "cg-report" 5 | } 6 | 7 | -------------------------------------------------------------------------------- /examples/fake-quic-transport-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Check the debug console :). 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | All documents in this Repository are licensed by contributors 2 | under the 3 | [W3C Document License](https://www.w3.org/Consortium/Legal/copyright-documents). 4 | 5 | -------------------------------------------------------------------------------- /examples/quic-ws-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Check the debug console :). 5 | 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | All documentation, code and communication under this repository are covered by the [W3C Code of Ethics and Professional Conduct](https://www.w3.org/Consortium/cepc/). 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include webrtc-respec-ci/Makefile 2 | 3 | # Import the respec CI Makefile 4 | webrtc-respec-ci/Makefile: 5 | git clone --depth 5 https://github.com/w3c/webrtc-respec-ci $(dir $@) 6 | 7 | update:: 8 | git -C webrtc-respec-ci pull 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | dist: trusty 4 | 5 | branches: 6 | only: 7 | - /.*/ 8 | 9 | sudo: false 10 | 11 | addons: 12 | apt: 13 | packages: 14 | - libwww-perl 15 | - libcss-dom-perl 16 | chrome: stable 17 | 18 | cache: 19 | directories: 20 | - node_modules # NPM packages 21 | 22 | before_install: 23 | - nvm install lts/* 24 | 25 | install: 26 | - make travissetup 27 | 28 | script: 29 | - make check 30 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Web Real-Time Communications Working Group 2 | 3 | Contributions to this repository are intended to become part of Recommendation-track documents governed by the 4 | [W3C Patent Policy](https://www.w3.org/Consortium/Patent-Policy-20040205/) and 5 | [Document License](https://www.w3.org/Consortium/Legal/copyright-documents). To make substantive contributions to specifications, you must either participate 6 | in the relevant W3C Working Group or make a non-member patent licensing commitment. 7 | 8 | If you are not the sole contributor to a contribution (pull request), please identify all 9 | contributors in the pull request comment. 10 | 11 | To add a contributor (other than yourself, that's automatic), mark them one per line as follows: 12 | 13 | ``` 14 | +@github_username 15 | ``` 16 | 17 | If you added a contributor by mistake, you can remove them in a comment with: 18 | 19 | ``` 20 | -@github_username 21 | ``` 22 | 23 | If you are making a pull request on behalf of someone else but you had no part in designing the 24 | feature, you can remove yourself with the above syntax. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## P2P-WebTransport 2 | 3 | This document defines a set of ECMAScript APIs in WebIDL to allow data to be sent 4 | and received from another browser or device implementing the WebTransport 5 | protocol. This specification is being developed in conjunction with a protocol 6 | specification developed by the IETF WEBTRANS Working Group. 7 | 8 | ### Published Versions 9 | 10 | The editors' draft is the tip-of-tree of this document, it may contain work in 11 | progress changes and other inconsistencies, including an incomplete change log. 12 | 13 | * [Latest P2P WebTransport API draft at github](https://w3c.github.io/p2p-webtransport/) 14 | * [Original RTCQuicTransport API](https://webrtc.internaut.com/ortc/#quic-transport*) 15 | * [Latest WebTransport API draft at github](https://wicg.github.io/web-transport/) 16 | * [Archived previous version of the WebTransport API draft at github](https://w3c.github.io/p2p-webtransport/webtransport.html) 17 | * [Archived previous version of the Client/Server API draft at github](https://w3c.github.io/p2p-webtransport/cs.html) 18 | * [Archived previous version of P2P API draft at github](https://w3c.github.io/p2p-webtransport/index-old.html) 19 | 20 | ### Useful Links 21 | 22 | The content of this document is discussed at the 23 | [public-ortc](https://lists.w3.org/Archives/Public/public-ortc/) 24 | mailing list. 25 | 26 | [WEBTRANS IETF Working Group](https://tools.ietf.org/wg/webtrans/) 27 | 28 | [Contribution Guidelines](CONTRIBUTING.md) 29 | -------------------------------------------------------------------------------- /examples/quic-ws-test.js: -------------------------------------------------------------------------------- 1 | async function testWebSocket(ws) { 2 | console.log(ws); 3 | ws.onopen = () => { 4 | console.log("onopen"); 5 | }; 6 | ws.onclose = (evt) => { 7 | console.log("onclose: "); 8 | console.log(evt); 9 | }; 10 | ws.onerror = (evt) => { 11 | console.log("onerror: " + evt.message); 12 | }; 13 | ws.onmessage = (evt) => { 14 | console.log("Received message: "); 15 | console.log(evt.data); 16 | }; 17 | 18 | console.log("ws.readyState: " + ws.readyState); 19 | console.log("ws.binaryType: " + ws.binaryType); 20 | 21 | const msg0 = Uint8Array.from([0, 1, 2, 3]); 22 | ws.send(msg0); 23 | 24 | const msg1 = copyToArrayBuffer([1, 2, 3, 4]); 25 | ws.send(msg1); 26 | 27 | const msg2 = copyToBlob(repeat([3, 4, 5, 6], 100)); 28 | ws.send(msg2); 29 | 30 | await sleep(10); // Let previous sends happen before we change binary type. 31 | ws.binaryType = "string"; 32 | const msg3 = "Hello, \u2603."; 33 | ws.send(msg3); 34 | 35 | await sleep(10); // Let previous sends happen before we close. 36 | ws.close(500, "We failed"); 37 | console.log("ws.readyState: " + ws.readyState); 38 | } 39 | 40 | 41 | function* repeat(iterable, times) { 42 | for (let i = 0; i < times; i++) { 43 | yield* iterable; 44 | } 45 | } 46 | 47 | function sleep (time) { 48 | return new Promise((resolve) => setTimeout(resolve, time)); 49 | } 50 | 51 | async function test() { 52 | await testWebSocket(new QuicUnreliableDatagramWebSocket("datagram.us", 12345)); 53 | await testWebSocket(new QuicUnreliableStreamWebSocket("stream.us", 54321)); 54 | } 55 | 56 | test(); 57 | -------------------------------------------------------------------------------- /includes/style.css: -------------------------------------------------------------------------------- 1 | @media screen { 2 | html { background: #eeeeee; } 3 | body { margin-bottom: 30%; border-bottom: thin solid #3c790a; } 4 | } 5 | 6 | pre { margin-left: 2em; white-space: pre-wrap; } 7 | dt, dfn { font-weight: bold; font-style: normal; } 8 | i, em, dt dfn { font-style: italic; } 9 | pre, code { font-size: inherit; font-family: monospace, Droid Sans Fallback, sans-serif; font-variant: normal; } 10 | pre strong { color: black; font: inherit; font-weight: bold; background: yellow; } 11 | pre em { font-weight: bolder; font-style: normal; } 12 | @media screen { code { color: orange; } } 13 | var sub { vertical-align: bottom; font-size: smaller; position: relative; top: 0.1em; } 14 | table { border-collapse: collapse; border-style: hidden hidden none hidden; } 15 | table thead, table tbody { border-bottom: solid; } 16 | table tbody th { text-align: left; } 17 | table tbody th:first-child { border-left: solid; } 18 | table td, table th { border-left: solid; border-right: solid; border-bottom: solid thin; vertical-align: top; padding: 0.2em; } 19 | 20 | ins { background: green; color: white; /* color: green; border: solid thin lime; padding: 0.3em; line-height: 1.6em; */ text-decoration: none; } 21 | del { background: maroon; color: white; /* color: maroon; border: solid thin red; padding: 0.3em; line-height: 1.6em; */ text-decoration: line-through; } 22 | body ins, body del { display: block; } 23 | body * ins, body * del { display: inline; } 24 | 25 | 26 | li > span:not([title=""]):not([class="XXX"]):not([class="impl"]):not([class="note"]) { border-bottom: solid #99CC99; } 27 | 28 | .note { color: green; background: transparent; font-family: sans-serif, Droid Sans Fallback; } 29 | .warning { color: red; background: transparent; } 30 | .note, .warning { font-weight: bolder; font-style: italic; } 31 | .note em, .warning em, .note i, .warning i { font-style: normal; } 32 | p.note, div.note { padding: 0.5em 2em; } 33 | span.note { padding: 0 2em; } 34 | .note p:first-child, .warning p:first-child { margin-top: 0; } 35 | .note p:last-child, .warning p:last-child { margin-bottom: 0; } 36 | .warning:before { font-style: normal; } 37 | p.note:before { content: 'Note: '; } 38 | p.warning:before { content: '\26A0 Warning! '; } 39 | 40 | .bookkeeping:before { display: block; content: 'Bookkeeping details'; font-weight: bolder; font-style: italic; } 41 | .bookkeeping { font-size: 0.8em; margin: 2em 0; } 42 | .bookkeeping p { margin: 0.5em 2em; display: list-item; list-style: square; } 43 | .bookkeeping dt { margin: 0.5em 2em 0; } 44 | .bookkeeping dd { margin: 0 3em 0.5em; } 45 | 46 | .critical { margin: 1em; border: double thick red; padding: 1em; background: #FFFFCC; } 47 | .critical > :first-child { margin-top: 0; } 48 | 49 | .example { display: block; color: #222222; background: #FCFCFC; border-left: double; margin-left: 2em; padding-left: 1em; } 50 | td > .example:only-child { margin: 0 0 0 0.1em; } 51 | .rfc2119 { font-variant: small-caps;} -------------------------------------------------------------------------------- /includes/respec-config-webtransport.js: -------------------------------------------------------------------------------- 1 | var respecConfig = { 2 | specStatus: "CG-DRAFT", 3 | // if there a publicly available Editor's Draft, this is the link 4 | edDraftURI: "https://w3c.github.io/webrtc-quic/webtransport.html", 5 | shortName: "webtransport", 6 | editors: [ 7 | { name: "Peter Thatcher", company: "Google", w3cid: "68236" }, 8 | { name: "Bernard Aboba", company: "Microsoft Corporation", w3cid: "65611" }, 9 | { name: "Robin Raymond", company: "Optical Tone Ltd." } 10 | ], 11 | authors: [ 12 | ], 13 | wg: "Object-RTC API Community Group", 14 | wgURI: "https://www.w3.org/community/ortc/", 15 | wgPublicList: "public-ortc", 16 | wgPatentURI: "https://www.w3.org/2004/01/pp-impl/47318/status", 17 | issueBase: "https://github.com/w3c/webrtc-quic/issues", 18 | otherLinks: [ 19 | { 20 | key: "Participate", 21 | data: [ 22 | { 23 | value: "Mailing list", 24 | href: "https://lists.w3.org/Archives/Public/public-webrtc/" 25 | }, 26 | { 27 | value: "Browse open issues", 28 | href: "https://github.com/w3c/webrtc-quic/issues" 29 | }, 30 | { 31 | value: "IETF QUIC Working Group", 32 | href: "https://tools.ietf.org/wg/quic/" 33 | } 34 | ] 35 | } 36 | ], 37 | localBiblio: { 38 | "QUIC-DATAGRAM": { 39 | "title": "An Unreliable Datagram Extension to QUIC", 40 | "href": "https://tools.ietf.org/html/draft-pauly-quic-datagram", 41 | "authors": [ 42 | "T. Pauly", 43 | "E. Kinnear", 44 | "D. Schinazi" 45 | ], 46 | "status": "06 February 2019. Internet draft (work in progress)", 47 | "publisher": "IETF" 48 | }, 49 | "QUIC-TRANSPORT": { 50 | "title": "QUIC: A UDP-Based Multiplexed and Secure Transport", 51 | "href": "https://tools.ietf.org/html/draft-ietf-quic-transport", 52 | "authors": [ 53 | "J. Iyengar", 54 | "M. Thomson" 55 | ], 56 | "status": "11 March 2019. Internet draft (work in progress)", 57 | "publisher": "IETF" 58 | }, 59 | "WEB-TRANSPORT-OVERVIEW": { 60 | "title": "WebTransport Protocol Framework", 61 | "href": "https://tools.ietf.org/html/draft-vvv-webtransport-overview", 62 | "authors": [ 63 | "V. Vasiliev" 64 | ], 65 | "status": "03 May 2019. Internet draft (work in progress)", 66 | "publisher": "IETF" 67 | }, 68 | "WEB-TRANSPORT-QUIC": { 69 | "title": "WebTransport over QUIC", 70 | "href": "https://tools.ietf.org/html/draft-vvv-webtransport-quic", 71 | "authors": [ 72 | "V. Vasiliev" 73 | ], 74 | "status": "03 May 2019. Internet draft (work in progress)", 75 | "publisher": "IETF" 76 | }, 77 | "WEB-TRANSPORT-HTTP3": { 78 | "title": "WebTransport over HTTP/3", 79 | "href": "https://tools.ietf.org/html/draft-vvv-webtransport-http3", 80 | "authors": [ 81 | "V. Vasiliev" 82 | ], 83 | "status": "03 May 2019. Internet draft (work in progress)", 84 | "publisher": "IETF" 85 | }, 86 | "TLS13": { 87 | "title": "The Transport Layer Security (TLS) Protocol Version 1.3", 88 | "href": "https://tools.ietf.org/html/rfc8446", 89 | "authors": [ 90 | "E. Rescorla" 91 | ], 92 | "status": "Internet Standards Track document", 93 | "publisher": "IETF" 94 | }, 95 | "ALPN": { 96 | "title": "Transport Layer Security (TLS) Application-Layer Protocol Negotiation Extension", 97 | "href": "https://tools.ietf.org/html/rfc7301", 98 | "authors": [ 99 | "S. Friedl", 100 | "A. Popov", 101 | "A. Langley", 102 | "E. Stephan" 103 | ], 104 | "status": "Internet Standards Track document", 105 | "publisher": "IETF" 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /examples/fake-quic-transport-test.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | const hostname = "foo.com"; 3 | const port = 1234; 4 | const quic = new QuicTransport(hostname, port); 5 | quic.onstatechange = (evt) => { 6 | console.log("quic.onstatechange: " + quic.state); 7 | }; 8 | 9 | let receiveStream = null; 10 | quic.onreceivestream = (evt) => { 11 | console.log("Received stream (as expected)."); 12 | receiveStream = evt.stream; 13 | }; 14 | 15 | // Test datagram support. 16 | let acked = await quic.sendDatagram(new Uint8Array([1, 2, 3, 4])); 17 | console.assert(acked, "Datagram is should be acked."); 18 | quic.sendDatagram(new Uint8Array([5, 6])); 19 | console.assert(quic._datagramBufferedAmount == 2, 20 | "Should have buffered 2 datagrams"); 21 | quic.readyToSendDatagram().then(() => { 22 | console.log("Ready to send datagram (as expected)"); 23 | }); 24 | let datagrams = await quic.receiveDatagrams(); 25 | console.assert(datagrams.length == 2, 26 | "Should have received 2 datagrams"); 27 | 28 | // This doesn't resolve until we send a datagram. 29 | quic.receiveDatagrams().then((datagrams) => { 30 | console.assert(datagrams.length == 1, 31 | "Should have received 1 datagram"); 32 | }); 33 | let receivedDatagramsFailed = false; 34 | try { 35 | await quic.receiveDatagrams(); 36 | } catch(e) { 37 | receivedDatagramsFailed = true; 38 | } 39 | console.assert(receivedDatagramsFailed, 40 | "receiveDatagrams() should have failed"); 41 | quic.sendDatagram(new Uint8Array([1, 2, 3])); 42 | 43 | // Test basic stream support. 44 | const sendStream = await quic.createSendStream({disableRetransmissions: true}); 45 | console.assert(sendStream.writable, 46 | "sendStream should be writable."); 47 | let writeFailed = false; 48 | try { 49 | sendStream.write({data: new Uint8Array([]), finished: true}); 50 | } catch(e) { 51 | writeFailed = true; 52 | } 53 | console.assert(writeFailed, "Write should have failed with empty params."); 54 | 55 | // Test that if we send more than can be buffered on read side 56 | // that it gets buffered to the write side. 57 | sendStream.write({data: new Uint8Array(4999)}); 58 | // One byte write side, one byte read side. 59 | sendStream.write({data: new Uint8Array(2)}); 60 | 61 | console.assert(receiveStream.readableAmount == 5000, 62 | "Receive stream should have buffered max amount."); 63 | console.assert(sendStream.writeBufferedAmount == 1, 64 | "Remaining 1 byte should be buffered write side."); 65 | 66 | // Writing again buffers more data. 67 | sendStream.write({data: new Uint8Array(5), finished: true }); 68 | console.assert(receiveStream.readableAmount == 5000, 69 | "Receive stream should have buffered max amount."); 70 | console.assert(sendStream.writeBufferedAmount = 6, 71 | "QuicSendSteram should have 6 bytes buffered."); 72 | 73 | // Reading should trigger the rest of the buffered write data to 74 | // be written over. 75 | let readBuffer = new Uint8Array(10); 76 | let readAmount = receiveStream.readInto(readBuffer).amount; 77 | console.assert(readAmount == 10, "Should read all data."); 78 | console.assert(sendStream.writeBufferedAmount == 0, 79 | "No data should be write buffered."); 80 | console.assert(sendStream.writable == false, "FIN is written"); 81 | 82 | let writingAborted = false; 83 | receiveStream.abortReading(); 84 | await sendStream.writingAborted.then(() => { 85 | writingAborted = true; 86 | }); 87 | console.assert(writingAborted, "Writing should have been aborted."); 88 | 89 | let readingAborted = false; 90 | sendStream.abortWriting(); 91 | await receiveStream.readingAborted.then(() => { 92 | readingAborted = true; 93 | }); 94 | console.assert(readingAborted, "Reading should have been aborted."); 95 | 96 | writeFailed = false; 97 | try { 98 | sendStream.write({data: new Uint8Array([5, 0, 0])}); 99 | } catch(e) { 100 | writeFailed = true; 101 | } 102 | console.assert(writeFailed, "Write should have failed."); 103 | 104 | console.assert(quic.state == "connected"); 105 | quic.stop(); 106 | 107 | let sendStreamFailed = false; 108 | try { 109 | await quic.createSendStream(); 110 | } catch(e) { 111 | sendStreamFailed = true; 112 | } 113 | console.assert(sendStreamFailed, "createSendStream() should have failed."); 114 | 115 | let sendDatagramFailed = false; 116 | try { 117 | await quic.sendDatagram(toUint8Array("foo")); 118 | } catch(e) { 119 | sendDatagramFailed = true; 120 | } 121 | console.assert(sendDatagramFailed, "sendDatagram() should have failed."); 122 | 123 | let readyToSendDatagramFailed = false; 124 | try { 125 | await quic.readyToSendDatagram(); 126 | } catch(e) { 127 | readyToSendDatagramFailed = true; 128 | } 129 | console.assert(readyToSendDatagramFailed, "readyToSendDatagram() should've failed."); 130 | 131 | console.assert(quic.state == "closed"); 132 | } 133 | 134 | test(); 135 | -------------------------------------------------------------------------------- /includes/respec-config.js: -------------------------------------------------------------------------------- 1 | var respecConfig = { 2 | specStatus: "CG-DRAFT", 3 | // if there a publicly available Editor's Draft, this is the link 4 | edDraftURI: "https://w3c.github.io/p2p-webtransport/", 5 | shortName: "p2p-webtransport", 6 | editors: [ 7 | { name: "Peter Thatcher", company: "Microsoft Corporation", w3cid: "68236" }, 8 | { name: "Bernard Aboba", company: "Microsoft Corporation", w3cid: "65611" }, 9 | { name: "Robin Raymond", company: "Microsoft Corporation" } 10 | ], 11 | authors: [ 12 | ], 13 | group: "ortc", 14 | wgPublicList: "public-ortc", 15 | github: { 16 | repoURL: "https://github.com/w3c/p2p-webtransport", 17 | branch: "master" 18 | }, 19 | otherLinks: [ 20 | { 21 | key: "Participate", 22 | data: [ 23 | { 24 | value: "Mailing list", 25 | href: "https://lists.w3.org/Archives/Public/public-ortc/" 26 | }, 27 | { 28 | value: "IETF AVTCORE Working Group", 29 | href: "https://datatracker.ietf.org/wg/avtcore/" 30 | } 31 | ] 32 | } 33 | ], 34 | lint: { "no-unused-dfns": false }, 35 | localBiblio: { 36 | "IANA-STUN-6": { 37 | "title": "STUN Error Codes", 38 | "href": "https://www.iana.org/assignments/stun-parameters/stun-parameters.xhtml#stun-parameters-6", 39 | "publisher": "IANA" 40 | }, 41 | "ORTC": { 42 | "title": "Object RTC (ORTC) API for WebRTC", 43 | "href": "https://w3c.github.io/ortc/", 44 | "authors": [ 45 | "Robin Raymond", 46 | "Bernard Aboba", 47 | "Justin Uberti" 48 | ], 49 | "status": "25 January 2021 (work in progress)", 50 | "publisher": "W3C" 51 | }, 52 | "RFC9221": { 53 | "title": "An Unreliable Datagram Extension to QUIC", 54 | "href": "https://datatracker.ietf.org/doc/html/rfc9221", 55 | "authors": [ 56 | "T. Pauly", 57 | "E. Kinnear", 58 | "D. Schinazi" 59 | ], 60 | "status": "March 2022. RFC", 61 | "publisher": "IETF" 62 | }, 63 | "RFC9000": { 64 | "title": "QUIC: A UDP-Based Multiplexed and Secure Transport", 65 | "href": "https://datatracker.ietf.org/doc/html/rfc9000", 66 | "authors": [ 67 | "J. Iyengar", 68 | "M. Thomson" 69 | ], 70 | "status": "May 2021. RFC", 71 | "publisher": "IETF" 72 | }, 73 | "RFC9443": { 74 | "title": "Multiplexing Scheme Updates for QUIC", 75 | "href": "https://datatracker.ietf.org/doc/html/rfc9443", 76 | "authors": [ 77 | "B. Aboba", 78 | "G. Salgueiro", 79 | "C. Perkins" 80 | ], 81 | "status": "July 2023. RFC", 82 | "publisher": "IETF" 83 | }, 84 | "RFC7675": { 85 | "title": "Session Traversal Utilities for NAT (STUN) Usage for Consent Freshness", 86 | "href": "https://datatracker.ietf.org/doc/html/rfc7675", 87 | "authors": [ 88 | "M. Perumal", 89 | "D. Wing", 90 | "R. Ravindranath", 91 | "T. Reddy", 92 | "M. Thomson" 93 | ], 94 | "status": "October 2015. RFC", 95 | "publisher": "IETF" 96 | }, 97 | "RFC8446": { 98 | "title": "The Transport Layer Security (TLS) Protocol Version 1.3", 99 | "href": "https://datatracker.ietf.org/doc/html/rfc8446", 100 | "authors": [ 101 | "E. Rescorla" 102 | ], 103 | "status": "August 2018. RFC", 104 | "publisher": "IETF" 105 | }, 106 | "RFC8832": { 107 | "title": "WebRTC Data Channel Establishment Protocol", 108 | "href": "https://datatracker.ietf.org/doc/html/rfc8832", 109 | "authors": [ 110 | "R. Jesup", 111 | "S. Loreto", 112 | "M. Tuexen" 113 | ], 114 | "status": "January 2021. RFC", 115 | "publisher": "IETF" 116 | }, 117 | "JSEP": { 118 | "title": "Javascript Session Establishment Protocol", 119 | "href": "https://datatracker.ietf.org/doc/html/draft-uberti-rtcweb-rfc8829bis", 120 | "authors": [ 121 | "J. Uberti", 122 | "C. Jennings", 123 | "E. Rescorla" 124 | ], 125 | "status": "26 July 2023. Internet Draft (work in progress)", 126 | "publisher": "IETF" 127 | }, 128 | "RFC8826": { 129 | "title": "Security Considerations for WebRTC", 130 | "href": "https://datatracker.ietf.org/doc/html/rfc8826", 131 | "authors": [ 132 | "E. Rescorla" 133 | ], 134 | "status": "January 2021. RFC", 135 | "publisher": "IETF" 136 | }, 137 | "RFC8827": { 138 | "title": "WebRTC Security Architecture", 139 | "href": "https://datatracker.ietf.org/doc/html/rfc8827", 140 | "authors": [ 141 | "E. Rescorla" 142 | ], 143 | "status": "January 2021. RFC", 144 | "publisher": "IETF" 145 | }, 146 | "WEBRTC-STATS": { 147 | "title": "Identifiers for WebRTC's Statistics API", 148 | "href": "https://w3c.github.io/webrtc-stats/", 149 | "authors": [ 150 | "Harald Alvestrand", 151 | "Varun Singh" 152 | ], 153 | "status": "06 October 2020 (work in progress)", 154 | "publisher": "W3C" 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /examples/quic-ws.js: -------------------------------------------------------------------------------- 1 | // - Make sure MessageEvent.type is correct 2 | // - Make sure onerror is being fired correctly 3 | // - Fill in onmessage .origin, lastEventId, source, and ports? 4 | // - Set the CloseEvent.code? 5 | // - Set readyState to CLOSING = 2 at some point? 6 | // - Send/receive/negotiate extensions/protocols? 7 | 8 | class QuicWebSocketBase { 9 | constructor(hostname, port) { 10 | this._hostname = hostname; 11 | this._port = port; 12 | this._readyState = 0; 13 | this._binaryType = "uint8array"; 14 | this.onopen = null; 15 | this.onerror = null; 16 | this.onclose = null; 17 | this.onmessage = null; 18 | 19 | this._quic = new QuicTransport(hostname, port); 20 | this._error = null; 21 | this._quic.onerror = (error) => { 22 | this._handleQuicError(error); 23 | }; 24 | this._quic.onstatechange = () => { 25 | this._handleQuicStateChange(); 26 | }; 27 | } 28 | 29 | get url() { 30 | return ""; // Doesn't make sense if constructor takes hostname + port. 31 | } 32 | 33 | get readyState() { 34 | return this._readyState; 35 | } 36 | 37 | get extensions() { 38 | return ""; 39 | } 40 | 41 | get protocol() { 42 | return ""; 43 | } 44 | 45 | get binaryType() { 46 | return this._binaryType; 47 | } 48 | 49 | set binaryType(type) { 50 | if (type == "blob" || type == "arraybuffer" || type == "uint8array" || type == "string") { 51 | this._binaryType = type; 52 | return; 53 | } 54 | throw TypeError("binaryType must by blob, arraybuffer, or uint8array."); 55 | } 56 | 57 | close(code, reason) { 58 | this._quic.stop({ 59 | errorCode: code, 60 | reason: reason 61 | }); 62 | } 63 | 64 | _handleQuicMessageReceived(data) { 65 | if (!this.onmessage) { 66 | return; 67 | } 68 | this.onmessage(new MessageEvent("message", { 69 | data: fromUint8Array(data, this.binaryType), 70 | })); 71 | } 72 | 73 | _handleQuicError(error) { 74 | this._error = error; 75 | if (!this.onerror) { 76 | return; 77 | } 78 | this.onerror(error); 79 | } 80 | 81 | _handleQuicStateChange() { 82 | let state = this._quic.state; 83 | if (state == "connecting") { 84 | this._readyState = 0; 85 | return; 86 | } 87 | 88 | if (state == "connected") { 89 | this._readyState = 1; 90 | if (!this.onopen) { 91 | return; 92 | } 93 | this.onopen(); 94 | return; 95 | } 96 | 97 | // Closed or failed 98 | this._readyState = 3; 99 | if (!this.onclose) { 100 | return; 101 | } 102 | if (this._quic.state == "failed") { 103 | this.onclose(new CloseEvent({ 104 | wasClean: false 105 | })); 106 | return; 107 | } 108 | 109 | // Closed 110 | if (!this._error) { 111 | this.onclose(new CloseEvent({ 112 | wasClean: true 113 | })); 114 | return; 115 | } 116 | 117 | // Close w/ error 118 | this.onclose(new CloseEvent({ 119 | wasClean: false, 120 | reason: this._error.message 121 | })); 122 | } 123 | } 124 | 125 | class QuicUnreliableDatagramWebSocket extends QuicWebSocketBase { 126 | constructor(hostname, port) { 127 | super(hostname, port); 128 | 129 | this._quic.receiveDatagrams().then((datagrams) => { 130 | this._handleReceivedDatagrams(datagrams); 131 | }); 132 | } 133 | 134 | get bufferedAmount() { 135 | return 0; 136 | } 137 | 138 | async send(data) { 139 | data = await toUint8Array(data); 140 | if (data.length == 0) { 141 | throw new TypeError("Empty messages not supported."); 142 | } 143 | if (data.length > this._quic.maxDatagramSize) { 144 | throw new TypeError("Message too big."); 145 | } 146 | this._quic.sendDatagram(data); 147 | } 148 | 149 | _handleReceivedDatagrams(datagrams) { 150 | for (let datagram of datagrams) { 151 | this._handleQuicMessageReceived(datagram); 152 | } 153 | this._quic.receiveDatagrams().then((datagrams) => { 154 | this._handleReceivedDatagrams(datagrams); 155 | }); 156 | } 157 | } 158 | 159 | class QuicUnreliableStreamWebSocket extends QuicWebSocketBase { 160 | constructor(hostname, port) { 161 | super(hostname, port); 162 | 163 | this._recvStreams = new Set(); 164 | this._quic.onreceivestream = event => { 165 | this._readStreamAsOneMessage(event.stream); 166 | }; 167 | } 168 | 169 | get bufferedAmount() { 170 | let bufferedAmount = 0; 171 | for (let recvStream of this._recvStreams) { 172 | bufferedAmount += recvStream.readableAmount; 173 | } 174 | return bufferedAmount; 175 | } 176 | 177 | async send(data) { 178 | data = await toUint8Array(data); 179 | if (data.length == 0) { 180 | throw new TypeError("Empty messages not supported."); 181 | } 182 | let stream = await this._quic.createSendStream({ 183 | disableRetransmissions: true 184 | }); 185 | stream.write({ 186 | data: data, 187 | finished: true 188 | }); 189 | } 190 | 191 | async _readStreamAsOneMessage(stream) { 192 | this._recvStreams.add(stream); 193 | 194 | let buffer = new Uint8Array(); 195 | let bufferedSize = 0; 196 | let finished = false; 197 | // We keep appending to an array. If we wanted to be efficient 198 | // we could wait for the finish to arrive and write everything at 199 | // once in order to reduce copies. 200 | while (stream.readable) { 201 | await stream.waitForReadable(1); 202 | let readBuffer = new Uint8Array(stream.readableAmount); 203 | let read = stream.readInto(readBuffer); 204 | let concatBuffer = new Uint8Array(buffer.length + readBuffer.length); 205 | concatBuffer.set(buffer); 206 | concatBuffer.set(readBuffer, buffer.length); 207 | buffer = concatBuffer; 208 | finished = read.finished; 209 | } 210 | if (finished) { 211 | this._handleQuicMessageReceived(buffer); 212 | } 213 | 214 | this._recvStreams.delete(stream); 215 | } 216 | } 217 | 218 | // We create our own CloseEvent because the built in CloseEvent has 219 | // read only properties. 220 | class CloseEvent extends Event { 221 | constructor(params) { 222 | super(); 223 | this.reason = ""; 224 | if (params.reason) { 225 | this.reason = params.reason; 226 | } 227 | this.wasClean = false; 228 | if (params.wasClean === true) { 229 | this.wasClean = true; 230 | } 231 | } 232 | } 233 | 234 | async function toUint8Array(data) { 235 | if (data instanceof Uint8Array) { 236 | return data; 237 | } 238 | if (data instanceof ArrayBuffer) { 239 | return new Uint8Array(data); 240 | } 241 | if (data instanceof Blob) { 242 | return new Uint8Array(await readBlobAsArrayBuffer(data)); 243 | } 244 | if (typeof data == "string") { 245 | return utf8encode(data); 246 | } 247 | return Uint8Array.from(data); 248 | } 249 | 250 | async function readBlobAsArrayBuffer(blob) { 251 | const reader = new FileReader(); 252 | const loadend = new Promise((resolve, reject) => { 253 | reader.onloadend = resolve; 254 | }); 255 | reader.readAsArrayBuffer(blob); 256 | await loadend; 257 | return reader.result; 258 | } 259 | 260 | function fromUint8Array(array, binaryType) { 261 | if (binaryType == "uint8array") { 262 | return array; 263 | } 264 | if (binaryType == "blob") { 265 | if (array.buffer.byteLength > array.length) { 266 | return copyToBlob(array); 267 | } 268 | return new Blob([array.buffer]); 269 | } 270 | if (binaryType == "arraybuffer") { 271 | if (array.buffer.byteLength > array.length) { 272 | return copyToArrayBuffer(array); 273 | } 274 | return array.buffer; 275 | } 276 | if (binaryType == "string") { 277 | try { 278 | return utf8decode(array); 279 | } catch(e) { 280 | return ""; 281 | } 282 | } 283 | return array; 284 | } 285 | 286 | function copyToBlob(values) { 287 | return new Blob([copyToArrayBuffer(values)]); 288 | } 289 | 290 | function copyToArrayBuffer(values) { 291 | const array = new Uint8Array(values); 292 | return array.buffer; 293 | } 294 | 295 | function utf8encode(str) { 296 | return Uint8Array.from(Array.from(unescape(encodeURIComponent(str))).map(c => c.codePointAt(0))); 297 | } 298 | 299 | function utf8decode(bytes) { 300 | return decodeURIComponent(escape(Array.from(bytes).map(cp => String.fromCodePoint(cp)).join(""))); 301 | } 302 | -------------------------------------------------------------------------------- /examples/fake-quic-transport.js: -------------------------------------------------------------------------------- 1 | // TODO: 2 | // - add QuicTransport onbidirectionalstream, createBidrectionalStream() 3 | // 4 | // A QuicTransport that is connected to itself, meaning that datagrams sent 5 | // are received on the same transport, and QuicSendStreams created are connected 6 | // to a QuicReceiveStrem on the same transport. 7 | class QuicTransport { 8 | constructor(hostname, port) { 9 | this._hostname = hostname; 10 | this._port = port; 11 | this._state = new State("new"); 12 | this._onError = new Event(); 13 | this._onReceiveStream = new Event(); 14 | 15 | this._maxReceivedDatagramAmount = 1000; // Make something up. 16 | this._resolveReceivedDatagrams = null; 17 | this._unresolvedDatagramPromise = false; 18 | this._receivedDatagrams = []; 19 | 20 | // See https://tools.ietf.org/html/draft-ietf-quic-transport-17#section-2.1 21 | // Including 2 bit suffix, client send streams get 2, 6, 10, ... 22 | this._lastSendStreamId = 10; 23 | this._stopInfo = null; 24 | 25 | // Do later to allow watching state change 26 | later(() => { 27 | this._state.change("connecting"); 28 | this._state.change("connected"); 29 | }); 30 | } 31 | 32 | get state() { 33 | return this._state.current; 34 | } 35 | 36 | get maxDatagramSize() { 37 | return 1280; 38 | } 39 | 40 | stop(info) { 41 | this._stopInfo = info; 42 | if (info) { 43 | this._onError.fire({ 44 | message: info.reason 45 | }); 46 | } 47 | this._state.change("closed"); 48 | } 49 | 50 | get _closedOrFailed() { 51 | return this._state.current == "closed" || this._state.current == "failed"; 52 | } 53 | 54 | allocateSendStreamId() { 55 | // See https://tools.ietf.org/html/draft-ietf-quic-transport-17#section-2.1 56 | this._lastSendStreamId += 4; 57 | return this._lastSendStreamId; 58 | } 59 | 60 | async createSendStream(params) { 61 | if (this._closedOrFailed) { 62 | throw new InvalidStateError(); 63 | } 64 | await this._state.until(state => state == "connected"); 65 | 66 | const sendStreamId = this.allocateSendStreamId(); 67 | // See https://tools.ietf.org/html/draft-ietf-quic-transport-17#section-2.1 68 | const recvStreamId = sendStreamId + 1; 69 | const maxBufferedAmount = 5000; // Just make something up 70 | const recvStream = new QuicReceiveStream(this, recvStreamId, maxBufferedAmount); 71 | const sendStream = new QuicSendStream(this, sendStreamId, params, recvStream, maxBufferedAmount); 72 | return sendStream; 73 | } 74 | 75 | readyToSendDatagram() { 76 | if (this._closedOrFailed) { 77 | throw new InvalidStateError(); 78 | } 79 | return new Promise((resolve, reject) => { 80 | // Let's pretend that congestion control never blocks the transport ;). 81 | resolve(); 82 | }); 83 | } 84 | 85 | sendDatagram(data) { 86 | if (this._closedOrFailed) { 87 | throw new InvalidStateError(); 88 | } 89 | // Drop datagrams once the max is hit. 90 | if (this._receivedDatagrams.length == this._maxReceivedDatagramAmount) { 91 | return new Promise((resolve, reject) => { 92 | resolve(false); 93 | }); 94 | } 95 | 96 | this._receivedDatagrams.push(data); 97 | // Resolve a receivedDatagrams() promise if one has been returned. 98 | if (this._unresolvedDatagramPromise) { 99 | this._resolveReceivedDatagrams(this._receivedDatagrams.slice()); 100 | this._receivedDatagrams = []; 101 | this._resolveReceivedDatagrams = null; 102 | this._unresolvedDatagramPromise = false; 103 | } 104 | 105 | // Return a promise resolved with true, because we know that the datagram 106 | // has been "acked". 107 | return new Promise((resolve, reject) => { 108 | resolve(true); 109 | }); 110 | } 111 | 112 | receiveDatagrams() { 113 | if (this._unresolvedDatagramPromise) { 114 | // Can't return a promise if previous one is unresolved. 115 | throw new InvalidStateError(); 116 | } 117 | 118 | if (this._receivedDatagrams.length > 0) { 119 | // Already received datagrams, go ahead and resolve the 120 | // promise immediately. 121 | return new Promise((resolve, reject) => { 122 | resolve(this._receivedDatagrams.slice()); 123 | this._receivedDatagrams = []; 124 | }); 125 | } 126 | this._unresolvedDatagramPromise = true; 127 | return new Promise((resolve, reject) => { 128 | this._resolveReceivedDatagrams = resolve; 129 | }); 130 | } 131 | 132 | get _datagramBufferedAmount() { 133 | return this._receivedDatagrams.length; 134 | } 135 | 136 | set onstatechange(handler) { 137 | this._state.onchange = (payload) => handler(); 138 | } 139 | 140 | set onerror(handler) { 141 | this._onError.handler = handler; 142 | } 143 | 144 | set onreceivestream(handler) { 145 | this._onReceiveStream.handler = handler; 146 | } 147 | 148 | set ondatagramreceived(handler) { 149 | this._onDatagramReceived.handler = handler; 150 | } 151 | } 152 | 153 | class QuicStream { 154 | get transport() { 155 | return this._transport; 156 | } 157 | 158 | get streamId() { 159 | return this._streamId; 160 | } 161 | } 162 | 163 | class QuicReceiveStream extends QuicStream { 164 | constructor(transport, streamId, maxBufferedAmount) { 165 | super(); 166 | this._transport = transport; 167 | this._streamId = streamId; 168 | 169 | // Data received but not read (buffered) 170 | this._receiveBuffer = new CircularBuffer(maxBufferedAmount); 171 | this._receivedFinBit = false; 172 | this._readableAmount = new State(0); 173 | // Whether or not the fin bit has been read out or not 174 | this._readFinBit = false; 175 | 176 | // Allows the send stream to abort the reading. 177 | this._abortReadingFromSendStream = null; 178 | this._readingAbortedFromSendStream = new Promise((resolve, reject) => { 179 | this._abortReadingFromSendStream = resolve; 180 | }); 181 | // Allows send stream to access Promise for when reading is aborted from 182 | // this receive stream. 183 | this._abortReadingFromReceiveStream = null; 184 | this._readingAbortedFromReceiveStream = new Promise((resolve, reject) => { 185 | this._abortReadingFromReceiveStream = resolve; 186 | }); 187 | } 188 | 189 | get readable() { 190 | return !this._readFinBit && !this._transport._closedOrFailed; 191 | } 192 | 193 | get readableAmount() { 194 | return this._readableAmount.current; 195 | } 196 | 197 | get readingAborted() { 198 | return this._readingAbortedFromSendStream; 199 | } 200 | 201 | readInto(array) { 202 | const amount = this._receiveBuffer.dequeInto(array); 203 | if (this._receivedFinBit && this._receiveBuffer.usedSize == 0) { 204 | this._readFinBit = true; 205 | } 206 | this._readableAmount.change(this._receiveBuffer.usedSize); 207 | return { 208 | amount: amount, 209 | finished: this._readFinBit 210 | }; 211 | } 212 | 213 | abortReading(info) { 214 | this._abortReadingFromReceiveStream(); 215 | } 216 | 217 | async waitForReadable(wantedAmount) { 218 | await this._readableAmount.until(readableAmount => readableAmount >= wantedAmount); 219 | } 220 | 221 | set _onreadableamountchanged(handler) { 222 | this._readableAmount.onchange = handler; 223 | } 224 | 225 | // Used by QuicSendStream. 226 | _receive(array, finBit) { 227 | if (array.length > this._receiveBuffer.unusedSize) { 228 | console.error("QuicSendStream sent more data than can be buffered."); 229 | } 230 | 231 | let x = this._receiveBuffer.queue(array); 232 | if (finBit) { 233 | this._receivedFinBit = finBit; 234 | } 235 | this._readableAmount.change(this._receiveBuffer.usedSize); 236 | } 237 | } 238 | 239 | // TODO: Implement params.disableRetransmissions. 240 | class QuicSendStream extends QuicStream { 241 | constructor(transport, streamId, params, recvStream, maxBufferedAmount) { 242 | super(); 243 | this._transport = transport; 244 | this._streamId = streamId; 245 | this._params = params; 246 | this._recvStream = recvStream; 247 | 248 | this._writeBuffer = new CircularBuffer(maxBufferedAmount); 249 | this._writeBufferedAmount = new State(0); 250 | this._wroteFinBit = false; 251 | 252 | this._recvStream._onreadableamountchanged = (readableAmount) => { 253 | this._dequeUnreceived(); 254 | }; 255 | this._recvStreamFired = false; 256 | } 257 | 258 | get writable() { 259 | return !this._wroteFinBit && !this._transport._closedOrFailed; 260 | } 261 | 262 | get writeBufferedAmount() { 263 | return this._writeBufferedAmount.current; 264 | } 265 | 266 | get writingAborted() { 267 | return this._recvStream._readingAbortedFromReceiveStream; 268 | } 269 | 270 | write(params) { 271 | if (!this.writable) { 272 | throw new InvalidStateError(); 273 | } 274 | if (params.finished && params.data.length == 0) { 275 | throw new NotSupportedError(); 276 | } 277 | if (params.data.length > this._writeBuffer.unusedSize) { 278 | // We can't write more than available in the buffer. 279 | throw new NotSupportedError(); 280 | } 281 | if (params.finished) { 282 | this._wroteFinBit = true; 283 | } 284 | 285 | if (this.writeBufferedAmount > 0) { 286 | // Backpressure is being applied from receive side, so append to 287 | // already buffered data. 288 | this._queueUnreceived(params.data); 289 | } else { 290 | let sendAmount = Math.min(this._recvStream._receiveBuffer.unusedSize, 291 | params.data.length); 292 | let sendData = params.data.slice(0, sendAmount); 293 | // Send the FIN bit to receive side if it has been written by write() 294 | // and we have dequeued everything in the write buffer. 295 | this._recvStream._receive(sendData, this._wroteFinBit); 296 | if (sendAmount < params.data.length) { 297 | // Queue data that couldn't be sent. 298 | this._queueUnreceived(params.data.slice(sendAmount)); 299 | } 300 | } 301 | 302 | if (!this._recvStreamFired) { 303 | this._transport._onReceiveStream.fire(new ReceiveStreamEvent(this._recvStream)); 304 | this._recvStreamFired = true; 305 | } 306 | } 307 | 308 | abortWriting(info) { 309 | this._recvStream._abortReadingFromSendStream(); 310 | } 311 | 312 | async waitForWriteBufferedAmountBelow(threshold) { 313 | await this._writeBufferedAmount.until(writableAmount => writableAmount < threshold); 314 | } 315 | 316 | 317 | _queueUnreceived(unreceived) { 318 | let queuedAmount = this._writeBuffer.queue(unreceived); 319 | if (queuedAmount < unreceived.length) { 320 | console.error("Could not buffer all write() data."); 321 | this.abortWriting(500); 322 | } 323 | if (queuedAmount) { 324 | this._writeBufferedAmount.change(this._writeBuffer.usedSize); 325 | } 326 | } 327 | 328 | _dequeUnreceived() { 329 | let dequeAmount = Math.min(this._recvStream._receiveBuffer.unusedSize, 330 | this._writeBuffer.usedSize); 331 | if (dequeAmount == 0) { 332 | return; 333 | } 334 | let sendData = new Uint8Array(dequeAmount); 335 | this._writeBuffer.dequeInto(sendData); 336 | // Send the FIN bit to receive side if it has been written by write() 337 | // and we have dequeued everything in the write buffer. 338 | let sendFin = this._wroteFinBit && this._writeBuffer.usedSize == 0; 339 | this._recvStream._receive(sendData, sendFin); 340 | this._writeBufferedAmount.change(this._writeBuffer.usedSize); 341 | } 342 | } 343 | 344 | class State { 345 | constructor(state) { 346 | this._state = state; 347 | this._changed = new Event(); 348 | } 349 | 350 | get current() { 351 | return this._state; 352 | } 353 | 354 | async until(pred) { 355 | while(!pred(this.current)) { 356 | await this._changed.nextStatePromise; 357 | } 358 | } 359 | 360 | set onchange(handler) { 361 | this._changed.handler = handler; 362 | } 363 | 364 | change(state) { 365 | this._state = state; 366 | this._changed.fire(new Event()); 367 | } 368 | } 369 | 370 | class Event { 371 | constructor() { 372 | this._handler = null; 373 | this._resolveNextState = null; 374 | this._nextStatePromise = new Promise((resolve, reject) => { 375 | this._resolveNextState = resolve; 376 | }); 377 | } 378 | 379 | get nextStatePromise() { 380 | return this._nextStatePromise; 381 | } 382 | 383 | set handler(handler) { 384 | this._handler = handler; 385 | } 386 | 387 | fire(payload) { 388 | if (this._handler) { 389 | this._handler(payload); 390 | } 391 | this._resolveNextState(); 392 | this._nextStatePromise = new Promise((resolve, reject) => { 393 | this._resolveNextState = resolve; 394 | }); 395 | } 396 | } 397 | 398 | class InvalidStateError extends Error { 399 | } 400 | 401 | class NotSupportedError extends Error { 402 | } 403 | 404 | class ReceiveStreamEvent extends Event { 405 | constructor(stream) { 406 | super(); 407 | this.stream = stream; 408 | } 409 | } 410 | 411 | class DatagramReceivedEvent extends Event { 412 | constructor(data) { 413 | super(); 414 | this.data = data; 415 | } 416 | } 417 | 418 | class CircularBuffer { 419 | constructor(maxSize) { 420 | this._array = new Uint8Array(maxSize); 421 | this._start = 0; 422 | this._end = 0; 423 | this._usedSize = 0; 424 | } 425 | 426 | get maxSize() { 427 | return this._array.length; 428 | } 429 | 430 | get usedSize() { 431 | return this._usedSize; 432 | } 433 | 434 | get unusedSize() { 435 | return this.maxSize - this._usedSize; 436 | } 437 | 438 | queue(array) { 439 | let amount = Math.min(array.length, this.unusedSize); 440 | 441 | // Lazy copy; could be faster if not so lazy 442 | for (let i = 0; i < amount; i++) { 443 | this._array[this._end] = array[i]; 444 | this._end = (this._end + 1) % this.maxSize; 445 | } 446 | this._usedSize += amount; 447 | return amount; 448 | } 449 | 450 | dequeInto(array) { 451 | let amount = Math.min(array.length, this.usedSize); 452 | 453 | // Lazy copy; could be faster if not so lazy 454 | for (let i = 0; i < amount; i++) { 455 | array[i] = this._array[this._start]; 456 | this._start = (this._start + 1) % this.maxSize; 457 | } 458 | this._usedSize -= amount; 459 | return amount; 460 | } 461 | } 462 | 463 | function later(f) { 464 | setTimeout(f, 0); 465 | } 466 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |This document defines a set of ECMAScript APIs in WebIDL to allow data to be sent 13 | and received from another browser or device implementing the QUIC protocol. 14 | The specification for multiplexing of QUIC with STUN/TURN/ZRTP/DTLS/RTP/RTCP 15 | [[RFC9443]] was developed within the IETF AVTCORE Working Group.
16 |22 | This specification extends the WebRTC [[WEBRTC]], ORTC [[ORTC]] and WebTransport 23 | [[WEBTRANSPORT]] APIs to enable peer-to-peer operation using QUIC [[RFC9000]]. 24 | This specification supports the exchange of arbitrary data with remote peers using 25 | NAT-traversal technologies such as ICE, STUN, and TURN. As specified in [[RFC9443]], 26 | QUIC can be multiplexed on the same port as RTP, RTCP, DTLS, STUN and TURN, allowing 27 | the API defined in this specification to be utilized along with the functionality 28 | defined in [[WEBRTC]] and [[ORTC]] including communication using audio/video media 29 | and SCTP data channels. 30 |
31 |32 | This specification defines an interface to QUIC streams [[RFC9000]] as well as 33 | datagrams [[RFC9221]]. By utilizing a QUIC stream per message, it is possible 34 | to implement support for message-based communications (such as {{RTCDataChannel}}) 35 | on top. 36 |
37 |This specification extends the WebTransport API [[WEBTRANSPORT]] 38 | under development within the W3C WebTransport WG.
39 |42 | Conformance requirements phrased as algorithms or specific steps may be 43 | implemented in any manner, so long as the end result is equivalent. (In 44 | particular, the algorithms defined in this specification are intended 45 | to be easy to follow, and not intended to be performant.) 46 |
47 |48 | Implementations that use ECMAScript to implement the APIs defined in 49 | this specification MUST implement them in a manner consistent with the 50 | ECMAScript Bindings defined in the Web IDL specification [[!WEBIDL]], 51 | as this specification uses that specification and terminology. 52 |
53 |57 | The {{EventHandler}} interface, representing a callback used for event 58 | handlers, is defined in [[!HTML]]. 59 |
60 |61 | The concepts [= queue a task =] and [= networking task source =] are 62 | defined in [[!HTML]]. 63 |
64 |65 | The concept [= fire an event =] is defined in [[!DOM]]. 66 |
67 |68 | The terms [= event =], [= event handlers =] and [= event handler event 69 | types =] are defined in [[!HTML]]. 70 |
71 |72 | {{Performance.timeOrigin}} and {{Performance.now()}} are defined in 73 | [[!hr-time]]. 74 |
75 |76 | The terms serializable objects, 77 | [= serialization steps =], and [= deserialization steps =] are defined in [[!HTML]]. 78 |
79 |80 | When referring to exceptions, the terms [= exception/throw =] and 81 | [= exception/created =] are defined in [[!WEBIDL]]. 82 |
83 |84 | The callback {{VoidFunction}} is defined in [[!WEBIDL]]. 85 |
86 |87 | The term "throw" is used as specified in [[!INFRA]]: it terminates the 88 | current processing steps. 89 |
90 |91 | The terms fulfilled, 92 | rejected, resolved, and 94 | settled used in the context of Promises are defined in 95 | [[!ECMASCRIPT-6.0]]. 96 |
97 |98 | WebTransport is defined in [[!WEBTRANSPORT]] Section 5. 99 |
100 |101 | RTCIceTransport is defined in [[!WEBRTC]] Section 5.6 and [[!ORTC]] Section 3. 102 |
103 |104 | RTCCertificate is defined in [[!WEBRTC]] Section 4.9 and [[!ORTC]] Section 15. 105 |
106 |107 | RTCDtlsFingerprint is defined in [[!WEBRTC]] Section 5.5.2 and [[!ORTC]] Section 4.5. 108 |
109 |110 | [[\State]] is defined in [[!WEBTRANSPORT]] Section 5.1. 111 |
112 |
116 | The RTCQuicTransport interface extends the WebTransport interface
117 | to support peer-to-peer use cases, by adding information relating to use of a
118 | QUIC transport with an ICE transport.
119 |
123 | A RTCQuicTransport instance is constructed
124 | using an RTCIceTransport and an optional sequence of
125 | RTCCertificate objects. A
126 | RTCQuicTransport object whose {{RTCQuicTransport/[[State]]}} internal
127 | slot is "closed" or "failed" can be
128 | garbage-collected when it is no longer referenced.
129 |
131 | The QUIC negotiation occurs between transport endpoints determined via ICE. 132 | Multiplexing of QUIC with STUN, TURN, DTLS, RTP and RTCP is defined in [[RFC9443]]. 133 |
134 |
135 | A newly constructed RTCQuicTransport MUST listen and respond to incoming QUIC packets before
137 | start() is called. However, to complete the negotiation it is
138 | necessary to verify the remote fingerprint by computing fingerprints for
139 | the selected remote certificate using the digest algorithms provided
140 | in remoteParameters.fingerprints[].algorithm. If a
141 | calculated fingerprint and algorithm matches a fingerprint and algorithm
142 | included in remoteParameters.fingerprints[],
143 | the remote fingerprint is verified. After the QUIC handshake exchange
144 | completes (but before the remote fingerprint is verified) incoming media packets
145 | may be received. A modest buffer MUST be
146 | provided to avoid loss of media prior to remote fingerprint validation (which can
147 | begin after start() is called).
148 |
154 | [Exposed=Window]
155 | interface RTCQuicTransport : WebTransport {
156 | constructor(RTCIceTransport transport, optional sequence<RTCCertificate> certificates);
157 | readonly attribute RTCIceTransport transport;
158 | RTCQuicParameters getLocalParameters ();
159 | RTCQuicParameters? getRemoteParameters ();
160 | sequence<RTCCertificate> getCertificates ();
161 | sequence<ArrayBuffer> getRemoteCertificates ();
162 | undefined start (RTCQuicParameters remoteParameters);
163 | };
164 |
167 | When RTCQuicTransport.constructor() is invoked,
168 | the user agent MUST run the
169 | following steps:
170 |
state attribute has the
177 | value "closed" [= exception/throw =] an
178 | InvalidStateError and abort these steps.
179 | RTCQuicTransport whose {{RTCQuicTransport/[[State]]}} internal
182 | slot is not "closed", [= exception/throw =] an
183 | InvalidStateError and abort these steps.
184 | null otherwise.
188 | expires attribute of each RTCCertificate
192 | object is in the future. If a certificate has expired, [= exception/throw =]
193 | an InvalidAccessError and abort these steps.
194 | RTCQuicTransport object.
198 | "connecting".
202 | null or is empty,
215 | generate a certificate using the default key generation algorithm
216 | and store it in the {{RTCQuicTransport/[[Certificates]]}} internal slot.
217 | RTCQuicTransport| Parameter | 230 |Type | 231 |Nullable | 232 |Optional | 233 |Description | 234 |
|---|---|---|---|---|
| transport | 237 |RTCIceTransport |
238 | ✘ | 240 |✘ | 242 |243 | |
| certificates | 246 |
247 | sequence<RTCCertificate> |
248 | ✘ | 250 |✔ | 252 |253 | |
transport of type RTCIceTransport, readonlyThe associated RTCIceTransport instance.
267 | When the RTCIceTransport's state
268 | attribute changes values, the user agent MUST
269 | run the following steps:
state attribute is not
277 | "closed", abort these steps.
278 | RTCQuicTransport.
281 | "closed".
285 | getLocalParameters
297 | getLocalParameters() obtains the QUIC parameters of
298 | the local RTCQuicTransport upon construction.
299 | If multiple certificates were provided in the constructor, then
300 | multiple fingerprints will be returned, one for each certificate.
301 | getLocalParameters().role always returns the default
302 | role of a newly constructed RTCQuicTransport;
303 | for a browser this will be auto.
304 |
RTCQuicParameters
310 | getRemoteParameters
315 | getRemoteParameters() obtains
316 | the remote QUIC parameters passed in the
317 | start() method. Prior to calling
318 | start(), null is returned.
319 |
RTCQuicParameters, nullable
325 | getCertificatesgetCertificates() returns the value of the RTCQuicTransport's
330 | {{RTCQuicTransport/[[Certificates]]}} internal slot.
sequence<RTCCertificate>
336 | getRemoteCertificates
341 | getRemoteCertificates() returns the certificate chain in use by the remote side, with each
342 | certificate encoded in binary Distinguished Encoding Rules (DER) [[!X690]].
343 | getRemoteCertificates() returns an empty list prior to
344 | selection of the remote certificate, which is completed once quictransport's
345 | {{RTCQuicTransport/[[State]]}} internal slot transitions to "connected".
346 |
sequence<ArrayBuffer>
352 | start357 | Start QUIC transport negotiation with the parameters of the remote QUIC 358 | transport, including verification of the remote fingerprint. 359 | During connection establishment, use of this API must be indicated 360 | by selecting the ALPN token "q2q" in the crypto handshake. 361 | 362 |
363 |
364 | Only a single QUIC transport can be multiplexed over an ICE transport.
365 | Therefore if a RTCQuicTransport object
366 | quicTransportB is constructed with an
367 | {{RTCIceTransport}} object iceTransport
368 | previously used to construct another RTCQuicTransport
369 | object quicTransportA, then if
370 | quicTransportB.start() is called prior to having called
371 | quicTransportA.stop(), then [= exception/throw =] an
372 | InvalidStateError.
374 | If start is called after a previous start
375 | call, or if quictransport's {{RTCQuicTransport/[[State]]}} internal slot is
376 | "closed", [= exception/throw = ] an InvalidStateError.
377 |
379 | If all of the values of
380 | remoteParameters.fingerprints[j].algorithm
381 | are unsupported, where j goes from 0 to the number of fingerprints,
382 | [= exception/throw =] a NotSupportedError.
383 |
| Parameter | 388 |Type | 389 |Nullable | 390 |Optional | 391 |Description | 392 |
|---|---|---|---|---|
| remoteParameters | 395 |RTCQuicParameters |
396 | ✘ | 398 |✘ | 400 |401 | |
void
406 | The RTCQuicParameters dictionary includes information
415 | relating to QUIC configuration.
dictionary RTCQuicParameters {
418 | RTCQuicRole role = "auto";
419 | required sequence<RTCDtlsFingerprint> fingerprints;
420 | };
421 | role of type RTCQuicRole, defaulting to
427 | "auto"The QUIC role, with a default of auto.
fingerprints of type sequence<{{RTCDtlsFingerprint}}>Sequence of fingerprints, at least one fingerprint for each certificate 435 | (with one computed with the digest algorithm used in the certificate 436 | signature).
437 |RTCQuicRole indicates the role of the QUIC
445 | transport.
enum RTCQuicRole {
448 | "auto",
449 | "client",
450 | "server"
451 | };
452 | | Enumeration description | 456 ||
|---|---|
auto |
459 |
460 | The QUIC role is determined based on the resolved ICE role:
461 | the ICE |
464 |
client |
467 |
468 | The QUIC client role. 469 | |
470 |
server |
473 |
474 | The QUIC server role. 475 | |
476 |
484 | To diagnose QUIC role issues, an application may wish to determine
485 | the desired and actual QUIC role of an RTCQuicTransport.
486 | For a browser implementing ORTC, a RTCQuicTransport
487 | object assumes a QUIC role of auto upon construction.
488 | This implies that the QUIC role is determined by the ICE role. Since
489 | getLocalParameters().role always returns the role assigned
490 | to an RTCQuicTransport object upon construction
491 | (auto for a browser), the getLocalParameters
492 | method cannot be used to determine the desired or actual role of an
493 | RTCQuicTransport.
494 |
496 | An application can determine the desired role of an
497 | RTCQuicTransport from the value of
498 | remoteParameters.role passed to
499 | RTCQuicTransport.start(remoteParameters).
500 | If remoteParameters.role is server
501 | then the desired role of the RTCQuicTransport
502 | is client. If remoteParameters.role
503 | is client then the desired role of the
504 | RTCQuicTransport is server.
505 |
507 | The RTCQuicTransport.transport.onstatechange EventHandler
508 | can be used to determine whether an RTCQuicTransport
509 | transitions to the desired role. When
510 | RTCQuicTransport.transport.state transitions to
511 | connected, if RTCQuicTransport.transport.role
512 | is controlled then the role of the
513 | RTCQuicTransport is client.
514 | If RTCQuicTransport.transport.role
515 | is controlling then the role of the
516 | RTCQuicTransport is server.
517 |
523 | function initiate(mySignaller) {
524 | // Prepare the ICE gatherer
525 | var gatherOptions = {
526 | gatherPolicy: "all",
527 | iceServers: [
528 | { urls: "stun:stun1.example.net" },
529 | { urls: "turn:turn.example.org", username: "user", credential: "myPassword",
530 | credentialType: "password" }
531 | ]
532 | };
533 | var iceGatherer = new RTCIceGatherer(gatherOptions);
534 | // Handle state changes
535 | iceGatherer.onstatechange = function(event) {
536 | myIceGathererStateChange("iceGatherer", event.state);
537 | };
538 | // Handle errors
539 | iceGatherer.onerror = errorHandler;
540 | // Prepare to signal local candidates
541 | iceGatherer.onlocalcandidate = function(event) {
542 | mySignaller.mySendLocalCandidate(event.candidate);
543 | };
544 |
545 | // Start gathering
546 | iceGatherer.gather();
547 | // Create ICE transport
548 | var ice = new RTCIceTransport(iceGatherer);
549 | // Prepare to handle remote ICE candidates
550 | mySignaller.onRemoteCandidate = function(remote) {
551 | ice.addRemoteCandidate(remote.candidate);
552 | };
553 |
554 | // Create the DTLS certificate
555 | var certs;
556 | var keygenAlgorithm = { name: "ECDSA", namedCurve: "P-256" };
557 | RTCCertificate.generateCertificate(keygenAlgorithm).then(function(certificate){
558 | certs[0] = certificate;
559 | }, function(){
560 | trace('Certificate could not be created');
561 | });
562 |
563 | // Create DTLS and QUIC transport
564 | var dtls = new RTCDtlsTransport(ice, certs);
565 | var quic = new RTCQuicTransport(ice, certs);
566 |
567 | mySignaller.sendInitiate({
568 | ice: iceGatherer.getLocalParameters(),
569 | dtls: dtls.getLocalParameters(),
570 | quic: quic.getLocalParameters(),
571 | // ... marshall RtpSender/RtpReceiver capabilities as illustrated in Section 6.5 Example 9.
572 | }, function(remote) {
573 | // Start the ICE, DTLS and QUIC transports
574 | ice.start(iceGatherer, remote.ice, RTCIceRole.controlling);
575 | dtls.start(remote.dtls);
576 | quic.start(remote.quic);
577 | // ... configure RtpSender/RtpReceiver objects as illustrated in Section 6.5 Example 9.
578 | });
579 | }
580 |
581 |
582 | // This is an example of how to answer
583 | // Include some helper functions
584 | import {trace, errorHandler, mySendLocalCandidate, myIceGathererStateChange,
585 | myIceTransportStateChange, myDtlsTransportStateChange} from 'helper';
586 |
587 | function accept(mySignaller, remote) {
588 | var gatherOptions = {
589 | gatherPolicy: "all",
590 | iceServers: [
591 | { urls: "stun:stun1.example.net" },
592 | { urls: "turn:turn.example.org", username: "user", credential: "myPassword",
593 | credentialType: "password" }
594 | ]
595 | };
596 | var iceGatherer = new RTCIceGatherer(gatherOptions);
597 | // Handle state changes
598 | iceGatherer.onstatechange = function(event) {
599 | myIceGathererStateChange("iceGatherer", event.state);
600 | };
601 | // Handle errors
602 | iceGatherer.onerror = errorHandler;
603 | // Prepare to signal local candidates
604 | iceGatherer.onlocalcandidate = function(event) {
605 | mySignaller.mySendLocalCandidate(event.candidate);
606 | };
607 |
608 | // Start gathering
609 | iceGatherer.gather();
610 | // Create ICE transport
611 | var ice = new RTCIceTransport(iceGatherer);
612 | // Prepare to handle remote ICE candidates
613 | mySignaller.onRemoteCandidate = function(remote) {
614 | ice.addRemoteCandidate(remote.candidate);
615 | };
616 |
617 | // Create the DTLS certificate
618 | var certs;
619 | var keygenAlgorithm = { name: "ECDSA", namedCurve: "P-256" };
620 | RTCCertificate.generateCertificate(keygenAlgorithm).then(function(certificate){
621 | certs[0] = certificate;
622 | }, function(){
623 | trace('Certificate could not be created');
624 | });
625 |
626 | // Create DTLS and SCTP transport
627 | var dtls = new RTCDtlsTransport(ice, certs);
628 | var quic = new RTCQuicTransport(ice, certs);
629 |
630 | mySignaller.sendAccept({
631 | ice: iceGatherer.getLocalParameters(),
632 | dtls: dtls.getLocalParameters(),
633 | quic: quic.getLocalParameters(),
634 | // ... marshall RtpSender/RtpReceiver capabilities as illustrated in Section 6.5 Example 9.
635 | });
636 |
637 | // Start the ICE, DTLS and SCTP transports
638 | ice.start(iceGatherer, remote.ice, RTCIceRole.controlled);
639 | dtls.start(remote.dtls);
640 | // Start the QuicTransport
641 | quic.start(remote.quic);
642 |
643 | // ... configure RtpSender/RtpReceiver objects as illustrated in Section 6.5 Example 9.
644 |
645 | }
646 |
647 | 651 | This section is non-normative; it specifies no new behaviour, but 652 | instead summarizes information already present in other parts of the 653 | specification. The overall security considerations of the 654 | APIs and protocols used in WebRTC are described in [[RFC8827]]. 655 |
656 |659 | The QUIC API enables data to be communicated between 660 | browsers and other devices, including other browsers. 661 |
662 |663 | This means that data can be shared between applications 664 | running in different browsers, or between an application running in the 665 | same browser and something that is not a browser. This is an extension 666 | to the Web model which has had barriers against sending data 667 | between entities with different origins. 668 |
669 |670 | This specification provides no user prompts or chrome indicators 671 | for communication; it assumes that once the Web page has been allowed to 672 | access data, it is free to share that data with other entities as it 673 | chooses. Peer-to-peer exchanges of data via QUIC can therefore 674 | occur without any user explicit consent or involvement. 675 |
676 |Since the browser is an active platform executing in a trusted network 680 | environment (inside the firewall), it is important to limit the damage 681 | that the browser can do to other elements on the local network, and it is 682 | important to protect data from interception, manipulation and 683 | modification by untrusted participants.
684 |Mitigations include:
685 |These measures are specified in the relevant IETF documents.
697 |Utilizing the generateCertificate() API in [[!WEBRTC]], it is possible to
701 | generate and store certificates that can subsequently be reused in constructing
702 | RTCQuicTransport objects. These persistent certificates
703 | can therefore be used to identify a user.
This document defines a set of ECMAScript APIs in WebIDL to allow data to be sent 13 | and received between a browser and server implementing the QUIC 14 | protocol. This specification is being developed in conjunction with a protocol 15 | specification developed by the IETF QUIC Working Group.
16 |This specification uses QUIC [[!QUIC-TRANSPORT]] to send data 22 | to and receive data from servers. It can be used like WebSockets 23 | but with support for multiple streams, unidirectional streams, 24 | out-or-order deliver, and unreliable delivery.
25 |The QUIC API presented in this specification 26 | represents a preliminary proposal based on work-in-progress 27 | within the IETF QUIC WG. Since the QUIC transport specification is 28 | a work-in-progress, both the protocol and API are likely to 29 | change significantly going forward.
30 |This specification defines conformance criteria that apply to a single 33 | product: the user agent that implements the interfaces that it 34 | contains.
35 |Conformance requirements phrased as algorithms or specific steps may be 36 | implemented in any manner, so long as the end result is equivalent. (In 37 | particular, the algorithms defined in this specification are intended to be 38 | easy to follow, and not intended to be performant.)
39 |Implementations that use ECMAScript to implement the APIs defined in 40 | this specification MUST implement them 41 | in a manner consistent with the ECMAScript Bindings defined in the Web IDL 42 | specification [[!WEBIDL-1]], as this specification uses that specification 43 | and terminology.
44 |The EventHandler
49 | interface, representing a callback used for event handlers, and the ErrorEvent
51 | interface are defined in [[!HTML51]].
The concepts queue a task, 54 | fires a simple 56 | event and networking 58 | task source are defined in [[!HTML51]].
59 |The term finished reading means that the application has read all
60 | available data up to the STREAM frame with the FIN bit set, which causes
61 | the [[\Readable]] slot to be set to false.
The terms event, event 64 | handlers and event 66 | handler event types are defined in [[!HTML51]].
67 |When referring to exceptions, the terms throw and 69 | create are 71 | defined in [[!WEBIDL-1]].
72 |The terms fulfilled, rejected, 74 | resolved, pending and 75 | settled used in the context of Promises are defined in 76 | [[!ECMASCRIPT-6.0]].
77 |The QuicTransportBase is the base interface
81 | for QuicTransport. Most of the functionality of a
82 | QuicTransport is in the base class to allow for other
83 | subclasses (such as a p2p variant) to share the same interface.
84 |
An QuicTransportBase instance can be associated to
88 | one or more QuicBidirectionalStream,
89 | QuicSendStream, or QuicReceiveStream
90 | instances.
96 | interface QuicTransportBase {
97 | readonly attribute QuicTransportState state;
98 | readonly attribute unsigned short maxDatagramSize;
99 | void stop (QuicTransportStopInfo stopInfo);
100 | Promise<QuicBidirectionalStream> createBidirectionalStream ();
101 | Promise<QuicSendStream> createSendStream (optional QuicStreamParameters parameters);
102 | Promise<void> readyToSendDatagram ();
103 | Promise<boolean> sendDatagram (Uint8Array data);
104 | Promise<sequence<Uint8Array>> receiveDatagrams ();
105 | attribute EventHandler onstatechange;
106 | attribute EventHandler onerror;
107 | attribute EventHandler onreceivestream;
108 | attribute EventHandler onbidirectionalstream;
109 | };
110 | state of type QuicTransportState, readonlyThe current state of the QUIC transport. On getting, it 118 | MUST return the value 119 | of the [[\QuicTransportState]] internal slot.
120 |maxDatagramSize of type unsigned short, readonlyThe maximum size data that may be passed to sendDatagram.
125 |onstatechange of type EventHandlerThis event handler, of event handler event type
130 | statechange, MUST
131 | be fired any time the [[\QuicTransportState]] slot changes, unless
132 | the state changes due to calling stop.
onerror of type EventHandlerThis event handler, of event handler event type error,
138 | MUST be fired on reception of a QUIC
139 | error; an implementation SHOULD include QUIC error information in
141 | error.message (defined in [[!HTML51]] Section 7.1.3.8.2). This
142 | event MUST be fired before the
143 | onstatechange event.
onreceivestream of type EventHandlerThis event handler, of event handler event type
149 | receivestream,
150 | MUST be fired on when data is received
151 | from a newly created remote QuicReceiveStream for the
152 | first time.
153 |
onbidirectionalstream of type EventHandlerThis event handler, of event handler event type
159 | bidirectionalstream,
160 | MUST be fired when data is received
161 | from a newly created remote QuicBidirectionalStream for the
162 | first time.
163 |
stopStops and closes the QuicTransportBase object.
174 | This triggers an Immediate Close as described in [[QUIC-TRANSPORT]] section 10.3.
175 |
When stop is called, the user agent MUST
176 | run the following steps:
QuicTransportBase
179 | on which stop is invoked."closed"
181 | then abort these steps."closed".stopInfo be the first argument.void
194 | | Parameter | 199 |Type | 200 |Nullable | 201 |Optional | 202 |Description | 203 |
|---|---|---|---|---|
| stopInfo | 206 |QuicTransportStopInfo |
207 | ✘ | 209 |✘ | 211 |212 | |
createBidirectionalStreamCreates an QuicBidirectionalStream object.
When createBidectionalStream is called, the user agent
221 | MUST run the following
222 | steps:
Let transport be the QuicTransportBase
226 | on which createBidectionalStream is invoked.
If transport's state is "closed" or
230 | "failed", immediately return a new rejected promise with a
231 | newly created InvalidStateError and abort these steps.
If transport's state is "connected",
235 | immediately return a new resolved promise with a newly created
236 | QuicBidirectionalStream object,
237 | add the QuicBidirectionalStream to the transport
238 | and abort these steps.
Let p be a new promise.
242 |Return p and continue the following steps in 245 | the background.
246 |Resolve p with a newly created
250 | QuicBidirectionalStream object,
251 | add the QuicBidirectionalStream to the transport
252 | when all of the following conditions are met:
transport's state has transitioned to
255 | "connected"Reject p with a newly created
265 | InvalidStateError when all of the following conditions are met:
266 |
transport's state transitions to
268 | "closed" or "failed"Promise<QuicBidirectionalStream>
279 | createSendStreamCreates an QuicSendStream object.
When createSendStream is called, the user agent
285 | MUST run the following
286 | steps:
Let transport be the QuicTransportBase
290 | on which createSendStream is invoked.
If transport's state is "closed" or
294 | "failed", immediately return a new rejected promise with a
295 | newly created InvalidStateError and abort these steps.
If transport's state is "connected",
299 | immediately return a new resolved promise with a newly created
300 | QuicSendStream object,
301 | add the QuicSendStream to the transport
302 | and abort these steps.
Let p be a new promise.
306 |Return p and continue the following steps in 309 | the background.
310 |Resolve p with a newly created
314 | QuicSendStream object,
315 | add the QuicSendStream to the transport
316 | when all of the following conditions are met:
transport's state has transitioned to
319 | "connected"Reject p with a newly created
329 | InvalidStateError when all of the following conditions are met:
330 |
transport's state transitions to
332 | "closed" or "failed"Promise<QuicSendStream>
343 | readyToSendDatagramReturns a promise that will be resolved when the QuicTransport can send 348 | a datagram as defined by [[QUIC-DATAGRAM]].
349 |When readyToSendDatagram is called, the user agent
350 | MUST run the following
351 | steps:
Let p be a new promise.
355 |Let transport be the
358 | QuicTransportBase on which
359 | readyToSendDatagram is invoked.
Return p and continue the following steps in 363 | the background.
364 |If transport can send a datagram, imediately resolve 368 | p and abort these steps.
369 |If transport's state is "failed"
372 | or "closed" immediately reject p with a newly
373 | created InvalidStateError and abort these steps.
If transport is blocked from sending a datagram due to
377 | congestion control, resolve p when transport
378 | is no longer blocked.
reject p with a newly created
382 | InvalidStateError if the transport's
383 | state transitions to "failed" or "closed".
Promise<void>
392 | sendDatagramSends a datagram as defined by [[QUIC-DATAGRAM]].
397 |When sendDatagram is called, the user agent
398 | MUST run the following
399 | steps:
Let transport be
404 | the QuicTransportBase on
405 | which sendDatagram is invoked.
If transport's state is not connected return
409 | a promise rejected with a newly created
410 | InvalidStateError and abort these steps.
If data is too large to fit into a
414 | datagram, return a promise rejected with a newly created
415 | InvalidArgumentError and abort these steps.
If transport is unable to send the datagram due
419 | to congestion control, return a promise rejected with
420 | a newly created InvalidStateError and abort
421 | these steps.
Let p be a new promise.
425 |Return p and continue the following steps in 428 | the background.
429 || Parameter | 448 |Type | 449 |Nullable | 450 |Optional | 451 |Description | 452 |
|---|---|---|---|---|
| data | 455 |Uint8Array |
456 | ✘ | 458 |✘ | 460 |461 | |
Promise<boolean>
466 | receiveDatagramsIf datagrams have been received since the last call to receiveDatagrams(), 471 | return a new promise resolved with all of the received datagrams.
472 |If not, return a new promise that will resolve when more datagrams are received, 473 | resolved with all datagrams received.
474 |If too many datagrams are queued between calls to receiveDatagrams(), 475 | the implementation may drop datagrams and replace them with a null value 476 | in the sequence of datagrams returned in the next call to receiveDatagrams(). 477 | One null value may represent many dropped datagrams.
478 |
receiveDatagrams() may only be called once at a time. 479 | If a promised returned from a previous call is still unresolved, 480 | the user agent MUST return a new promise rejected with an InvalidStateError.
481 |Promise<sequence<Uint8Array>>
483 | To add the QuicBidirectionalStream to the QuicTransportBase
495 | run the following steps:
Let stream be the newly created
499 | QuicBidirectionalStream object.
Add stream to transport's [[\QuicTransportReadableStreams]] 503 | internal slot.
504 |Add stream to transport's [[\QuicTransportWritableStreams]] 507 | internal slot.
508 |Continue the following steps in the background.
511 |Create stream's associated underlying data 514 | transport.
515 |To add the QuicSendStream to the QuicTransportBase
521 | run the following steps:
Let stream be the newly created
525 | QuicSendStream object.
Add stream to transport's [[\QuicTransportWritableStreams]] 529 | internal slot.
530 |Continue the following steps in the background.
533 |Create stream's associated underlying data 536 | transport.
537 |The QuicStreamParameters dictionary includes information
544 | relating to QUIC stream configuration.
dictionary QuicStreamParameters {
547 | bool disableRetransmissions = false;
548 | };
549 | disableRetransmissions of type bool, defaulting to
555 | falsedisableRetransmissions, with a default of false. If
558 | true, the stream will be sent without retransmissions. If false, the
559 | stream will be sent with retransmissions.
The receivestream event uses the
568 | ReceiveStreamEvent interface.
571 | [ Constructor (DOMString type, ReceiveStreamEventInit eventInitDict), Exposed=Window]
572 | interface ReceiveStreamEvent : Event {
573 | readonly attribute QuicReceiveStream stream;
574 | };
575 | ReceiveStreamEvent| Parameter | 585 |Type | 586 |Nullable | 587 |Optional | 588 |Description | 589 |
|---|---|---|---|---|
| type | 592 |DOMString |
593 | ✘ | 595 |✘ | 597 |598 | |
| eventInitDict | 601 |ReceiveStreamEventInit |
602 | ✘ | 604 |✘ | 606 |607 | |
stream of type QuicReceiveStream, readonlyThe stream
621 | attribute represents the QuicReceiveStream object
622 | associated with the event.
The ReceiveStreamEventInit dictionary includes
629 | information on the configuration of the QUIC stream.
dictionary ReceiveStreamEventInit : EventInit {
631 | QuicReceiveStream stream;
632 | };
633 | stream of type QuicReceiveStreamThe QuicReceiveStream object associated with the
641 | event.
The bidirectionalstream event uses the
650 | BidirectionalStreamEvent interface.
653 | [ Constructor (DOMString type, BidirectionalStreamEventInit eventInitDict), Exposed=Window]
654 | interface BidirectionalStreamEvent : Event {
655 | readonly attribute QuicBidirectionalStream stream;
656 | };
657 | BidirectionalStreamEvent| Parameter | 667 |Type | 668 |Nullable | 669 |Optional | 670 |Description | 671 |
|---|---|---|---|---|
| type | 674 |DOMString |
675 | ✘ | 677 |✘ | 679 |680 | |
| eventInitDict | 683 |BidirectionalStreamEventInit |
684 | ✘ | 686 |✘ | 688 |689 | |
stream of type QuicBidirectionalStream, readonlyThe stream
703 | attribute represents the QuicBidirectionalStream object
704 | associated with the event.
The BidirectionalStreamEventInit dictionary includes
711 | information on the configuration of the QUIC stream.
dictionary BidirectionalStreamEventInit : EventInit {
713 | QuicBidirectionalStream stream;
714 | };
715 | stream of type QuicBidirectionalStreamThe QuicBidirectionalStream object associated with the
723 | event.
QuicTransportState indicates the state of the QUIC
732 | transport.
enum QuicTransportState {
735 | "new",
736 | "connecting",
737 | "connected",
738 | "closed",
739 | "failed"
740 | };
741 | | Enumeration description | 746 ||
|---|---|
new |
749 |
750 | The |
753 |
connecting |
757 |
758 | QUIC is in the process of negotiating a secure connection.
759 | Once a secure connection is negotiated
760 | (but prior to verification of the remote fingerprint, enabled by calling
761 | |
763 |
connected |
767 |
768 | QUIC has completed negotiation of a secure connection. 769 | Outgoing data and media can now flow through. 770 | |
771 |
closed |
775 |
776 | The QUIC connection has been closed intentionally via a call to
777 |
|
808 |
failed |
812 |
813 | The QUIC connection has been closed as the result of an error (such as
814 | receipt of an error alert or a failure to validate the remote
815 | fingerprint). When the
|
844 |
The QuicTransportStopInfo dictionary includes information
852 | relating to the error code for stopping a QuicTransportBase.
853 | This information is used to set the error code and reason for an CONNECTION_CLOSE
854 | frame.
dictionary QuicTransportStopInfo {
857 | unsigned short errorCode = 0;
858 | DOMString reason = "";
859 | };
860 |
861 | errorCode of type unsigned short, defaulting to
867 | 0.The error code used in CONNECTION_CLOSE frame.
870 |reason of type DOMString, defaulting to ""The reason for stopping the QuicTransportBase
The QuicTransportBase is a subclass of
884 | QuicTransportBase focused on client/server use cases.
889 | [ Constructor (DOMString host, unsigned short port), Exposed=Window]
890 | interface QuicTransport : QuicTransportBase {
891 | };
892 | QuicTransport constructor is invoked,
895 | the user agent MUST run the
896 | following steps:
897 | NotSupportedError and abort these steps.
901 | QuicTransport object.
905 | QuicWritableStream
908 | objects, initialized to empty.
909 | QuicReadableStream
912 | objects, initialized to empty.
913 | "connecting".
917 | Uint8Array, initialized to empty.
920 | Promise<sequence<Uint8Array>>?,
923 | initialized to null.
924 | "failed" and abort these steps.
937 | "failed" and abort these steps.
942 |
943 | ",".
948 | "*",
957 | set quictransport's [[\QuicTransportState]]
958 | internal slot to "connected" and abort these steps.
959 | "failed".
963 | QuicTransport| Parameter | 978 |Type | 979 |Nullable | 980 |Optional | 981 |Description | 982 |
|---|---|---|---|---|
| host | 985 |DOMString |
986 | ✘ | 988 |✘ | 990 |The host to connect to. | 991 |
| port | 994 |unsigned short |
995 | ✘ | 997 |✘ | 999 |The port to connect to. | 1000 |
The QUIC Stream API includes information relating
1012 | to a QUIC stream.
QuicBidirectionalStream, QuicSendStream,
1016 | and QuicReceiveStream instances are associated to
1017 | a QuicTransportBase instance.
An QuicBidirectionalStream can be created in the following ways:
QuicTransportBase's createBidirectionalStream method.bidirectionalstream event on the
1025 | QuicTransportBase.An QuicSendStream can be created in the following ways:
QuicTransportBase's createSendStream method.An QuicReceiveStream can be created in the following
1032 | ways:
receivestream event on the
1035 | QuicTransportBase.
1042 | [ Exposed=Window ]
1043 | interface mixin QuicWritableStream {
1044 | readonly attribute boolean writable;
1045 | readonly attribute unsigned long writeBufferedAmount;
1046 | readonly attribute Promise<QuicStreamAbortInfo> writingAborted;
1047 | void write (QuicStreamWriteParameters data);
1048 | void abortWriting (QuicStreamAbortInfo abortInfo);
1049 | Promise<void> waitForWriteBufferedAmountBelow(unsigned long threshold);
1050 | };
1051 |
1052 | The QuicWritableStream will initialize with
1055 | the following:
QuicWritableStream.true.writable of type boolean
1069 | readonlyThe writable
1072 | attribute represents whether data can be written to the
1073 | QuicWritableStream. On getting it
1074 | MUST return the value of the
1075 | [[\Writable]] slot.
writeBufferedAmount of type unsigned
1078 | long, readonlyThe writeBufferedAmount
1081 | attribute represents the number of bytes of application data
1082 | that have been queued using write but that, as of the last
1083 | time the event loop started executing a task, had not yet been transmitted
1084 | to the network. This includes any data sent during the execution of the
1085 | current task, regardless of whether the user agent is able to
1086 | transmit text asynchronously with script execution. This does not
1087 | include framing overhead incurred by the protocol, or buffering done
1088 | by the operating system or network hardware. On getting, it
1089 | MUST return the value of the
1090 | QuicWritableStream's [[\WriteBufferedAmount]] internal slot.
1091 |
writingAborted of type QuicStreamAbortInfo
1093 | readonlyThe writingAborted
1096 | attribute represents a promise that resolves when the
1097 | STOP_SENDING frame is received from the QuicReadableStream.
1098 | When the stream receives a STOP_SENDING frame from its corresponding
1099 | QuicReadableStream, the user agent
1100 | MUST run the following:
1101 |
QuicWritableStream object.
1103 | false.QuicTransportBase,
1106 | which the stream was created from.
1107 | QuicStreamAbortInfo with the errorCode
1111 | set to the value from the STOP_SENDING frame.writeWrites data to the stream. When the remote QuicTransportBase
1123 | receives the STREAM frame from this stream for the first time, it will trigger the
1124 | creation of the corresponding remote stream. When the write method is
1125 | called, the user agent MUST
1126 | run the following steps:
QuicWritableStream
1130 | object on which data is to be sent.false, throw a NotSupportedError and abort
1133 | these steps.false,
1135 | throw an InvalidStateError and abort these steps.true, run the
1142 | following:false.QuicTransportBase,
1148 | which the stream was created from.QuicTransportBase's onerror
1156 | EventHandler.| Parameter | 1162 |Type | 1163 |Nullable | 1164 |Optional | 1165 |Description | 1166 |
|---|---|---|---|---|
| data | 1169 |QuicStreamWriteParameters |
1170 | ✘ | 1172 |✘ | 1174 |1175 | |
void
1180 | abortWritingA hard shutdown of the QuicWritableStream. It may be called
1185 | regardless of whether the QuicWritableStream
1186 | was created by the local or remote peer. When the abortWriting()
1187 | method is called, the user agent MUST
1188 | run the following steps:
QuicWritableStream object
1191 | which is about to abort writing.false,
1193 | throw an InvalidStateError and abort these steps.false.QuicTransportBase,
1197 | which the stream was created from.| Parameter | 1208 |Type | 1209 |Nullable | 1210 |Optional | 1211 |Description | 1212 |
|---|---|---|---|---|
| abortInfo | 1215 |QuicStreamAbortInfo |
1216 | ✘ | 1218 |✘ | 1220 |1221 | |
void
1226 | waitForWriteBufferedAmountBelowwaitForWriteBufferedAmountBelow resolves the promise when
1231 | the data queued in the write buffer falls below the given threshold.
1232 | If waitForWriteBufferedAmountBelow
1233 | is called multiple times, multiple promises could be resolved when the
1234 | write buffer falls below the threshold for each promise. The Promise will
1235 | be rejected with a newly created InvalidStateError if the
1236 | stream's [[\Writable]] slot transitions from true to false
1237 | and the promise isn't settled. When the waitForWriteBufferedAmountBelow method
1238 | is called, the user agent MUST run
1239 | the following steps:
QuicWritableStream
1242 | object on which waitForWriteBufferedAmountBelow was invoked.false, reject p with a
1246 | newly created InvalidStateError and abort
1247 | these steps.undefined.| Parameter | 1257 |Type | 1258 |Nullable | 1259 |Optional | 1260 |Description | 1261 |
|---|---|---|---|---|
| threshold | 1264 |unsigned long |
1265 | ✘ | 1267 |✘ | 1269 |1270 | |
Promise<void>
1275 |
1285 | [ Exposed=Window ]
1286 | interface mixin QuicReadableStream {
1287 | readonly attribute boolean readable;
1288 | readonly attribute unsigned long readableAmount;
1289 | readonly attribute Promise<QuicStreamAbortInfo> readingAborted;
1290 | QuicStreamReadResult readInto (Uint8Array data);
1291 | void abortReading (QuicStreamAbortInfo abortInfo);
1292 | Promise<void> waitForReadable(unsigned long amount);
1293 | };
1294 |
1295 | The QuicReadableStream will initialize with
1298 | the following:
QuicReadableStream.true.readable of type boolean,
1312 | readonlyThe readable
1315 | attribute represents whether data can be read from the QuicReadableStream.
1316 | On getting, it MUST return the value of the
1317 | QuicReadableStream's [[\Readable]] slot.
1318 |
readableAmount of type unsigned
1320 | long, readonlyThe readableAmount
1323 | attribute represents the number of bytes buffered for access by
1324 | readInto but that, as of the last time the event loop
1325 | started executing a task, had not yet been read. This does not include
1326 | framing overhead incurred by the protocol, or buffers associated with
1327 | the network hardware. On getting, it MUST
1328 | return the value of the QuicReadableStream's
1329 | [[\ReadableAmount]] internal slot.
readingAborted of type QuicStreamAbortInfo
1332 | readonlyThe readingAborted
1335 | attribute represents a promise that resolves when the
1336 | RST_STREAM frame is received from the QuicWritableStream.
1337 | When the stream receives a RST_STREAM frame from its corresponding
1338 | QuicWritableStream, the user agent
1339 | MUST run the following:
1340 |
QuicReadableStream
1342 | objectfalse.QuicTransportBase,
1346 | which the stream was created from.
1347 | QuicStreamAbortInfo with errorCode
1351 | set to the value of the errror code from the RST_STREAM frame.readIntoReads from the QuicReadableStream into the buffer specified
1363 | by the first argument and returns QuicStreamReadResult.
1364 | When the readInto method is called, the user agent
1365 | MUST run the following steps:
QuicReadableStream object
1368 | on which readInto is invoked.false,
1370 | throw an InvalidStateError, then abort these steps.QuicStreamReadResult
1373 | to be returned.amount set to 0 and finished set to
1376 | true and abort these steps.amount to the size of
1381 | data in bytes.finished to true.false.QuicTransportBase,
1389 | which the stream was created from.
1390 | finished to false.| Parameter | 1400 |Type | 1401 |Nullable | 1402 |Optional | 1403 |Description | 1404 |
|---|---|---|---|---|
| data | 1407 |Uint8Array |
1408 | ✘ | 1410 |✘ | 1412 |1413 | |
QuicStreamReadResult
1418 | abortReadingA hard shutdown of the QuicReadableStream. It may be called
1423 | regardless of whether the QuicReadableStream object
1424 | was created by the local or remote peer. When the abortReading()
1425 | method is called, the user agent MUST
1426 | run the following steps:
QuicReadableStream object
1429 | which is about to abort reading.false,
1431 | throw an InvalidStateError and abort these steps.false.QuicTransportBase,
1435 | which the stream was created from.
1436 | | Parameter | 1446 |Type | 1447 |Nullable | 1448 |Optional | 1449 |Description | 1450 |
|---|---|---|---|---|
| abortInfo | 1453 |QuicStreamAbortInfo |
1454 | ✘ | 1456 |✘ | 1458 |1459 | |
void
1464 | waitForReadablewaitForReadable waits for data to become available, or
1469 | for the QuicReadableStream to be finished reading. It
1470 | resolves the promise when the data queued in the read buffer
1471 | increases above the amount provided as an argument or when a STREAM frame
1472 | with the FIN bit set has been received. If waitForReadable
1473 | is called multiple times, multiple promises could be resolved.
1474 | The Promise will be rejected with a newly created
1475 | InvalidStateError if the stream's
1476 | [[\Readable]] slot transitions from true to false and the promise
1477 | isn't settled. When the waitForReadable method is
1478 | called, the user agent MUST
1479 | run the following steps:
QuicReadableStream
1482 | on which waitForReadable is invoked.false, reject p with a
1486 | newly created InvalidStateError and abort
1487 | these steps.undefined when
1490 | any of the following conditions are met:
1491 | | Parameter | 1505 |Type | 1506 |Nullable | 1507 |Optional | 1508 |Description | 1509 |
|---|---|---|---|---|
| amount | 1512 |unsigned long |
1513 | ✘ | 1515 |✘ | 1517 |1518 | |
Promise<void>
1523 |
1533 | [ Exposed=Window ]
1534 | interface QuicStream {
1535 | readonly attribute unsigned long long streamId;
1536 | readonly attribute QuicTransportBase transport;
1537 | };
1538 |
1539 | streamId of type unsigned long long, readonlyThe readonly attribute referring to the ID of the
1547 | QuicStream object.
transport of type QuicTransportBase, readonlyThe readonly attribute referring to the related QuicTransportBase object.
1562 | [ Exposed=Window ]
1563 | interface QuicBidirectionalStream : QuicStream {
1564 | };
1565 | QuicBidirectionalStream includes QuicWritableStream;
1566 | QuicBidirectionalStream includes QuicReadableStream;
1567 |
1568 |
1574 | [ Exposed=Window ]
1575 | interface QuicSendStream : QuicStream {
1576 | };
1577 | QuicSendStream includes QuicWritableStream;
1578 |
1579 |
1585 | [ Exposed=Window ]
1586 | interface QuicReceiveStream : QuicStream {
1587 | };
1588 | QuicReceiveStream includes QuicReadableStream;
1589 |
1590 | The QuicStreamWriteParameters dictionary includes information
1595 | relating to the data to be written with QuicWritableStream.write.
dictionary QuicStreamWriteParameters {
1598 | Uint8Array data;
1599 | boolean finished = false;
1600 | };
1601 |
1602 | data of type Uint8Array.The data to be written.
1610 |finished of type boolean.Set to true if this is the last data to be written.
1615 | This will result in a STREAM frame with the FIN bit set.
The QuicStreamReadResult dictionary includes information
1624 | relating to the result returned from readInto.
dictionary QuicStreamReadResult {
1627 | unsigned long amount;
1628 | boolean finished = false;
1629 | };
1630 |
1631 | amount of type unsigned long.The amount of data read in bytes.
1639 |finished of type boolean.Set to true if the QuicReadableStream has
1644 | finished reading.
The QuicStreamAbortInfo dictionary includes information
1653 | relating to the error code for aborting a QUIC stream. This could be used either
1654 | in a RST_STREAM frame or STOP_SENDING frame.
dictionary QuicStreamAbortInfo {
1657 | unsigned short errorCode = 0;
1658 | };
1659 |
1660 | errorCode of type unsigned short.The error code used in the RST_STREAM or STOP_SENDING frame. 1668 | The default value of 0 means "STOPPING."
1669 |This section is non-normative; it specifies no new behaviour, but 1678 | instead summarizes information already present in other parts of the 1679 | specification.
1680 | 1681 |The fact that communication is taking place cannot be hidden from 1684 | adversaries that can observe the network, so this has to be regarded as 1685 | public information.
1686 |Since the QUIC protocol utilizes a cryptographic negotiation based on 1687 | TLS 1.3 [[TLS13]] in order to encrypt communications, it provides confidentiality.
1688 |The following events fire on QuicTransportBase objects:
| Event name | 1697 |Interface | 1698 |Fired when... | 1699 |
|---|---|---|
error |
1704 | ErrorEvent |
1705 | The QuicTransportBase object has encountered an error. |
1706 |
statechange |
1709 | Event |
1710 | The QuicTransportState changed. |
1711 |
receivestream |
1714 | ReceiveStreamEvent |
1715 | A new QuicReceiveStream is dispatched to the
1716 | script in response to the remote peer creating a send only QUIC stream and
1717 | sending data on it. Prior to receivestream
1718 | firing, the QuicReceiveStream is added to
1719 | QuicTransportBase's [[\QuicTransportReadableStreams]]
1720 | internal slot. |
1721 |
bidirectionalstream |
1724 | BidirectionalStreamEvent |
1725 | A new QuicBidirectionalStream is dispatched to the
1726 | script in response to the remote peer creating a bidirectional QUIC stream and
1727 | sending data on it. Prior to bidirectionalstream
1728 | firing, the QuicBidirectionalStream is added to the
1729 | QuicTransportBase's [[\QuicTransportReadableStreams]]
1730 | and [[\QuicTransportWritableStreams]] internal slots. |
1731 |
Unreliable delivery can be achieved by creating many streams with retransmissions disabled, 1740 | each transporting a single small message.
1741 |
1742 | let quic = getQuicTransport();
1743 | let messages = getMessages();
1744 | for (msg in messages) {
1745 | quic.createSendStream({disableRetransmissions: true}).write({data: msg, finished: true});
1746 | }
1747 |
1748 | Sending a buffer of QUIC datagrams can be achieved by using the
1752 | sendDatagram and readyToSendDatagram methods. In
1753 | the following example datagrams are only sent if the
1754 | QuicTransport is ready to send, however the sending is not
1755 | blocked on the ACK promise returned from sendDatagram (these are
1756 | ignored in this example).
1758 | const quic = getQuicTransport();
1759 | const datagrams = getDatagramsToSend();
1760 | datagrams.forEach((datagram) => {
1761 | await quic.readyToSendDatagram();
1762 | quic.sendDatagram(datagram);
1763 | });
1764 |
1765 | Sending QUIC datagrams at a fixed rate regardless if the transport is ready to
1769 | send can be achieved by simply using sendDatagram and not using
1770 | the readyToSendDatagram method. More complex scenarios can utilize
1771 | the readyToSendDatagram method.
1773 | // Sends datagrams every 100 ms.
1774 | const quic = getQuicTransport();
1775 | setInterval(() => {
1776 | quic.sendDatagram(createDatagram());
1777 | }, 100);
1778 |
1779 | Receiving QUIC datagrams can be achieved by using the
1783 | receiveDatagrams method, remembering to check for null values
1784 | indicating that packets are not being processed quickly enough.
1785 |
1787 | const quic = getQuicTransport();
1788 | const datagrams = await quic.receiveDatagrams();
1789 | for (let data of datagrams) {
1790 | if (data == null) {
1791 | // Log that datagrams were lost. Look into making the event handler faster
1792 | // or reducing the send rate of the remote side.
1793 | } else {
1794 | // Process the data
1795 | }
1796 | };
1797 |
1798 | This section will be removed before publication.
1803 |The editors wish to thank the Working Group chairs and Team Contact, 1807 | Harald Alvestrand, Stefan Håkansson, Bernard Aboba and Dominique 1808 | Hazaël-Massieux, for their support. Contributions to this 1809 | specification were provided by Robin Raymond.
1810 |The QuicTransport and QuicStream objects
1811 | were initially described in the W3C ORTC CG,
1812 | and have been adapted for use in this specification.