@fails-components/webtransport 0.0.8 → 0.1.0-macarmbuild.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,23 @@
1
+ /* eslint-disable no-undef */
2
+ import { expect } from 'chai'
3
+ import { Http3EventLoop } from '../lib/event-loop.js'
4
+
5
+ describe('event-loop', () => {
6
+ beforeEach(() => {
7
+ if (Http3EventLoop.globalLoop != null) {
8
+ // shut down loop, otherwise we have to wait for
9
+ // it to time out which takes a long time.
10
+ Http3EventLoop.globalLoop.shutdownEventLoop()
11
+ }
12
+ })
13
+
14
+ it('should start and stop the event loop', () => {
15
+ expect(Http3EventLoop.globalLoop).to.be.null
16
+
17
+ Http3EventLoop.createGlobalEventLoop()
18
+ expect(Http3EventLoop.globalLoop).to.not.be.null
19
+
20
+ Http3EventLoop.globalLoop?.shutdownEventLoop()
21
+ expect(Http3EventLoop.globalLoop).to.be.null
22
+ })
23
+ })
@@ -53,6 +53,7 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
53
53
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
54
54
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
55
55
 
56
+ // @ts-expect-error node-forge has no types and @types/node-forge do not include oids
56
57
  import forge from 'node-forge'
57
58
  import { webcrypto as crypto } from 'crypto'
58
59
  import { X509Certificate } from 'crypto'
@@ -62,7 +63,7 @@ const { pki, asn1, oids } = forge
62
63
  /**
63
64
  * Converts an X.509 subject or issuer to an ASN.1 RDNSequence.
64
65
  *
65
- * @param obj the subject or issuer (distinguished name).
66
+ * @param {any} obj the subject or issuer (distinguished name).
66
67
  *
67
68
  * @return the ASN.1 RDNSequence.
68
69
  */
@@ -110,18 +111,17 @@ function _dnToAsn1(obj) {
110
111
  return rval
111
112
  }
112
113
 
114
+ const jan_1_1950 = new Date('1950-01-01T00:00:00Z')
115
+ const jan_1_2050 = new Date('2050-01-01T00:00:00Z')
113
116
  // taken from node-forge almost not modified
114
117
  /**
115
118
  * Converts a Date object to ASN.1
116
119
  * Handles the different format before and after 1st January 2050
117
120
  *
118
- * @param date date object.
121
+ * @param {Date} date date object.
119
122
  *
120
123
  * @return the ASN.1 object representing the date.
121
124
  */
