@fails-components/webtransport 0.0.9 → 0.1.0-macarmbuild.6

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.
Files changed (90) hide show
  1. package/CMakeLists.txt +110 -73
  2. package/build.js +32 -8
  3. package/dist/lib/client.d.ts +74 -0
  4. package/dist/lib/client.d.ts.map +1 -0
  5. package/dist/lib/dom.d.ts +70 -0
  6. package/dist/lib/dom.d.ts.map +1 -0
  7. package/dist/lib/event-loop.d.ts +26 -0
  8. package/dist/lib/event-loop.d.ts.map +1 -0
  9. package/dist/lib/index.d.ts +79 -0
  10. package/dist/lib/index.d.ts.map +1 -0
  11. package/dist/lib/native.d.ts +2 -0
  12. package/dist/lib/native.d.ts.map +1 -0
  13. package/dist/lib/server.d.ts +75 -0
  14. package/dist/lib/server.d.ts.map +1 -0
  15. package/dist/lib/session.d.ts +221 -0
  16. package/dist/lib/session.d.ts.map +1 -0
  17. package/dist/lib/stream.d.ts +116 -0
  18. package/dist/lib/stream.d.ts.map +1 -0
  19. package/dist/lib/transport.d.ts +25 -0
  20. package/dist/lib/transport.d.ts.map +1 -0
  21. package/dist/lib/types.d.ts +157 -0
  22. package/dist/lib/types.d.ts.map +1 -0
  23. package/dist/lib/utils.d.ts +6 -0
  24. package/dist/lib/utils.d.ts.map +1 -0
  25. package/dist/lib/webtransport.d.ts +35 -0
  26. package/dist/lib/webtransport.d.ts.map +1 -0
  27. package/dist/test/bidirectional-streams.spec.d.ts +5 -0
  28. package/dist/test/bidirectional-streams.spec.d.ts.map +1 -0
  29. package/dist/test/datagrams.spec.d.ts +5 -0
  30. package/dist/test/datagrams.spec.d.ts.map +1 -0
  31. package/dist/test/echoclient.d.ts +2 -0
  32. package/dist/test/echoclient.d.ts.map +1 -0
  33. package/dist/test/echoserver.d.ts +2 -0
  34. package/dist/test/echoserver.d.ts.map +1 -0
  35. package/dist/test/event-loop.spec.d.ts +2 -0
  36. package/dist/test/event-loop.spec.d.ts.map +1 -0
  37. package/dist/test/fixtures/certificate.d.ts +21 -0
  38. package/dist/test/fixtures/certificate.d.ts.map +1 -0
  39. package/dist/test/fixtures/chai.d.ts +2 -0
  40. package/dist/test/fixtures/chai.d.ts.map +1 -0
  41. package/dist/test/fixtures/read-stream.d.ts +10 -0
  42. package/dist/test/fixtures/read-stream.d.ts.map +1 -0
  43. package/dist/test/fixtures/reader-value.d.ts +7 -0
  44. package/dist/test/fixtures/reader-value.d.ts.map +1 -0
  45. package/dist/test/fixtures/server.d.ts +10 -0
  46. package/dist/test/fixtures/server.d.ts.map +1 -0
  47. package/dist/test/fixtures/write-stream.d.ts +10 -0
  48. package/dist/test/fixtures/write-stream.d.ts.map +1 -0
  49. package/dist/test/test.d.ts +2 -0
  50. package/dist/test/test.d.ts.map +1 -0
  51. package/dist/test/testsuite.d.ts +25 -0
  52. package/dist/test/testsuite.d.ts.map +1 -0
  53. package/dist/test/unidirectional-streams.spec.d.ts +5 -0
  54. package/dist/test/unidirectional-streams.spec.d.ts.map +1 -0
  55. package/lib/client.js +163 -0
  56. package/lib/dom.ts +95 -0
  57. package/lib/event-loop.js +79 -0
  58. package/lib/index.js +33 -0
  59. package/lib/native.js +25 -0
  60. package/lib/server.js +165 -0
  61. package/lib/session.js +422 -0
  62. package/lib/stream.js +325 -0
  63. package/lib/transport.js +42 -0
  64. package/lib/types.ts +181 -0
  65. package/lib/utils.js +22 -0
  66. package/lib/webtransport.js +114 -0
  67. package/package.json +18 -6
  68. package/readme.md +28 -5
  69. package/src/http3client.h +0 -1
  70. package/src/http3eventloop.cc +52 -0
  71. package/src/http3eventloop.h +22 -1
  72. package/src/http3server.cc +4 -1
  73. package/src/http3server.h +8 -0
  74. package/src/http3serversession.h +1 -1
  75. package/test/bidirectional-streams.spec.js +138 -0
  76. package/test/datagrams.spec.js +132 -0
  77. package/test/echoclient.js +10 -6
  78. package/test/echoserver.js +6 -8
  79. package/test/event-loop.spec.js +24 -0
  80. package/test/{certificate.js → fixtures/certificate.js} +31 -15
  81. package/test/fixtures/chai.js +6 -0
  82. package/test/fixtures/read-stream.js +36 -0
  83. package/test/fixtures/reader-value.js +24 -0
  84. package/test/fixtures/server.js +58 -0
  85. package/test/fixtures/write-stream.js +17 -0
  86. package/test/test.js +29 -3
  87. package/test/testsuite.js +22 -0
  88. package/test/unidirectional-streams.spec.js +128 -0
  89. package/tsconfig.json +40 -0
  90. package/src/webtransport.js +0 -870
