@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.
- package/CMakeLists.txt +109 -75
- package/build.js +32 -8
- package/lib/client.js +163 -0
- package/lib/dom.ts +95 -0
- package/lib/event-loop.js +79 -0
- package/lib/index.js +32 -0
- package/lib/native.js +25 -0
- package/lib/server.js +164 -0
- package/lib/session.js +422 -0
- package/lib/stream.js +325 -0
- package/lib/transport.js +42 -0
- package/lib/types.ts +173 -0
- package/lib/utils.js +23 -0
- package/lib/webtransport.js +114 -0
- package/package.json +15 -6
- package/readme.md +28 -5
- package/src/http3client.h +0 -1
- package/src/http3eventloop.cc +52 -0
- package/src/http3eventloop.h +22 -1
- package/src/http3server.cc +4 -1
- package/src/http3server.h +8 -0
- package/src/http3serversession.h +1 -1
- package/src/http3wtsessionvisitor.h +1 -1
- package/test/bidirectional-streams.spec.js +137 -0
- package/test/datagrams.spec.js +131 -0
- package/test/echoclient.js +10 -6
- package/test/echoserver.js +6 -8
- package/test/event-loop.specoff.js +23 -0
- package/test/{certificate.js → fixtures/certificate.js} +24 -8
- package/test/fixtures/read-stream.js +36 -0
- package/test/fixtures/reader-value.js +25 -0
- package/test/fixtures/server.js +58 -0
- package/test/fixtures/write-stream.js +18 -0
- package/test/suite.specoff.js +9 -0
- package/test/test.js +29 -3
- package/test/testsuite.js +22 -0
- package/test/unidirectional-streams.spec.js +127 -0
- package/tsconfig.json +40 -0
- package/src/webtransport.js +0 -870
package/package.json
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fails-components/webtransport",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.1.0-macarmbuild.5",
|
|
4
4
|
"description": "A component to add webtransport support (server and client) to node.js using libquiche",
|
|
5
|
-
"main": "
|
|
6
|
-
"module": "
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"module": "lib/index.js",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=16"
|
|
9
9
|
},
|
|
10
10
|
"type": "module",
|
|
11
|
+
"types": "./dist/lib/index.d.ts",
|
|
11
12
|
"scripts": {
|
|
12
13
|
"start": "node test/echoserver.js",
|
|
13
|
-
"test": "
|
|
14
|
+
"test": "mocha test/*.spec.js",
|
|
15
|
+
"oldtest": "node test/test.js",
|
|
14
16
|
"install": "node build.js install",
|
|
15
17
|
"build": "node build.js build",
|
|
16
18
|
"rebuild": "node build.js rebuild",
|
|
17
19
|
"rebuild-debug": "node build.js rebuild-debug -D",
|
|
18
|
-
"build-debug": "node build.js build-debug"
|
|
20
|
+
"build-debug": "node build.js build-debug",
|
|
21
|
+
"types": "tsc"
|
|
19
22
|
},
|
|
20
23
|
"repository": {
|
|
21
24
|
"type": "git",
|
|
@@ -38,6 +41,10 @@
|
|
|
38
41
|
"devDependencies": {
|
|
39
42
|
"@babel/core": "^7.17.10",
|
|
40
43
|
"@babel/eslint-parser": "^7.17.0",
|
|
44
|
+
"@types/chai": "^4.3.3",
|
|
45
|
+
"@types/mocha": "^10.0.0",
|
|
46
|
+
"@types/node": "^18.11.5",
|
|
47
|
+
"chai": "^4.3.6",
|
|
41
48
|
"cmake-js": "^6.3.2",
|
|
42
49
|
"eslint": "^7.32.0",
|
|
43
50
|
"eslint-config-prettier": "^7.2.0",
|
|
@@ -47,8 +54,10 @@
|
|
|
47
54
|
"eslint-plugin-prettier": "^3.4.1",
|
|
48
55
|
"eslint-plugin-promise": "^4.3.1",
|
|
49
56
|
"eslint-plugin-standard": "^4.1.0",
|
|
57
|
+
"mocha": "^10.1.0",
|
|
50
58
|
"node-forge": "^1.3.1",
|
|
51
|
-
"prettier": "^2.4.1"
|
|
59
|
+
"prettier": "^2.4.1",
|
|
60
|
+
"typescript": "^4.8.4"
|
|
52
61
|
},
|
|
53
62
|
"binary": {
|
|
54
63
|
"napi_versions": [
|
package/readme.md
CHANGED
|
@@ -40,23 +40,46 @@ In case of github packages, please add to your `.npmrc` file
|
|
|
40
40
|
```
|
|
41
41
|
In this case you need to be authenticated against github.
|
|
42
42
|
|
|
43
|
-
The package provides prebuild binaries for `windows`, `linux` and `macos` for the platform `x64` and `ia32` (only windows).
|
|
43
|
+
The package provides prebuild binaries for `windows`, `linux` and `macos` for the platform `x64` and `ia32` (only windows).
|
|
44
44
|
Other platforms may be possible via crosscompiling in the github actions, if someone needs this, PRs are welcome.
|
|
45
45
|
|
|
46
46
|
Of course you can also build the binary on your system.
|
|
47
47
|
If you are running the compiling install as root, you need to use `--unsafe-perm` as flag.
|
|
48
|
-
Installing the package without prebuild requires a full building environment including clang-9, perl6, python, golang, ninja-build, icu. See the `Dockerfile` or `Dockerfile.development` for required debian packages.
|
|
49
|
-
This should work for Windows, linux and Mac OS X.
|
|
48
|
+
Installing the package without prebuild requires a full building environment including clang-9, perl6, python, golang, ninja-build, icu. See the `Dockerfile` or `Dockerfile.development` for required debian packages.
|
|
49
|
+
This should work for Windows, linux and Mac OS X.
|
|
50
50
|
(You may want to check out the building dependencies (especially for windows) for BoringSSl, zlib, abseil on their respective websites).
|
|
51
51
|
|
|
52
|
-
Of course, PR for patches and for compiling instructions and necessary changes are welcome for all possible environments.
|
|
52
|
+
Of course, PR for patches and for compiling instructions and necessary changes are welcome for all possible environments.
|
|
53
53
|
|
|
54
54
|
** Warning the build time takes more than 15 minutes, on windows even longer! (Due to the building of the third party libraries). **
|
|
55
55
|
|
|
56
|
-
In the directory `test` you find a simple echo server code. That answers to a series of WebTransport echos. Furthermore some example browser code and finally a unit test of the library including certificate generation.
|
|
56
|
+
In the directory `test` you find a simple echo server code. That answers to a series of WebTransport echos. Furthermore some example browser code and finally a unit test of the library including certificate generation.
|
|
57
57
|
|
|
58
58
|
When testing remember you might need to start chromium based browser with certain flags to accept your http/3 certificate with errors, e.g.:
|
|
59
59
|
```
|
|
60
60
|
chrome --ignore-certificate-errors-spki-list=FINGERPRINTOFYOURCERTIFICATE --ignore-certificate-errors --v=2 --enable-logging=stderr --origin-to-force-quic-on=192.168.1.50:8080
|
|
61
61
|
```
|
|
62
62
|
of course replace IP and fingerprint of your certificate accordingly.
|
|
63
|
+
|
|
64
|
+
## Specification divergence
|
|
65
|
+
|
|
66
|
+
This module implements parts of the [WebTransport spec](https://datatracker.ietf.org/doc/html/draft-vvv-webtransport-quic-00) but not all of it.
|
|
67
|
+
|
|
68
|
+
The types from the [W3C Working Draft](https://www.w3.org/TR/webtransport/) have been added to [lib/dom.ts](https://github.com/fails-components/webtransport/blob/master/lib/dom.ts) but some fields are commented out.
|
|
69
|
+
|
|
70
|
+
These fields are unimplemented by this module at this time. Some may be implemented in the future, others are legacy fields that may be removed from the spec. PRs are welcome!
|
|
71
|
+
|
|
72
|
+
They are:
|
|
73
|
+
|
|
74
|
+
### WebTransport
|
|
75
|
+
|
|
76
|
+
* [getStats()](https://www.w3.org/TR/webtransport/#dom-webtransport-getstats)
|
|
77
|
+
* [reliability](https://www.w3.org/TR/webtransport/#dom-webtransport-reliability)
|
|
78
|
+
|
|
79
|
+
### WebTransportDatagramDuplexStream
|
|
80
|
+
|
|
81
|
+
* [maxDatagramSize](https://www.w3.org/TR/webtransport/#dom-webtransportdatagramduplexstream-maxdatagramsize)
|
|
82
|
+
* [incomingMaxAge](https://www.w3.org/TR/webtransport/#dom-webtransportdatagramduplexstream-incomingmaxage)
|
|
83
|
+
* [outgoingMaxAge](https://www.w3.org/TR/webtransport/#dom-webtransportdatagramduplexstream-outgoingmaxage)
|
|
84
|
+
* [incomingHighWaterMark](https://www.w3.org/TR/webtransport/#dom-webtransportdatagramduplexstream-incominghighwatermark)
|
|
85
|
+
* [outgoingHighWaterMark](https://www.w3.org/TR/webtransport/#dom-webtransportdatagramduplexstream-outgoinghighwatermark)
|
package/src/http3client.h
CHANGED
package/src/http3eventloop.cc
CHANGED
|
@@ -295,6 +295,17 @@ namespace quic
|
|
|
295
295
|
progress_->Send(&report, 1);
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
void Http3EventLoop::informServerStatus(Http3Server *serverobj, NetworkStatus status, ServerStatusDetails* details)
|
|
299
|
+
{
|
|
300
|
+
struct Http3ProgressReport report;
|
|
301
|
+
report.type = Http3ProgressReport::ServerStatus;
|
|
302
|
+
report.serverobj = serverobj;
|
|
303
|
+
report.status = status;
|
|
304
|
+
report.details = details;
|
|
305
|
+
if (progress_)
|
|
306
|
+
progress_->Send(&report, 1);
|
|
307
|
+
}
|
|
308
|
+
|
|
298
309
|
void Http3EventLoop::freeData(Napi::Env env, void *data, std::string *hint)
|
|
299
310
|
{
|
|
300
311
|
// ok free data is actually using a string object
|
|
@@ -631,6 +642,41 @@ namespace quic
|
|
|
631
642
|
cbsession_.Call({retObj});
|
|
632
643
|
}
|
|
633
644
|
|
|
645
|
+
void Http3EventLoop::processServerStatus(Http3Server *serverobj, NetworkStatus status, ServerStatusDetails * details)
|
|
646
|
+
{
|
|
647
|
+
if (!checkQw()) return;
|
|
648
|
+
HandleScope scope(qw_->Env());
|
|
649
|
+
|
|
650
|
+
Napi::Object objVal = serverobj->getJS()->Value();
|
|
651
|
+
|
|
652
|
+
Napi::Object retObj = Napi::Object::New(qw_->Env());
|
|
653
|
+
retObj.Set("purpose", "ServerStatus");
|
|
654
|
+
retObj.Set("object", objVal);
|
|
655
|
+
Napi::Number portVal = Napi::Number::New(qw_->Env(), details->port);
|
|
656
|
+
retObj.Set("port", portVal);
|
|
657
|
+
Napi::String hostVal = Napi::String::New(qw_->Env(), details->host);
|
|
658
|
+
retObj.Set("host", hostVal);
|
|
659
|
+
|
|
660
|
+
delete details; // we own it and must throw it away
|
|
661
|
+
switch (status)
|
|
662
|
+
{
|
|
663
|
+
case NetError: {
|
|
664
|
+
retObj.Set("status", "error");
|
|
665
|
+
} break;
|
|
666
|
+
case NetListening: {
|
|
667
|
+
retObj.Set("status", "listening");
|
|
668
|
+
} break;
|
|
669
|
+
case NetClose: {
|
|
670
|
+
retObj.Set("status", "close");
|
|
671
|
+
} break;
|
|
672
|
+
default: {
|
|
673
|
+
Napi::Error::New(Env(), "Unknown status from server").ThrowAsJavaScriptException();
|
|
674
|
+
return;
|
|
675
|
+
} break;
|
|
676
|
+
};
|
|
677
|
+
cbtransport_.Call({retObj});
|
|
678
|
+
}
|
|
679
|
+
|
|
634
680
|
void Http3EventLoop::HandleProgressCallback(const Http3ProgressReport *data, size_t count)
|
|
635
681
|
{
|
|
636
682
|
for (int i = 0; i < count; i++)
|
|
@@ -688,6 +734,12 @@ namespace quic
|
|
|
688
734
|
processStream(false, false, cur.sessionobj, cur.stream);
|
|
689
735
|
}
|
|
690
736
|
break;
|
|
737
|
+
case Http3ProgressReport::ServerStatus:
|
|
738
|
+
{
|
|
739
|
+
processServerStatus(cur.serverobj, cur.status, cur.details);
|
|
740
|
+
cur.para = nullptr; // take ownership of the data
|
|
741
|
+
}
|
|
742
|
+
break;
|
|
691
743
|
case Http3ProgressReport::StreamRecvSignal:
|
|
692
744
|
{
|
|
693
745
|
processStreamRecvSignal(cur.streamobj, cur.wtscode, cur.nettask);
|
package/src/http3eventloop.h
CHANGED
|
@@ -47,6 +47,13 @@ namespace quic
|
|
|
47
47
|
streamFinal
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
+
enum NetworkStatus
|
|
51
|
+
{
|
|
52
|
+
NetError,
|
|
53
|
+
NetClose,
|
|
54
|
+
NetListening
|
|
55
|
+
};
|
|
56
|
+
|
|
50
57
|
struct Http3Constructors
|
|
51
58
|
{
|
|
52
59
|
Napi::FunctionReference stream;
|
|
@@ -54,6 +61,11 @@ namespace quic
|
|
|
54
61
|
|
|
55
62
|
};
|
|
56
63
|
|
|
64
|
+
struct ServerStatusDetails {
|
|
65
|
+
std::string host;
|
|
66
|
+
int port;
|
|
67
|
+
};
|
|
68
|
+
|
|
57
69
|
class Http3ProgressReport // actually struct would be a better fit but make napi happy
|
|
58
70
|
{
|
|
59
71
|
public:
|
|
@@ -69,6 +81,7 @@ namespace quic
|
|
|
69
81
|
IncomUniDiStream,
|
|
70
82
|
OutgoBiDiStream,
|
|
71
83
|
OutgoUniDiStream,
|
|
84
|
+
ServerStatus,
|
|
72
85
|
StreamRecvSignal,
|
|
73
86
|
StreamRead,
|
|
74
87
|
StreamWrite,
|
|
@@ -91,6 +104,7 @@ namespace quic
|
|
|
91
104
|
{
|
|
92
105
|
WebTransportSessionError wtecode;
|
|
93
106
|
NetworkTask nettask;
|
|
107
|
+
NetworkStatus status;
|
|
94
108
|
};
|
|
95
109
|
union
|
|
96
110
|
{
|
|
@@ -104,8 +118,11 @@ namespace quic
|
|
|
104
118
|
{
|
|
105
119
|
bool success;
|
|
106
120
|
};
|
|
121
|
+
union {
|
|
107
122
|
|
|
108
|
-
|
|
123
|
+
std::string *para = nullptr; // for session and others, we own it, and must delete it
|
|
124
|
+
ServerStatusDetails * details;
|
|
125
|
+
};
|
|
109
126
|
};
|
|
110
127
|
|
|
111
128
|
class Http3EventLoop : // may be replace char later
|
|
@@ -179,6 +196,8 @@ namespace quic
|
|
|
179
196
|
void informSessionClosed(Http3WTSession *sessionobj, WebTransportSessionError error_code, absl::string_view error_message);
|
|
180
197
|
void informSessionReady(Http3WTSession *sessionobj);
|
|
181
198
|
|
|
199
|
+
void informServerStatus(Http3Server *serverobj, NetworkStatus status, ServerStatusDetails* details);
|
|
200
|
+
|
|
182
201
|
void informAboutStream(bool incom, bool bidir, Http3WTSession *sessionobj, Http3WTStream *stream);
|
|
183
202
|
void informStreamRecvSignal(Http3WTStream *streamobj, WebTransportStreamError error_code, NetworkTask task);
|
|
184
203
|
void informAboutStreamRead(Http3WTStream *streamobj, std::string *data, bool fin);
|
|
@@ -239,6 +258,8 @@ namespace quic
|
|
|
239
258
|
void processSessionClose(Http3WTSession *sessionobj, uint32_t errorcode, const std::string &path);
|
|
240
259
|
void processSessionReady(Http3WTSession *sessionobj);
|
|
241
260
|
|
|
261
|
+
void processServerStatus(Http3Server *serverobj, NetworkStatus status, ServerStatusDetails * details);
|
|
262
|
+
|
|
242
263
|
void processStream(bool incom, bool bidi, Http3WTSession *sessionobj, Http3WTStream *stream);
|
|
243
264
|
void processStreamRecvSignal(Http3WTStream *streamobj, WebTransportStreamError error_code, NetworkTask task);
|
|
244
265
|
void processStreamRead(Http3WTStream *streamobj, std::string *data, bool fin);
|
package/src/http3server.cc
CHANGED
|
@@ -93,9 +93,12 @@ namespace quic
|
|
|
93
93
|
dispatcher_.reset(CreateQuicDispatcher());
|
|
94
94
|
dispatcher_->InitializeWithWriter(new QuicDefaultPacketWriter(fd_));
|
|
95
95
|
std::move(closer).Cancel();
|
|
96
|
+
|
|
97
|
+
eventloop_->informServerStatus(this, NetListening, getStatusDetails());
|
|
96
98
|
return true;
|
|
97
99
|
}
|
|
98
100
|
|
|
101
|
+
eventloop_->informServerStatus(this, NetError, getStatusDetails());
|
|
99
102
|
return false;
|
|
100
103
|
}
|
|
101
104
|
|
|
@@ -113,6 +116,7 @@ namespace quic
|
|
|
113
116
|
QuicUdpSocketApi api;
|
|
114
117
|
api.Destroy(fd_);
|
|
115
118
|
fd_ = -1;
|
|
119
|
+
eventloop_->informServerStatus(this, NetClose, getStatusDetails());
|
|
116
120
|
eventloop_->informUnref(this->getJS()); // must be done on the other thread...
|
|
117
121
|
return true;
|
|
118
122
|
}
|
|
@@ -397,5 +401,4 @@ namespace quic
|
|
|
397
401
|
obj->eventloop_->Schedule(task);
|
|
398
402
|
}
|
|
399
403
|
}
|
|
400
|
-
|
|
401
404
|
}
|
package/src/http3server.h
CHANGED
|
@@ -50,6 +50,7 @@ namespace quic
|
|
|
50
50
|
|
|
51
51
|
void addPath(const Napi::CallbackInfo &info);
|
|
52
52
|
|
|
53
|
+
|
|
53
54
|
static void InitExports(Napi::Env env, Napi::Object exports)
|
|
54
55
|
{
|
|
55
56
|
Napi::Function tplsrv = DefineClass(env, "Http3WebTransportServer",
|
|
@@ -94,6 +95,13 @@ namespace quic
|
|
|
94
95
|
|
|
95
96
|
Http3ServerJS *getJS() { return js_; };
|
|
96
97
|
|
|
98
|
+
ServerStatusDetails *getStatusDetails() {
|
|
99
|
+
ServerStatusDetails* details = new ServerStatusDetails();
|
|
100
|
+
details->host = host_;
|
|
101
|
+
details->port = port_;
|
|
102
|
+
return details;
|
|
103
|
+
}
|
|
104
|
+
|
|
97
105
|
private:
|
|
98
106
|
bool startServerInt();
|
|
99
107
|
bool stopServerInt();
|
package/src/http3serversession.h
CHANGED
|
@@ -222,7 +222,7 @@ namespace quic
|
|
|
222
222
|
void closeInt(int code, std::string &reason)
|
|
223
223
|
{
|
|
224
224
|
std::function<void()> task = [this, code, reason]()
|
|
225
|
-
{ session_->CloseSession(code, reason); };
|
|
225
|
+
{ if (session_) session_->CloseSession(code, reason); };
|
|
226
226
|
eventloop_->Schedule(task);
|
|
227
227
|
}
|
|
228
228
|
|
|
@@ -0,0 +1,137 @@
|
|
|
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 = '/bidirectional-streams'
|
|
16
|
+
|
|
17
|
+
describe('bidirectional 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
|
+
this.timeout(30000) // this can vary depending on the setup and is not an error
|
|
28
|
+
// but it smells like a work around regarding a deeper problem
|
|
29
|
+
|
|
30
|
+
beforeEach(async () => {
|
|
31
|
+
this.timeout(2000)
|
|
32
|
+
;({ server, certificate } = await createServer())
|
|
33
|
+
server.startServer()
|
|
34
|
+
await server.ready
|
|
35
|
+
|
|
36
|
+
const address = server.address()
|
|
37
|
+
|
|
38
|
+
if (address == null || address.port == null) {
|
|
39
|
+
throw new Error('No address')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
url = `https://${address.host}:${address.port}`
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
afterEach(async () => {
|
|
46
|
+
if (client != null) {
|
|
47
|
+
client.close()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (server != null) {
|
|
51
|
+
server.stopServer()
|
|
52
|
+
await server.closed
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('sends and receives data over an outgoing bidirectional stream', async () => {
|
|
57
|
+
this.timeout(200)
|
|
58
|
+
// server context - waits for the client to open a bidi stream and pipes it back to them
|
|
59
|
+
Promise.resolve().then(async () => {
|
|
60
|
+
const session = await getReaderValue(server.sessionStream(SERVER_PATH))
|
|
61
|
+
const bidiStream = await getReaderValue(
|
|
62
|
+
session.incomingBidirectionalStreams
|
|
63
|
+
)
|
|
64
|
+
// redirect input to output
|
|
65
|
+
await bidiStream.readable.pipeTo(bidiStream.writable)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
// client context - connects to the server, opens a bidi stream, sends some data and reads the response
|
|
69
|
+
client = new WebTransport(`${url}${SERVER_PATH}`, {
|
|
70
|
+
serverCertificateHashes: [
|
|
71
|
+
{
|
|
72
|
+
algorithm: 'sha-256',
|
|
73
|
+
value: certificate.hash
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
})
|
|
77
|
+
await client.ready
|
|
78
|
+
|
|
79
|
+
const input = [
|
|
80
|
+
Uint8Array.from([0, 1, 2, 3, 4]),
|
|
81
|
+
Uint8Array.from([5, 6, 7, 8, 9]),
|
|
82
|
+
Uint8Array.from([10, 11, 12, 13, 14])
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
const stream = await client.createBidirectionalStream()
|
|
86
|
+
await writeStream(stream.writable, input)
|
|
87
|
+
|
|
88
|
+
const output = await readStream(stream.readable)
|
|
89
|
+
expect(output).to.deep.equal(
|
|
90
|
+
input,
|
|
91
|
+
'Did not receive the same bytes we sent'
|
|
92
|
+
)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('sends and receives data over an incoming bidirectional stream', async () => {
|
|
96
|
+
this.timeout(200)
|
|
97
|
+
/** @type {Deferred<Uint8Array[]>} */
|
|
98
|
+
const serverData = defer()
|
|
99
|
+
const input = [
|
|
100
|
+
Uint8Array.from([0, 1, 2, 3, 4]),
|
|
101
|
+
Uint8Array.from([5, 6, 7, 8, 9]),
|
|
102
|
+
Uint8Array.from([10, 11, 12, 13, 14])
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
// server context - waits for the client to connect, opens a bidi stream, sends some data and reads the response
|
|
106
|
+
Promise.resolve().then(async () => {
|
|
107
|
+
const session = await getReaderValue(server.sessionStream(SERVER_PATH))
|
|
108
|
+
const stream = await session.createBidirectionalStream()
|
|
109
|
+
|
|
110
|
+
await writeStream(stream.writable, input)
|
|
111
|
+
|
|
112
|
+
const output = await readStream(stream.readable)
|
|
113
|
+
serverData.resolve(output)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
// client context - waits for the server to open a bidi stream then pipes it back to them
|
|
117
|
+
client = new WebTransport(`${url}${SERVER_PATH}`, {
|
|
118
|
+
serverCertificateHashes: [
|
|
119
|
+
{
|
|
120
|
+
algorithm: 'sha-256',
|
|
121
|
+
value: certificate.hash
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
})
|
|
125
|
+
await client.ready
|
|
126
|
+
|
|
127
|
+
const bidiStream = await getReaderValue(client.incomingBidirectionalStreams)
|
|
128
|
+
// redirect input to output
|
|
129
|
+
await bidiStream.readable.pipeTo(bidiStream.writable)
|
|
130
|
+
|
|
131
|
+
const received = await serverData.promise
|
|
132
|
+
expect(received).to.deep.equal(
|
|
133
|
+
input,
|
|
134
|
+
'Did not receive the same bytes we sent'
|
|
135
|
+
)
|
|
136
|
+
})
|
|
137
|
+
})
|
|
@@ -0,0 +1,131 @@
|
|
|
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 = '/datagrams'
|
|
16
|
+
|
|
17
|
+
describe('datagrams', 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('client sends datagrams to the server', async () => {
|
|
53
|
+
this.timeout(200)
|
|
54
|
+
// server context - waits for the client to connect and pipes their datagrams back to them
|
|
55
|
+
Promise.resolve().then(async () => {
|
|
56
|
+
const session = await getReaderValue(server.sessionStream(SERVER_PATH))
|
|
57
|
+
|
|
58
|
+
// redirect input to output
|
|
59
|
+
await session.datagrams.readable.pipeTo(session.datagrams.writable)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// client context - connects to the server, sends some datagrams and reads the response
|
|
63
|
+
client = new WebTransport(`${url}${SERVER_PATH}`, {
|
|
64
|
+
serverCertificateHashes: [
|
|
65
|
+
{
|
|
66
|
+
algorithm: 'sha-256',
|
|
67
|
+
value: certificate.hash
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
})
|
|
71
|
+
await client.ready
|
|
72
|
+
|
|
73
|
+
const input = [
|
|
74
|
+
Uint8Array.from([0, 1, 2, 3, 4]),
|
|
75
|
+
Uint8Array.from([5, 6, 7, 8, 9]),
|
|
76
|
+
Uint8Array.from([10, 11, 12, 13, 14])
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
await writeStream(client.datagrams.writable, input)
|
|
80
|
+
|
|
81
|
+
const output = await readStream(client.datagrams.readable, input.length)
|
|
82
|
+
expect(output).to.deep.equal(
|
|
83
|
+
input,
|
|
84
|
+
'Did not receive the same bytes we sent'
|
|
85
|
+
)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('receives datagrams from the server', async () => {
|
|
89
|
+
this.timeout(200)
|
|
90
|
+
/** @type {Deferred<Uint8Array[]>} */
|
|
91
|
+
const serverData = defer()
|
|
92
|
+
const input = [
|
|
93
|
+
Uint8Array.from([0, 1, 2, 3, 4]),
|
|
94
|
+
Uint8Array.from([5, 6, 7, 8, 9]),
|
|
95
|
+
Uint8Array.from([10, 11, 12, 13, 14])
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
// server context - waits for the client to connect, sends some datagrams and reads the response
|
|
99
|
+
Promise.resolve().then(async () => {
|
|
100
|
+
const session = await getReaderValue(server.sessionStream(SERVER_PATH))
|
|
101
|
+
|
|
102
|
+
await writeStream(session.datagrams.writable, input)
|
|
103
|
+
|
|
104
|
+
const output = await readStream(session.datagrams.readable, input.length)
|
|
105
|
+
|
|
106
|
+
// have to close the session to end the client's datagram stream
|
|
107
|
+
session.close()
|
|
108
|
+
serverData.resolve(output)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
// client context - pipes the server's datagrams back to them
|
|
112
|
+
client = new WebTransport(`${url}${SERVER_PATH}`, {
|
|
113
|
+
serverCertificateHashes: [
|
|
114
|
+
{
|
|
115
|
+
algorithm: 'sha-256',
|
|
116
|
+
value: certificate.hash
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
})
|
|
120
|
+
await client.ready
|
|
121
|
+
|
|
122
|
+
// redirect input to output
|
|
123
|
+
await client.datagrams.readable.pipeTo(client.datagrams.writable)
|
|
124
|
+
|
|
125
|
+
const received = await serverData.promise
|
|
126
|
+
expect(received).to.deep.equal(
|
|
127
|
+
input,
|
|
128
|
+
'Did not receive the same bytes we sent'
|
|
129
|
+
)
|
|
130
|
+
})
|
|
131
|
+
})
|
package/test/echoclient.js
CHANGED
|
@@ -3,18 +3,22 @@
|
|
|
3
3
|
// found in the LICENSE file.
|
|
4
4
|
|
|
5
5
|
import { echoTestsConnection } from './testsuite.js'
|
|
6
|
-
import { WebTransport
|
|
6
|
+
import { WebTransport } from '../lib/index.js'
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* @param {{ hostname: string, port: number }} args
|
|
10
|
+
* @param {{ serverCertificateHashes: Array<{ algorithm: string, value: string }> }} hashes
|
|
11
|
+
*/
|
|
8
12
|
async function startClientTests(args, hashes) {
|
|
9
13
|
const url = 'https://' + args.hostname + ':' + args.port + '/echo'
|
|
10
14
|
console.log('startconnection')
|
|
11
|
-
const hashargs = {
|
|
12
|
-
|
|
13
|
-
(el) => ({
|
|
15
|
+
const hashargs = {
|
|
16
|
+
...hashes,
|
|
17
|
+
serverCertificateHashes: hashes.serverCertificateHashes.map((el) => ({
|
|
14
18
|
algorithm: el.algorithm,
|
|
15
19
|
value: Buffer.from(el.value.split(':').map((el) => parseInt(el, 16)))
|
|
16
|
-
})
|
|
17
|
-
|
|
20
|
+
}))
|
|
21
|
+
}
|
|
18
22
|
// eslint-disable-next-line no-undef
|
|
19
23
|
console.log('hashagrs', hashargs)
|
|
20
24
|
const transport = new WebTransport(url, hashargs)
|
package/test/echoserver.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
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
|
-
import { Http3Server } from '../
|
|
5
|
+
import { Http3Server } from '../lib/index.js'
|
|
6
6
|
import { runEchoServer } from './testsuite.js'
|
|
7
7
|
import { existsSync, readFileSync, writeFile } from 'node:fs'
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
import { generateWebTransportCertificate } from './fixtures/certificate.js'
|
|
10
10
|
|
|
11
11
|
let certificate = null
|
|
12
12
|
|
|
@@ -27,24 +27,22 @@ if (!certificate) {
|
|
|
27
27
|
certificate = await generateWebTransportCertificate(attrs, {
|
|
28
28
|
days: 13
|
|
29
29
|
})
|
|
30
|
-
writeFile('./certificatecache.json', JSON.stringify(certificate),(err)=>{
|
|
30
|
+
writeFile('./certificatecache.json', JSON.stringify(certificate), (err) => {
|
|
31
31
|
if (err) console.log('write certificate cache error', err)
|
|
32
32
|
})
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
console.log('certificate hash ', certificate.fingerprint)
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
37
|
try {
|
|
40
38
|
const http3server = new Http3Server({
|
|
41
39
|
port: 8080,
|
|
42
40
|
host: '0.0.0.0',
|
|
43
41
|
secret: 'mysecret',
|
|
44
|
-
cert: certificate.cert,
|
|
42
|
+
cert: certificate.cert,
|
|
45
43
|
privKey: certificate.private
|
|
46
44
|
})
|
|
47
|
-
|
|
45
|
+
|
|
48
46
|
runEchoServer(http3server)
|
|
49
47
|
http3server.startServer() // you can call destroy to remove the server
|
|
50
48
|
} catch (error) {
|