122
-
123
- const jan_1_1950 = new Date('1950-01-01T00:00:00Z')
124
- const jan_1_2050 = new Date('2050-01-01T00:00:00Z')
125
125
  function _dateToAsn1(date) {
126
126
  if (date >= jan_1_1950 && date < jan_1_2050) {
127
127
  return asn1.create(
@@ -144,8 +144,8 @@ function _dateToAsn1(date) {
144
144
  /**
145
145
  * Convert signature parameters object to ASN.1
146
146
  *
147
- * @param {String} oid Signature algorithm OID
148
- * @param params The signature parametrs object
147
+ * @param {string} oid Signature algorithm OID
148
+ * @param {any} params The signature parameters object
149
149
  * @return ASN.1 object representing signature parameters
150
150
  */
151
151
  function _signatureParametersToAsn1(oid, params) {
@@ -217,7 +217,7 @@ function _signatureParametersToAsn1(oid, params) {
217
217
  /**
218
218
  * Gets the ASN.1 TBSCertificate part of an X.509v3 certificate.
219
219
  *
220
- * @param cert the certificate.
220
+ * @param {any} cert the certificate.
221
221
  *
222
222
  * @return the asn1 TBSCertificate.
223
223
  */
@@ -319,6 +319,10 @@ function getTBSCertificate(cert) {
319
319
  // because serial numbers use ones' complement notation
320
320
  // this RFC in section 4.1.2.2 requires serial numbers to be positive
321
321
  // http://www.ietf.org/rfc/rfc5280.txt
322
+ /**
323
+ * @param {string} hexString
324
+ * @returns
325
+ */
322
326
  function toPositiveHex(hexString) {
323
327
  let mostSiginficativeHexAsInt = parseInt(hexString[0], 16)
324
328
  if (mostSiginficativeHexAsInt < 8) {
@@ -330,6 +334,18 @@ function toPositiveHex(hexString) {
330
334
  }
331
335
 
332
336
  // the next is an edit of the selfsigned function reduced to the function necessary for webtransport
337
+ /**
338
+ * @typedef {object} Certificate
339
+ * @property {string} public
340
+ * @property {string} private
341
+ * @property {string} cert
342
+ * @property {Uint8Array} hash
343
+ * @property {string} fingerprint
344
+ *
345
+ * @param {*} attrs
346
+ * @param {*} options
347
+ * @returns {Promise<Certificate | null>}
348
+ */
333
349
  export async function generateWebTransportCertificate(attrs, options) {
334
350
  try {
335
351
  let keyPair = await crypto.subtle.generateKey(
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Read a stream contents to the end and return it
3
+ *
4
+ * @template T
5
+ * @param {ReadableStream<T>} readable
6
+ * @param {number} [expected]
7
+ * @returns
8
+ */
9
+ export async function readStream(readable, expected) {
10
+ const reader = readable.getReader()
11
+
12
+ try {
13
+ /** @type {T[]} */
14
+ const output = []
15
+
16
+ while (true) {
17
+ const { done, value } = await reader.read()
18
+
19
+ if (done) {
20
+ break
21
+ }
22
+
23
+ if (value != null) {
24
+ output.push(value)
25
+ }
26
+
27
+ if (expected != null && output.length === expected) {
28
+ break
29
+ }
30
+ }
31
+
32
+ return output
33
+ } finally {
34
+ reader.releaseLock()
35
+ }
36
+ }
@@ -0,0 +1,25 @@
1
+
2
+ /**
3
+ * @template T
4
+ * @param {ReadableStream<T>} readableStream
5
+ * @returns {Promise<T>}
6
+ */
7
+ export async function getReaderValue (readableStream) {
8
+ const reader = readableStream.getReader()
9
+
10
+ try {
11
+ const { done, value } = await reader.read()
12
+
13
+ if (done) {
14
+ throw new Error('Stream ended')
15
+ }
16
+
17
+ if (!value) {
18
+ throw new Error('Stream value was undefined')
19
+ }
20
+
21
+ return value
22
+ } finally {
23
+ reader.releaseLock()
24
+ }
25
+ }
@@ -0,0 +1,58 @@
1
+ import { generateWebTransportCertificate } from './certificate.js'
2
+ import { Http3Server } from '../../lib/index.js'
3
+
4
+ export async function createServer() {
5
+ const attrs = [
6
+ { shortName: 'C', value: 'DE' },
7
+ { shortName: 'ST', value: 'Berlin' },
8
+ { shortName: 'L', value: 'Berlin' },
9
+ { shortName: 'O', value: 'WebTransport Test Server' },
10
+ { shortName: 'CN', value: '127.0.0.1' }
11
+ ]
12
+
13
+ const certificate = await generateWebTransportCertificate(attrs, {
14
+ days: 13
15
+ })
16
+
17
+ if (certificate == null) {
18
+ throw new Error('Certificate generation failed')
19
+ }
20
+
21
+ const server = new Http3Server({
22
+ port: 0,
23
+ host: '127.0.0.1',
24
+ secret: 'mysecret',
25
+ cert: certificate.cert, // unclear if it is the correct format
26
+ privKey: certificate.private
27
+ })
28
+
29
+ return {
30
+ server,
31
+ certificate
32
+ }
33
+ }
34
+
35
+ /**
36
+ * @param {import('../../lib/server.js').Http3Server} server
37
+ * @param {string} path
38
+ */
39
+ export async function getServerSession(server, path) {
40
+ const sessionStream = await server.sessionStream(path)
41
+ const sessionReader = sessionStream.getReader()
42
+
43
+ try {
44
+ const { done, value } = await sessionReader.read()
45
+
46
+ if (done) {
47
+ throw new Error('Server is gone')
48
+ }
49
+
50
+ if (!value) {
51
+ throw new Error('Session was undefined')
52
+ }
53
+
54
+ return value
55
+ } finally {
56
+ sessionReader.releaseLock()
57
+ }
58
+ }
@@ -0,0 +1,18 @@
1
+
2
+ /**
3
+ * Write contents to a stream and close it
4
+ *
5
+ * @template T
6
+ * @param {WritableStream<T>} writable
7
+ * @param {T[]} input
8
+ * @returns
9
+ */
10
+ export async function writeStream (writable, input) {
11
+ const writer = writable.getWriter()
12
+
13
+ for (const buf of input) {
14
+ await writer.write(buf)
15
+ }
16
+
17
+ await writer.close()
18
+ }
@@ -0,0 +1,9 @@
1
+ import { Http3EventLoop } from '../lib/event-loop.js'
2
+
3
+ after(async () => {
4
+ if (Http3EventLoop.globalLoop != null) {
5
+ // shut down loop, otherwise we have to wait for
6
+ // it to time out which takes a long time.
7
+ Http3EventLoop.globalLoop.shutdownEventLoop()
8
+ }
9
+ })
package/test/test.js CHANGED
@@ -4,8 +4,8 @@
4
4
 
5
5
  // this file runs various tests
6
6
 
7
- import { generateWebTransportCertificate } from './certificate.js'
8
- import { Http3Server, WebTransport, testcheck } from '../src/webtransport.js'
7
+ import { generateWebTransportCertificate } from './fixtures/certificate.js'
8
+ import { Http3Server, WebTransport, testcheck } from '../lib/index.js'
9
9
  import { echoTestsConnection, runEchoServer } from './testsuite.js'
10
10
 
11
11
  async function run() {
@@ -17,7 +17,28 @@ async function run() {
17
17
  console.log('global event loop gone, everything alright')
18
18
  process.exit(0)
19
19
  }
20
- }, 40 * 1000)
20
+ }, 50 * 1000)
21
+ console.log('try connecting to server that does not exist')
22
+ const badClient = new WebTransport('https://127.0.0.1:49823/echo', {
23
+ serverCertificateHashes: [
24
+ {
25
+ algorithm: 'sha-256',
26
+ value: Buffer.from(
27
+ 'a589bf4f98a0158aa890328d5d3f519b9e2a5b1e61b09eb10b7a9be0e79bf148',
28
+ 'hex'
29
+ )
30
+ }
31
+ ]
32
+ })
33
+ await badClient.ready
34
+ .then(() => {
35
+ console.error('Successfully connected to a non-running server?!')
36
+ process.exit(1)
37
+ })
38
+ .catch(() => {
39
+ console.log('Did not connect to non-running server')
40
+ })
41
+
21
42
  console.log('start generating self signed certificate')
22
43
 
23
44
  const attrs = [
@@ -32,6 +53,10 @@ async function run() {
32
53
  days: 13
33
54
  })
34
55
 
56
+ if (certificate == null) {
57
+ throw new Error('Certificate generation failed')
58
+ }
59
+
35
60
  console.log('start Http3Server and startup echo tests')
36
61
  // now ramp up the server
37
62
  const http3server = new Http3Server({
@@ -52,6 +77,7 @@ async function run() {
52
77
 
53
78
  const url = 'https://127.0.0.1:8080/echo'
54
79
 
80
+ /** @type {import('../lib/dom').WebTransport | null} */
55
81
  let client = new WebTransport(url, {
56
82
  serverCertificateHashes: [{ algorithm: 'sha-256', value: certificate.hash }]
57
83
  })
package/test/testsuite.js CHANGED
@@ -2,6 +2,9 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
 
5
+ /**
6
+ * @param {import('../lib/dom').WebTransport} session
7
+ */
5
8
  export async function incomingBidirectionalEchoTest(session) {
6
9
  try {
7
10
  const bidiReader = session.incomingBidirectionalStreams.getReader()
@@ -24,6 +27,9 @@ export async function incomingBidirectionalEchoTest(session) {
24
27
  }
25
28
  }
26
29
 
30
+ /**
31
+ * @param {import('../lib/dom').WebTransport} session
32
+ */
27
33
  export async function outgoingBidirectionalEchoTest(session) {
28
34
  try {
29
35
  const mybidistream = await session.createBidirectionalStream()
@@ -33,6 +39,9 @@ export async function outgoingBidirectionalEchoTest(session) {
33
39
  }
34
40
  }
35
41
 
42
+ /**
43
+ * @param {import('../lib/dom').WebTransport} session
44
+ */
36
45
  export async function unidirectionalEchoTest(session) {
37
46
  try {
38
47
  const unidiReader = session.incomingUnidirectionalStreams.getReader()
@@ -56,6 +65,9 @@ export async function unidirectionalEchoTest(session) {
56
65
  }
57
66
  }
58
67
 
68
+ /**
69
+ * @param {import('../lib/dom').WebTransport} session
70
+ */
59
71
  export async function datagramEchoTest(session) {
60
72
  try {
61
73
  session.datagrams.readable.pipeTo(session.datagrams.writable)
@@ -64,6 +76,9 @@ export async function datagramEchoTest(session) {
64
76
  }
65
77
  }
66
78
 
79
+ /**
80
+ * @param {import('../lib').Http3Server} server
81
+ */
67
82
  export async function runEchoServer(server) {
68
83
  try {
69
84
  const sessionStream = await server.sessionStream('/echo')
@@ -99,6 +114,10 @@ export async function runEchoServer(server) {
99
114
  }
100
115
  }
101
116
 
117
+ /**
118
+ * @param {ArrayLike<any>} array1
119
+ * @param {ArrayLike<any>} array2
120
+ */
102
121
  function testArraysEqual(array1, array2) {
103
122
  if (array1.length !== array2.length)
104
123
  throw new Error('Array not equal in length')
@@ -107,6 +126,9 @@ function testArraysEqual(array1, array2) {
107
126
  }
108
127
  }
109
128
 
129
+ /**
130
+ * @param {import('../lib/dom').WebTransport} transport
131
+ */
110
132
  export async function echoTestsConnection(transport) {
111
133
  // some echo tests for testing the webtransport library, not for production
112
134
  const stream = await transport.createBidirectionalStream()
@@ -0,0 +1,127 @@
1
+ /* eslint-disable no-undef */
2
+ import { createServer } from './fixtures/server.js'
3
+ import { getReaderValue } from './fixtures/reader-value.js'
4
+ import { WebTransport } from '../lib/index.js'
5
+ import { expect } from 'chai'
6
+ import { readStream } from './fixtures/read-stream.js'
7
+ import { writeStream } from './fixtures/write-stream.js'
8
+ import { defer } from '../lib/utils.js'
9
+
10
+ /**
11
+ * @template T
12
+ * @typedef {import('../lib/types').Deferred<T>} Deferred<T>
13
+ */
14
+
15
+ const SERVER_PATH = '/unidirectional-streams'
16
+
17
+ describe('unidirectional streams', function () {
18
+ /** @type {import('../lib/server').Http3Server} */
19
+ let server
20
+ /** @type {import('./fixtures/certificate.js').Certificate} */
21
+ let certificate
22
+ /** @type {import('../lib/dom').WebTransport | undefined} */
23
+ let client
24
+ /** @type {string} */
25
+ let url
26
+
27
+ beforeEach(async () => {
28
+ ;({ server, certificate } = await createServer())
29
+ server.startServer()
30
+ await server.ready
31
+
32
+ const address = server.address()
33
+
34
+ if (address == null || address.port == null) {
35
+ throw new Error('No address')
36
+ }
37
+
38
+ url = `https://${address.host}:${address.port}`
39
+ })
40
+
41
+ afterEach(async () => {
42
+ if (client != null) {
43
+ client.close()
44
+ }
45
+
46
+ if (server != null) {
47
+ server.stopServer()
48
+ await server.closed
49
+ }
50
+ })
51
+
52
+ it('sends data over an outgoing unidirectional stream', async () => {
53
+ this.timeout(200)
54
+ /** @type {Deferred<Uint8Array[]>} */
55
+ const serverData = defer()
56
+
57
+ // server context - waits for the client to open a bidi stream and pipes it back to them
58
+ Promise.resolve().then(async () => {
59
+ const session = await getReaderValue(server.sessionStream(SERVER_PATH))
60
+ const stream = await getReaderValue(session.incomingUnidirectionalStreams)
61
+
62
+ const output = await readStream(stream)
63
+ serverData.resolve(output)
64
+ })
65
+
66
+ // client context - connects to the server, opens a bidi stream, sends some data and reads the response
67
+ client = new WebTransport(`${url}${SERVER_PATH}`, {
68
+ serverCertificateHashes: [
69
+ {
70
+ algorithm: 'sha-256',
71
+ value: certificate.hash
72
+ }
73
+ ]
74
+ })
75
+ await client.ready
76
+
77
+ const input = [
78
+ Uint8Array.from([0, 1, 2, 3, 4]),
79
+ Uint8Array.from([5, 6, 7, 8, 9]),
80
+ Uint8Array.from([10, 11, 12, 13, 14])
81
+ ]
82
+
83
+ const stream = await client.createUnidirectionalStream()
84
+ await writeStream(stream, input)
85
+
86
+ const received = await serverData.promise
87
+ expect(received).to.deep.equal(
88
+ input,
89
+ 'Server did not receive the same bytes we sent'
90
+ )
91
+ })
92
+
93
+ it('receives data over an incoming unidirectional stream', async () => {
94
+ this.timeout(200)
95
+ const input = [
96
+ Uint8Array.from([0, 1, 2, 3, 4]),
97
+ Uint8Array.from([5, 6, 7, 8, 9]),
98
+ Uint8Array.from([10, 11, 12, 13, 14])
99
+ ]
100
+
101
+ // server context - waits for the client to connect, opens a bidi stream, sends some data and reads the response
102
+ Promise.resolve().then(async () => {
103
+ const session = await getReaderValue(server.sessionStream(SERVER_PATH))
104
+ const stream = await session.createUnidirectionalStream()
105
+
106
+ await writeStream(stream, input)
107
+ })
108
+
109
+ // client context - waits for the server to open a bidi stream then pipes it back to them
110
+ client = new WebTransport(`${url}${SERVER_PATH}`, {
111
+ serverCertificateHashes: [
112
+ {
113
+ algorithm: 'sha-256',
114
+ value: certificate.hash
115
+ }
116
+ ]
117
+ })
118
+ await client.ready
119
+
120
+ const stream = await getReaderValue(client.incomingUnidirectionalStreams)
121
+ const received = await readStream(stream)
122
+ expect(received).to.deep.equal(
123
+ input,
124
+ 'Did not receive the same bytes we sent'
125
+ )
126
+ })
127
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "compilerOptions": {
3
+ "strict": true,
4
+ // project options
5
+ "outDir": "dist",
6
+ "allowJs": true,
7
+ "checkJs": true,
8
+ "target": "ES2020",
9
+ "module": "ES2022",
10
+ "lib": ["ES2021", "ES2021.Promise", "ES2021.String", "ES2020.BigInt", "DOM", "DOM.Iterable"],
11
+ "noEmit": false,
12
+ "noEmitOnError": true,
13
+ "emitDeclarationOnly": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "incremental": true,
17
+ "composite": true,
18
+ "isolatedModules": true,
19
+ "removeComments": false,
20
+ "sourceMap": true,
21
+ // module resolution
22
+ "esModuleInterop": true,
23
+ "moduleResolution": "node",
24
+ // linter checks
25
+ "noImplicitReturns": false,
26
+ "noFallthroughCasesInSwitch": true,
27
+ "noUnusedLocals": true,
28
+ "noUnusedParameters": false,
29
+ // advanced
30
+ "importsNotUsedAsValues": "error",
31
+ "forceConsistentCasingInFileNames": true,
32
+ "skipLibCheck": true,
33
+ "stripInternal": true,
34
+ "resolveJsonModule": true
35
+ },
36
+ "include": [
37
+ "lib",
38
+ "test"
39
+ ]
40
+ }