package/package.json CHANGED
@@ -1,21 +1,25 @@
1
1
  {
2
2
  "name": "@fails-components/webtransport",
3
- "version": "0.0.9",
3
+ "version": "0.1.0-macarmbuild.6",
4
4
  "description": "A component to add webtransport support (server and client) to node.js using libquiche",
5
- "main": "src/webtransport.js",
6
- "module": "src/webtransport.js",
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": "node test/test.js",
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",
22
+ "lint": "eslint lib test"
19
23
  },
20
24
  "repository": {
21
25
  "type": "git",
@@ -38,7 +42,13 @@
38
42
  "devDependencies": {
39
43
  "@babel/core": "^7.17.10",
40
44
  "@babel/eslint-parser": "^7.17.0",
45
+ "@types/chai": "^4.3.3",
46
+ "@types/dirty-chai": "^2.0.2",
47
+ "@types/mocha": "^10.0.0",
48
+ "@types/node": "^18.11.5",
49
+ "chai": "^4.3.6",
41
50
  "cmake-js": "^6.3.2",
51
+ "dirty-chai": "^2.0.1",
42
52
  "eslint": "^7.32.0",
43
53
  "eslint-config-prettier": "^7.2.0",
44
54
  "eslint-config-standard": "^16.0.2",
@@ -47,8 +57,10 @@
47
57
  "eslint-plugin-prettier": "^3.4.1",
48
58
  "eslint-plugin-promise": "^4.3.1",
49
59
  "eslint-plugin-standard": "^4.1.0",
60
+ "mocha": "^10.1.0",
50
61
  "node-forge": "^1.3.1",
51
- "prettier": "^2.4.1"
62
+ "prettier": "^2.4.1",
63
+ "typescript": "^4.8.4"
52
64
  },
53
65
  "binary": {
54
66
  "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
@@ -11,7 +11,6 @@
11
11
  #define WT_HTTP3_CLIENT_H
12
12
 
13
13
  #include <napi.h>
14
- #include <uv.h>
15
14
 
16
15
  #include <cstdint>
17
16
  #include <memory>
@@ -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);
@@ -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
- std::string *para = nullptr; // for session, we own it, and must delete it
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);
@@ -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();
@@ -107,7 +107,7 @@ namespace quic
107
107
  {
108
108
  if (ShouldNegotiateWebTransport())
109
109
  {
110
- return HttpDatagramSupport::kDraft04And09;
110
+ return HttpDatagramSupport::kRfcAndDraft04;
111
111
  }
112
112
  return QuicServerSessionBase::LocalHttpDatagramSupport();
113
113
  }
@@ -0,0 +1,138 @@
1
+ /* eslint-env mocha */
2
+
3
+ import { createServer } from './fixtures/server.js'
4
+ import { getReaderValue } from './fixtures/reader-value.js'
5
+ import { WebTransport } from '../lib/index.js'
6
+ import { expect } from './fixtures/chai.js'
7
+ import { readStream } from './fixtures/read-stream.js'
8
+ import { writeStream } from './fixtures/write-stream.js'
9
+ import { defer } from '../lib/utils.js'
10
+
11
+ /**
12
+ * @template T
13
+ * @typedef {import('../lib/types').Deferred<T>} Deferred<T>
14
+ */
15
+
16
+ const SERVER_PATH = '/bidirectional-streams'
17
+
18
+ describe('bidirectional streams', function () {
19
+ /** @type {import('../lib/server').Http3Server} */
20
+ let server
21
+ /** @type {import('./fixtures/certificate.js').Certificate} */
22
+ let certificate
23
+ /** @type {import('../lib/dom').WebTransport | undefined} */
24
+ let client
25
+ /** @type {string} */
26
+ let url
27
+
28
+ this.timeout(30000) // this can vary depending on the setup and is not an error
29
+ // but it smells like a work around regarding a deeper problem
30
+
31
+ beforeEach(async () => {
32
+ this.timeout(2000)
33
+ ;({ server, certificate } = await createServer())
34
+ server.startServer()
35
+ await server.ready
36
+
37
+ const address = server.address()
38
+
39
+ if (address == null || address.port == null) {
40
+ throw new Error('No address')
41
+ }
42
+
43
+ url = `https://${address.host}:${address.port}`
44
+ })
45
+
46
+ afterEach(async () => {
47
+ if (client != null) {
48
+ client.close()
49
+ }
50
+
51
+ if (server != null) {
52
+ server.stopServer()
53
+ await server.closed
54
+ }
55
+ })
56
+
57
+ it('sends and receives data over an outgoing bidirectional stream', async () => {
58
+ this.timeout(200)
59
+ // server context - waits for the client to open a bidi stream and pipes it back to them
60
+ Promise.resolve().then(async () => {
61
+ const session = await getReaderValue(server.sessionStream(SERVER_PATH))
62
+ const bidiStream = await getReaderValue(
63
+ session.incomingBidirectionalStreams
64
+ )
65
+ // redirect input to output
66
+ await bidiStream.readable.pipeTo(bidiStream.writable)
67
+ })
68
+
69
+ // client context - connects to the server, opens a bidi stream, sends some data and reads the response
70
+ client = new WebTransport(`${url}${SERVER_PATH}`, {
71
+ serverCertificateHashes: [
72
+ {
73
+ algorithm: 'sha-256',
74
+ value: certificate.hash
75
+ }
76
+ ]
77
+ })
78
+ await client.ready
79
+
80
+ const input = [
81
+ Uint8Array.from([0, 1, 2, 3, 4]),
82
+ Uint8Array.from([5, 6, 7, 8, 9]),
83
+ Uint8Array.from([10, 11, 12, 13, 14])
84
+ ]
85
+
86
+ const stream = await client.createBidirectionalStream()
87
+ await writeStream(stream.writable, input)
88
+
89
+ const output = await readStream(stream.readable)
90
+ expect(output).to.deep.equal(
91
+ input,
92
+ 'Did not receive the same bytes we sent'
93
+ )
94
+ })
95
+
96
+ it('sends and receives data over an incoming bidirectional stream', async () => {
97
+ this.timeout(200)
98
+ /** @type {Deferred<Uint8Array[]>} */
99
+ const serverData = defer()
100
+ const input = [
101
+ Uint8Array.from([0, 1, 2, 3, 4]),
102
+ Uint8Array.from([5, 6, 7, 8, 9]),
103
+ Uint8Array.from([10, 11, 12, 13, 14])
104
+ ]
105
+
106
+ // server context - waits for the client to connect, opens a bidi stream, sends some data and reads the response
107
+ Promise.resolve().then(async () => {
108
+ const session = await getReaderValue(server.sessionStream(SERVER_PATH))
109
+ const stream = await session.createBidirectionalStream()
110
+
111
+ await writeStream(stream.writable, input)
112
+
113
+ const output = await readStream(stream.readable)
114
+ serverData.resolve(output)
115
+ })
116
+
117
+ // client context - waits for the server to open a bidi stream then pipes it back to them
118
+ client = new WebTransport(`${url}${SERVER_PATH}`, {
119
+ serverCertificateHashes: [
120
+ {
121
+ algorithm: 'sha-256',
122
+ value: certificate.hash
123
+ }
124
+ ]
125
+ })
126
+ await client.ready
127
+
128
+ const bidiStream = await getReaderValue(client.incomingBidirectionalStreams)
129
+ // redirect input to output
130
+ await bidiStream.readable.pipeTo(bidiStream.writable)
131
+
132
+ const received = await serverData.promise
133
+ expect(received).to.deep.equal(
134
+ input,
135
+ 'Did not receive the same bytes we sent'
136
+ )
137
+ })
138
+ })
@@ -0,0 +1,132 @@
1
+ /* eslint-env mocha */
2
+
3
+ import { createServer } from './fixtures/server.js'
4
+ import { getReaderValue } from './fixtures/reader-value.js'
5
+ import { WebTransport } from '../lib/index.js'
6
+ import { expect } from './fixtures/chai.js'
7
+ import { readStream } from './fixtures/read-stream.js'
8
+ import { writeStream } from './fixtures/write-stream.js'
9
+ import { defer } from '../lib/utils.js'
10
+
11
+ /**
12
+ * @template T
13
+ * @typedef {import('../lib/types').Deferred<T>} Deferred<T>
14
+ */
15
+
16
+ const SERVER_PATH = '/datagrams'
17
+
18
+ describe('datagrams', function () {
19
+ /** @type {import('../lib/server').Http3Server} */
20
+ let server
21
+ /** @type {import('./fixtures/certificate.js').Certificate} */
22
+ let certificate
23
+ /** @type {import('../lib/dom').WebTransport | undefined} */
24
+ let client
25
+ /** @type {string} */
26
+ let url
27
+
28
+ beforeEach(async () => {
29
+ ;({ server, certificate } = await createServer())
30
+ server.startServer()
31
+ await server.ready
32
+
33
+ const address = server.address()
34
+
35
+ if (address == null || address.port == null) {
36
+ throw new Error('No address')
37
+ }
38
+
39
+ url = `https://${address.host}:${address.port}`
40
+ })
41
+
42
+ afterEach(async () => {
43
+ if (client != null) {
44
+ client.close()
45
+ }
46
+
47
+ if (server != null) {
48
+ server.stopServer()
49
+ await server.closed
50
+ }
51
+ })
52
+
53
+ it('client sends datagrams to the server', async () => {
54
+ this.timeout(200)
55
+ // server context - waits for the client to connect and pipes their datagrams back to them
56
+ Promise.resolve().then(async () => {
57
+ const session = await getReaderValue(server.sessionStream(SERVER_PATH))
58
+
59
+ // redirect input to output
60
+ await session.datagrams.readable.pipeTo(session.datagrams.writable)
61
+ })
62
+
63
+ // client context - connects to the server, sends some datagrams and reads the response
64
+ client = new WebTransport(`${url}${SERVER_PATH}`, {
65
+ serverCertificateHashes: [
66
+ {
67
+ algorithm: 'sha-256',
68
+ value: certificate.hash
69
+ }
70
+ ]
71
+ })
72
+ await client.ready
73
+
74
+ const input = [
75
+ Uint8Array.from([0, 1, 2, 3, 4]),
76
+ Uint8Array.from([5, 6, 7, 8, 9]),
77
+ Uint8Array.from([10, 11, 12, 13, 14])
78
+ ]
79
+
80
+ await writeStream(client.datagrams.writable, input)
81
+
82
+ const output = await readStream(client.datagrams.readable, input.length)
83
+ expect(output).to.deep.equal(
84
+ input,
85
+ 'Did not receive the same bytes we sent'
86
+ )
87
+ })
88
+
89
+ it('receives datagrams from the server', async () => {
90
+ this.timeout(200)
91
+ /** @type {Deferred<Uint8Array[]>} */
92
+ const serverData = defer()
93
+ const input = [
94
+ Uint8Array.from([0, 1, 2, 3, 4]),
95
+ Uint8Array.from([5, 6, 7, 8, 9]),
96
+ Uint8Array.from([10, 11, 12, 13, 14])
97
+ ]
98
+
99
+ // server context - waits for the client to connect, sends some datagrams and reads the response
100
+ Promise.resolve().then(async () => {
101
+ const session = await getReaderValue(server.sessionStream(SERVER_PATH))
102
+
103
+ await writeStream(session.datagrams.writable, input)
104
+
105
+ const output = await readStream(session.datagrams.readable, input.length)
106
+
107
+ // have to close the session to end the client's datagram stream
108
+ session.close()
109
+ serverData.resolve(output)
110
+ })
111
+
112
+ // client context - pipes the server's datagrams back to them
113
+ client = new WebTransport(`${url}${SERVER_PATH}`, {
114
+ serverCertificateHashes: [
115
+ {
116
+ algorithm: 'sha-256',
117
+ value: certificate.hash
118
+ }
119
+ ]
120
+ })
121
+ await client.ready
122
+
123
+ // redirect input to output
124
+ await client.datagrams.readable.pipeTo(client.datagrams.writable)
125
+
126
+ const received = await serverData.promise
127
+ expect(received).to.deep.equal(
128
+ input,
129
+ 'Did not receive the same bytes we sent'
130
+ )
131
+ })
132
+ })
@@ -3,18 +3,22 @@
3
3
  // found in the LICENSE file.
4
4
 
5
5
  import { echoTestsConnection } from './testsuite.js'
6
- import { WebTransport } from '../src/webtransport.js'
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 = { ...hashes }
12
- hashargs.serverCertificateHashes = hashes.serverCertificateHashes.map(
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)
@@ -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 '../src/webtransport.js'
5
+ import { Http3Server } from '../lib/index.js'
6
6
  import { runEchoServer } from './testsuite.js'
7
7
  import { existsSync, readFileSync, writeFile } from 'node:fs'
8
- import { generateWebTransportCertificate } from './certificate.js'
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) {