@amritk/nish-x86_64-linux 0.24.0 → 0.25.0

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 (42) hide show
  1. package/bin/nish +0 -0
  2. package/docs/AI.md +24 -4
  3. package/package.json +1 -1
  4. package/runtime/browser.mjs +4 -3
  5. package/runtime/core/host.d.ts +8 -0
  6. package/runtime/core/host.mjs +50 -2
  7. package/runtime/nish.d.ts +100 -0
  8. package/runtime/nish.h +34 -0
  9. package/runtime/nish.mjs +21 -0
  10. package/runtime/node.mjs +57 -6
  11. package/runtime/runtime-net.c +44 -3
  12. package/runtime/runtime-os.c +115 -12
  13. package/scripts/build.sh +13 -3
  14. package/std/README.md +25 -8
  15. package/std/cli-args.ts +1216 -0
  16. package/std/cli-flags.ts +585 -0
  17. package/std/cli.ts +308 -19
  18. package/std/compress/checksum.ts +132 -20
  19. package/std/compress/deflate.ts +638 -248
  20. package/std/compress/gzip.ts +20 -6
  21. package/std/crypto/LICENSE-fiat-crypto-p384 +21 -0
  22. package/std/crypto/p256.ts +63 -1
  23. package/std/crypto/p384.ts +6044 -0
  24. package/std/crypto/pki-roots.ts +322 -0
  25. package/std/crypto/pki.ts +1207 -0
  26. package/std/crypto/rsa.ts +663 -0
  27. package/std/crypto/x509.ts +1054 -58
  28. package/std/net/dns-config.ts +734 -0
  29. package/std/net/dns-message.ts +517 -0
  30. package/std/net/dns-resolver.ts +1015 -0
  31. package/std/net/dns.ts +478 -0
  32. package/std/net/http1-client.ts +1227 -0
  33. package/std/net/http1-server.ts +7 -2
  34. package/std/net/http1.ts +9 -9
  35. package/std/net/tls/client.ts +1094 -0
  36. package/std/net/tls/codec.ts +585 -3
  37. package/std/net/tls/record-client.ts +1192 -0
  38. package/std/net/tls/verify.ts +221 -0
  39. package/std/net/tls-tcp.ts +642 -11
  40. package/std/net/tls.ts +2 -2
  41. package/std/net/url.ts +559 -4
  42. package/std/yaml-write.ts +415 -0
@@ -1,7 +1,10 @@
1
1
  /**
2
2
  * `nish/net/tls-tcp` — TLS 1.3 over TCP (WP34 T2): a pool of server
3
3
  * connections on `nish:net`, each a `TlsRecordServer` driving a `TlsServer`,
4
- * for a program that owns its readiness loop.
4
+ * for a program that owns its readiness loop; and `TlsTcpClient`, one
5
+ * client connection that authenticates the server (`nish/net/tls/verify`)
6
+ * and is driven by the same kind of loop or, through `tlsConnect`,
7
+ * `readWait` and `writeAll`, by none (its own comment, below, says how).
5
8
  *
6
9
  * const listener = tcpListen("0.0.0.0", 8443, 128);
7
10
  * const tls = new TlsTcpServer(config, listener, 64);
@@ -72,20 +75,61 @@
72
75
  * `close` frees the slot at once; `shutdown` sends the `close_notify` as the
73
76
  * socket takes it, and the slot is done when its interest says so.
74
77
  *
78
+ * **A closed connection lingers until the client hangs up.** Once a
79
+ * connection the server closed has sent everything, the slot shuts the
80
+ * socket's write side — the client reads the response, the `close_notify`
81
+ * and then the end of the stream — and reads and throws away what the
82
+ * client still sends, until its own end of stream, before its interest says
83
+ * `TLS_RECORD_DONE` (RFC 9112 §9.6's staged close). A socket closed with
84
+ * the client's bytes unread, or that the client writes to after the close,
85
+ * answers with a TCP reset, and a client may report that reset instead of
86
+ * the response it was sent: Chromium's `ERR_CONNECTION_RESET` after an HTTP/2
87
+ * `GOAWAY`, when its SETTINGS acknowledgement crossed the server's close. A
88
+ * lingering slot holds no key and no record buffer, asks only to be read,
89
+ * and gives up past `TLS_TCP_LINGER_LIMIT` bytes. A connection that failed,
90
+ * whose client sent its own `close_notify` (it writes nothing after it,
91
+ * RFC 8446 §6.1) or whose client's stream had already ended, is done at
92
+ * once. A client that never hangs up keeps its slot, as one that never sends
93
+ * does: how long a slot may wait is the program's (`docs/security/tls.md`,
94
+ * TLS-6 and TLS-4).
95
+ *
75
96
  * Written from RFC 8446 and docs/LANGUAGE.md's `nish:net`, not ported from
76
97
  * another implementation.
77
98
  */
78
99
  import { Secret } from "nish:secret"
79
- import { netClose, netRead, netWrite, tcpAccept } from "nish:net"
100
+ import {
101
+ connectResult,
102
+ netClose,
103
+ netRead,
104
+ netShutdown,
105
+ netWrite,
106
+ pollAdd,
107
+ pollCreate,
108
+ pollModify,
109
+ pollWait,
110
+ tcpAccept,
111
+ tcpConnect,
112
+ } from "nish:net"
113
+ import { monotonicNanos, wallClockNanos } from "nish:process"
80
114
  import { TlsP256Signer, TlsServer, TlsServerConfig } from "nish/net/tls"
115
+ import { TlsClient, TlsClientConfig } from "nish/net/tls/client"
116
+ import { TLS_RECORD_AUTH, TlsRecordClient } from "nish/net/tls/record-client"
117
+ import { TlsClientTrust, tlsClientAuthenticate, tlsClientSystemTrust } from "nish/net/tls/verify"
81
118
  import { TLS_ALERT_INTERNAL_ERROR } from "nish/net/tls/codec"
82
119
  import { TLS_MAX_RECORD } from "nish/net/tls/record"
83
120
  import { TlsTicketKeyInfo, TlsTicketStrikes } from "nish/net/tls/ticket"
84
121
  import {
85
122
  TLS_RECORD_AGAIN,
123
+ TLS_RECORD_DATA,
86
124
  TLS_RECORD_DONE,
87
125
  TLS_RECORD_INVALID,
126
+ TLS_RECORD_PIPE,
127
+ TLS_RECORD_RESET,
128
+ TLS_RECORD_STATE_CLOSED,
88
129
  TLS_RECORD_STATE_FAILED,
130
+ TLS_RECORD_STATE_OPEN,
131
+ TLS_RECORD_WANT_READ,
132
+ TLS_RECORD_WANT_WRITE,
89
133
  TlsRecordPools,
90
134
  TlsRecordServer,
91
135
  } from "nish/net/tls/record-server"
@@ -102,9 +146,36 @@ const TLS_TCP_FROM: i32 = 0
102
146
  /** The length of an x25519 private key and of a server random: what `TlsServer` takes for each. */
103
147
  const TLS_TCP_KEY_SIZE: i32 = 32
104
148
 
149
+ /** The most a lingering slot reads and throws away before it is done anyway: 64 KiB. */
150
+ const TLS_TCP_LINGER_LIMIT: i32 = 65536
151
+
152
+ /** In `lingered`: a slot that is not lingering. */
153
+ const TLS_TCP_LIVE: i32 = -1
154
+
155
+ /** In `lingered`: a slot whose linger is over, which is done. */
156
+ const TLS_TCP_LINGERED: i32 = -2
157
+
158
+ /** `netShutdown`'s write side. */
159
+ const TLS_TCP_SHUT_WRITE: i32 = 1
160
+
105
161
  /** The address form's length, from `nish:net`. */
106
162
  const TLS_TCP_ADDRESS_SIZE: i32 = 18
107
163
 
164
+ /** What `tlsConnect` answers when the connection did not open within its timeout. Linux's ETIMEDOUT. */
165
+ export const TLS_TCP_TIMED_OUT: i32 = -110
166
+
167
+ /** `pollWait`'s readable bit and its hang-up-or-error bit: the events `readable` takes. */
168
+ const TLS_TCP_READ_EVENTS: i32 = 5
169
+
170
+ /** The events `pollModify` takes, readable and writable: the low two `TLS_RECORD_*` bits. */
171
+ const TLS_TCP_POLL_EVENTS: i32 = 3
172
+
173
+ /** The token `tlsConnect`'s own loop watches its one socket under. */
174
+ const TLS_TCP_TOKEN: i32 = 1
175
+
176
+ /** Nanoseconds in a millisecond, for the clocks of `nish:process`. */
177
+ const TLS_TCP_NANOS_PER_MS: i64 = 1000000
178
+
108
179
  /**
109
180
  * A pool of TLS 1.3 server connections on one listening socket. `slot`s are
110
181
  * the indices `accept` answers, from 0 to the pool's size; a slot is the
@@ -125,6 +196,12 @@ export class TlsTcpServer {
125
196
  * it has never been used.
126
197
  */
127
198
  fds: i32[]
199
+ /**
200
+ * Each slot's linger, beside `fds`: `TLS_TCP_LIVE` until its connection is
201
+ * over, then how many bytes it has thrown away while it waits for the
202
+ * client's end of stream, then `TLS_TCP_LINGERED` once it is done.
203
+ */
204
+ lingered: i32[]
128
205
  /**
129
206
  * The slots closed since they were used, a stack whose top `accept` takes
130
207
  * before it builds a slot past the high-water mark, so finding one costs
@@ -180,6 +257,7 @@ export class TlsTcpServer {
180
257
  this.listener = listener
181
258
  this.connections = []
182
259
  this.fds = []
260
+ this.lingered = []
183
261
  this.free = []
184
262
  this.randoms = []
185
263
  this.slots = size < 1 ? 1 : size
@@ -301,6 +379,7 @@ export class TlsTcpServer {
301
379
  } else if (toI32(this.fds.length) < this.slots) {
302
380
  slot = toI32(this.fds.length)
303
381
  this.fds.push(TLS_TCP_FREE)
382
+ this.lingered.push(TLS_TCP_LIVE)
304
383
  }
305
384
  if (slot < 0 || slot >= toI32(this.fds.length)) {
306
385
  netClose(fd)
@@ -309,6 +388,7 @@ export class TlsTcpServer {
309
388
  const conn: TlsRecordServer =
310
389
  slot < toI32(this.connections.length) ? this.connections[slot] : this.build()
311
390
  this.fds[slot] = fd
391
+ this.lingered[slot] = TLS_TCP_LIVE
312
392
  const random: u8[] = this.randoms[slot]
313
393
  for (
314
394
  let k: i32 = 0;
@@ -326,13 +406,16 @@ export class TlsTcpServer {
326
406
  /**
327
407
  * The socket of `slot` is readable: reads it until it would block or the
328
408
  * connection has no room, handing each read to the connection, then sends
329
- * what that produced. Answers the connection's `interest`; a free slot
330
- * answers `TLS_RECORD_DONE`.
409
+ * what that produced. Answers the connection's `interest`, or, while the
410
+ * slot lingers, what `drain` answers; a free slot answers `TLS_RECORD_DONE`.
331
411
  */
332
412
  readable(slot: i32): i32 {
333
413
  if (!this.holds(slot)) {
334
414
  return TLS_RECORD_DONE
335
415
  }
416
+ if (this.lingered[slot] !== TLS_TCP_LIVE) {
417
+ return this.drain(slot)
418
+ }
336
419
  const fd: i32 = this.fds[slot]
337
420
  const conn: TlsRecordServer = this.connections[slot]
338
421
  while (true) {
@@ -365,13 +448,25 @@ export class TlsTcpServer {
365
448
 
366
449
  /**
367
450
  * Writes the connection's output until the socket would block or it is all
368
- * sent, and answers its `interest`; a slot never built answers
369
- * `TLS_RECORD_DONE`.
451
+ * sent, and answers its `interest`; once the connection is over and the
452
+ * program has read what the client sent, the slot lingers (`linger`). A
453
+ * free slot answers `TLS_RECORD_DONE`.
370
454
  */
371
455
  flush(slot: i32): i32 {
372
- if (slot < 0 || slot >= toI32(this.fds.length) || slot >= toI32(this.connections.length)) {
456
+ if (!this.holds(slot)) {
373
457
  return TLS_RECORD_DONE
374
458
  }
459
+ if (this.lingered[slot] !== TLS_TCP_LIVE) {
460
+ return this.lingerInterest(slot)
461
+ }
462
+ const wants: i32 = this.send(slot)
463
+ // Application data still unread keeps the connection, and its keys, until
464
+ // the program reads it: lingering gives back the buffer that holds it.
465
+ return (wants & TLS_RECORD_DONE) !== 0 && (wants & TLS_RECORD_DATA) === 0 ? this.linger(slot) : wants
466
+ }
467
+
468
+ /** Writes `slot`'s output until the socket would block or it is all sent; answers the connection's `interest`. */
469
+ send(slot: i32): i32 {
375
470
  const fd: i32 = this.fds[slot]
376
471
  const conn: TlsRecordServer = this.connections[slot]
377
472
  while (conn.outputEnd > conn.outputStart) {
@@ -387,9 +482,70 @@ export class TlsTcpServer {
387
482
  return conn.interest()
388
483
  }
389
484
 
485
+ /**
486
+ * `slot`'s connection is over and everything is sent. One the server
487
+ * closed, whose client has neither sent its `close_notify` nor ended its
488
+ * stream, lingers: the socket's write side is shut, every key wiped and
489
+ * every record buffer given back, and what the client sends is drained
490
+ * (`drain`). Any other is done.
491
+ */
492
+ linger(slot: i32): i32 {
493
+ const conn: TlsRecordServer = this.connections[slot]
494
+ if (
495
+ conn.state !== TLS_RECORD_STATE_CLOSED ||
496
+ conn.peerClosed ||
497
+ conn.ended ||
498
+ netShutdown(this.fds[slot], TLS_TCP_SHUT_WRITE) < 0
499
+ ) {
500
+ this.lingered[slot] = TLS_TCP_LINGERED
501
+ return TLS_RECORD_DONE
502
+ }
503
+ this.lingered[slot] = 0
504
+ conn.wipeKeys()
505
+ conn.release()
506
+ return this.drain(slot)
507
+ }
508
+
509
+ /**
510
+ * Reads what a lingering `slot`'s client sends and throws it away, until
511
+ * the socket would block (`TLS_RECORD_WANT_READ`), or its stream ends, the
512
+ * socket fails or more than `TLS_TCP_LINGER_LIMIT` bytes came
513
+ * (`TLS_RECORD_DONE`).
514
+ */
515
+ drain(slot: i32): i32 {
516
+ while (this.lingered[slot] >= 0) {
517
+ const n: i32 = netRead(this.fds[slot], this.scratch, TLS_TCP_FROM, toI32(this.scratch.length))
518
+ if (n === TLS_RECORD_AGAIN) {
519
+ return TLS_RECORD_WANT_READ
520
+ }
521
+ this.lingered[slot] =
522
+ n > 0 && this.lingered[slot] + n <= TLS_TCP_LINGER_LIMIT ? this.lingered[slot] + n : TLS_TCP_LINGERED
523
+ }
524
+ return TLS_RECORD_DONE
525
+ }
526
+
527
+ /**
528
+ * Whether `slot` lingers: its connection is over and it waits for the
529
+ * client to hang up. A carrier's idle clock does not move for what a
530
+ * lingering slot drains, so its idle timeout bounds the wait.
531
+ */
532
+ lingering(slot: i32): boolean {
533
+ return this.holds(slot) && this.lingered[slot] >= 0
534
+ }
535
+
536
+ /** A lingering `slot`'s interest: to be read, or `TLS_RECORD_DONE` once its linger is over. */
537
+ lingerInterest(slot: i32): i32 {
538
+ return this.lingered[slot] >= 0 ? TLS_RECORD_WANT_READ : TLS_RECORD_DONE
539
+ }
540
+
390
541
  /** What `slot`'s connection wants now, without touching the socket; `TLS_RECORD_DONE` for a free slot. */
391
542
  interest(slot: i32): i32 {
392
- return this.holds(slot) ? this.connections[slot].interest() : TLS_RECORD_DONE
543
+ if (!this.holds(slot)) {
544
+ return TLS_RECORD_DONE
545
+ }
546
+ return this.lingered[slot] !== TLS_TCP_LIVE
547
+ ? this.lingerInterest(slot)
548
+ : this.connections[slot].interest()
393
549
  }
394
550
 
395
551
  /** What `slot`'s CertificateVerify signs, while its `interest` says `TLS_RECORD_SIGN`; `null` otherwise. */
@@ -522,7 +678,9 @@ export class TlsTcpServer {
522
678
  * Closes the server's side of `slot`'s connection (§6.1): a `close_notify`
523
679
  * after whatever is already written, sent as the socket takes it. The slot
524
680
  * stays the program's until its interest says `TLS_RECORD_DONE` — every
525
- * byte is sent — and the program calls `close`. Answers the interest.
681
+ * byte is sent and, once the slot lingers (`linger`), the client has hung
682
+ * up — and the program calls `close`. Meanwhile the interest asks for the
683
+ * socket to be read. Answers the interest.
526
684
  */
527
685
  shutdown(slot: i32): i32 {
528
686
  if (!this.holds(slot)) {
@@ -545,13 +703,486 @@ export class TlsTcpServer {
545
703
  return
546
704
  }
547
705
  const conn: TlsRecordServer = this.connections[slot]
548
- conn.close()
549
- this.flush(slot)
706
+ if (this.lingered[slot] === TLS_TCP_LIVE) {
707
+ conn.close()
708
+ this.send(slot)
709
+ }
550
710
  netClose(this.fds[slot])
551
711
  this.fds[slot] = TLS_TCP_FREE
712
+ this.lingered[slot] = TLS_TCP_LIVE
552
713
  this.free.push(slot)
553
714
  conn.wipeKeys()
554
715
  // Whatever the last flush could not send is dropped with its buffer.
555
716
  conn.release()
556
717
  }
557
718
  }
719
+
720
+ /**
721
+ * One TLS 1.3 client connection over TCP: a `TlsRecordClient` driving a
722
+ * `TlsClient` on a socket `tcpConnect` opened, for a program that owns its
723
+ * readiness loop, as `TlsTcpServer` is for a server.
724
+ *
725
+ * const client = new TlsTcpClient(addr, new TlsClientConfig("example.com", ["h2"]), trust);
726
+ * pollAdd(loop, client.fd(), client.interest() & 3, TOKEN);
727
+ * // when the token comes back:
728
+ * let wants: i32 = (events & 5) !== 0 ? client.readable() : client.writable();
729
+ * if ((wants & TLS_RECORD_DATA) !== 0) { client.read(buf, 0, buf.length); ... }
730
+ * if ((wants & TLS_RECORD_DONE) !== 0) { client.close(); }
731
+ * else { pollModify(loop, client.fd(), wants & 3, TOKEN); }
732
+ *
733
+ * **The server is always authenticated.** When the handshake reaches the
734
+ * server's certificate chain and CertificateVerify, the client gives its own
735
+ * verdict at once (`tlsClientAuthenticate` in `nish/net/tls/verify`) under
736
+ * `trust` and the wall clock, before it reads the server's Finished, so
737
+ * `interest` never says `TLS_RECORD_AUTH` and there is no way to open a
738
+ * connection to a server the trust does not vouch for. A refusal fails the
739
+ * connection with its alert sent: `failure()` names it.
740
+ *
741
+ * **Randomness is the CSPRNG's.** The client random and both ephemeral keys
742
+ * (x25519, and P-256 for a server that asks for that group) are drawn from
743
+ * `crypto.getRandomValues` in the constructor; `TlsClient` copies the keys
744
+ * into its own `Secret`s and the arrays they were drawn into are zeroed.
745
+ *
746
+ * The socket is still connecting when the constructor returns, so the first
747
+ * interest is writable alone; the first event on it, writable or a hang-up,
748
+ * says how the connection went (`connectResult`), and a socket that did not
749
+ * connect leaves the client done, its failure in `connectError`.
750
+ */
751
+ export class TlsTcpClient {
752
+ conn: TlsRecordClient
753
+ trust: TlsClientTrust
754
+ /** The socket, -1 once `close` has run or when `tcpConnect` refused at once. */
755
+ socket: i32 = -1
756
+ /** Whether the socket has yet to say how its connection went. */
757
+ connecting: boolean = true
758
+ /**
759
+ * Why the connection failed below TLS, or 0: `tcpConnect`'s or
760
+ * `connectResult`'s failure, or what ended `tlsConnect`'s wait — its
761
+ * timeout (`TLS_TCP_TIMED_OUT`) or its loop's failure.
762
+ */
763
+ connectError: i32 = 0
764
+ /** Where the socket is read into before the connection takes the bytes: the largest record. */
765
+ scratch: u8[]
766
+
767
+ /**
768
+ * A connection to the server at `addr` (`nish:net`'s 18-byte form, which
769
+ * `netAddress` writes) under `config`, authenticated against `trust`. The
770
+ * name the certificate must carry is `config.serverName`, never one taken
771
+ * from `addr`. The ClientHello is sealed here and waits for the socket.
772
+ */
773
+ constructor(addr: u8[], config: TlsClientConfig, trust: TlsClientTrust) {
774
+ const random: u8[] = new Array<u8>(TLS_TCP_KEY_SIZE)
775
+ const x25519: u8[] = new Array<u8>(TLS_TCP_KEY_SIZE)
776
+ const p256: u8[] = new Array<u8>(TLS_TCP_KEY_SIZE)
777
+ crypto.getRandomValues(random)
778
+ crypto.getRandomValues(x25519)
779
+ crypto.getRandomValues(p256)
780
+ // `TlsClient` copies both keys and zeroes these arrays.
781
+ this.conn = new TlsRecordClient(new TlsClient(config, random, x25519, p256))
782
+ this.trust = trust
783
+ this.scratch = new Array<u8>(TLS_MAX_RECORD)
784
+ // A config `TlsClient` refused (a bad name or protocol) is never dialled:
785
+ // the client is failed already, its alert in `failure()`.
786
+ if (this.conn.state === TLS_RECORD_STATE_FAILED) {
787
+ this.connecting = false
788
+ return
789
+ }
790
+ const fd: i32 = toI32(addr.length) < TLS_TCP_ADDRESS_SIZE ? TLS_RECORD_INVALID : tcpConnect(addr)
791
+ if (fd < 0) {
792
+ this.connecting = false
793
+ this.connectError = fd
794
+ this.conn.abort()
795
+ return
796
+ }
797
+ this.socket = fd
798
+ }
799
+
800
+ /** The socket, for `pollAdd`; -1 when there is none. */
801
+ fd(): i32 {
802
+ return this.socket
803
+ }
804
+
805
+ /** Whether the handshake is done and the server authenticated, so application data flows. */
806
+ open(): boolean {
807
+ return this.conn.state === TLS_RECORD_STATE_OPEN
808
+ }
809
+
810
+ /** Why a failed connection failed: `connectError` when TCP did not connect, else the alert (`TlsRecordClient.failure`). */
811
+ failure(): i32 {
812
+ return this.connectError !== 0 ? this.connectError : this.conn.failure()
813
+ }
814
+
815
+ /** What the connection wants now, as `TLS_RECORD_*` bits, without touching the socket. */
816
+ interest(): i32 {
817
+ if (this.socket < 0) {
818
+ return TLS_RECORD_DONE
819
+ }
820
+ return this.connecting ? TLS_RECORD_WANT_WRITE : this.conn.interest()
821
+ }
822
+
823
+ /**
824
+ * The first event on a connecting socket: whether it connected. One that
825
+ * did not leaves the client failed with nothing sent, its reason in
826
+ * `connectError`.
827
+ */
828
+ connected(): boolean {
829
+ this.connecting = false
830
+ const result: i32 = connectResult(this.socket)
831
+ if (result !== 0) {
832
+ this.connectError = result
833
+ this.conn.abort()
834
+ return false
835
+ }
836
+ return true
837
+ }
838
+
839
+ /**
840
+ * The socket is readable, or hung up: reads it until it would block or the
841
+ * connection has no room, handing each read to the connection, then sends
842
+ * what that produced. Answers the `interest`.
843
+ */
844
+ readable(): i32 {
845
+ if (this.socket < 0) {
846
+ return TLS_RECORD_DONE
847
+ }
848
+ if (this.connecting && !this.connected()) {
849
+ return TLS_RECORD_DONE
850
+ }
851
+ const conn: TlsRecordClient = this.conn
852
+ while (true) {
853
+ const room: i32 = conn.space()
854
+ const want: i32 = room < toI32(this.scratch.length) ? room : toI32(this.scratch.length)
855
+ if (want <= 0 || conn.state === TLS_RECORD_STATE_FAILED || conn.peerClosed) {
856
+ break
857
+ }
858
+ const n: i32 = netRead(this.socket, this.scratch, TLS_TCP_FROM, want)
859
+ if (n <= 0) {
860
+ if (n === 0) {
861
+ conn.end()
862
+ } else if (n !== TLS_RECORD_AGAIN) {
863
+ conn.abort()
864
+ }
865
+ break
866
+ }
867
+ conn.receive(this.scratch, TLS_TCP_FROM, n)
868
+ }
869
+ return this.flush()
870
+ }
871
+
872
+ /** The socket is writable: on the first event, how the connection went; then sends what the connection holds. Answers the `interest`. */
873
+ writable(): i32 {
874
+ if (this.socket < 0) {
875
+ return TLS_RECORD_DONE
876
+ }
877
+ if (this.connecting && !this.connected()) {
878
+ return TLS_RECORD_DONE
879
+ }
880
+ return this.flush()
881
+ }
882
+
883
+ /**
884
+ * Authenticates the server once the handshake asks for it, then writes the
885
+ * connection's output until the socket would block or it is all sent, and
886
+ * answers the `interest`.
887
+ */
888
+ flush(): i32 {
889
+ if (this.socket < 0) {
890
+ return TLS_RECORD_DONE
891
+ }
892
+ if (this.connecting) {
893
+ return TLS_RECORD_WANT_WRITE
894
+ }
895
+ if ((this.conn.interest() & TLS_RECORD_AUTH) !== 0) {
896
+ tlsClientAuthenticate(this.conn, this.trust, wallClockNanos() / TLS_TCP_NANOS_PER_MS)
897
+ }
898
+ const conn: TlsRecordClient = this.conn
899
+ while (conn.outputEnd > conn.outputStart) {
900
+ const n: i32 = netWrite(this.socket, conn.output, conn.outputStart, conn.outputEnd - conn.outputStart)
901
+ if (n <= 0) {
902
+ if (n !== TLS_RECORD_AGAIN) {
903
+ conn.abort()
904
+ }
905
+ break
906
+ }
907
+ conn.consume(n)
908
+ }
909
+ return conn.interest()
910
+ }
911
+
912
+ /**
913
+ * Application data from the server into `buf[off .. off + len)`: the count,
914
+ * 0 at the end of its stream, -11 when nothing is waiting, -104 once the
915
+ * connection has failed, as `TlsRecordClient.read` says, and -32 once
916
+ * `close` has run. What reading makes room for is answered and sent here;
917
+ * re-arm with `interest()`.
918
+ */
919
+ read(buf: u8[], off: i32, len: i32): i32 {
920
+ if (this.socket < 0) {
921
+ return this.ended()
922
+ }
923
+ const n: i32 = this.conn.read(buf, off, len)
924
+ this.flush()
925
+ return n
926
+ }
927
+
928
+ /** What a call on a client that is done answers: -104 when it failed, else -32. */
929
+ ended(): i32 {
930
+ return this.conn.state === TLS_RECORD_STATE_FAILED ? TLS_RECORD_RESET : TLS_RECORD_PIPE
931
+ }
932
+
933
+ /**
934
+ * Application data from the server, waiting for it up to `timeoutMs`
935
+ * milliseconds in a loop of the client's own, for a program with no loop:
936
+ * the count, 0 at the end of the server's stream, `TLS_TCP_TIMED_OUT` when
937
+ * nothing came in time (the connection stays usable), -104 once it has
938
+ * failed, -32 once it is done otherwise, or the loop's failure. It drives
939
+ * the handshake too, so it may follow `new TlsTcpClient` directly.
940
+ */
941
+ readWait(buf: u8[], off: i32, len: i32, timeoutMs: i32): i32 {
942
+ const end: i64 = tlsTcpDeadline(timeoutMs)
943
+ let n: i32 = this.read(buf, off, len)
944
+ if (n !== TLS_RECORD_AGAIN) {
945
+ return n
946
+ }
947
+ const loop: i32 = this.ownLoop()
948
+ if (loop < 0) {
949
+ return loop
950
+ }
951
+ while (n === TLS_RECORD_AGAIN) {
952
+ const turned: i32 = this.turn(loop, end)
953
+ if (turned === TLS_RECORD_DONE) {
954
+ n = this.read(buf, off, len)
955
+ if (n === TLS_RECORD_AGAIN) {
956
+ n = this.ended()
957
+ }
958
+ break
959
+ }
960
+ if (turned < 0) {
961
+ n = turned
962
+ break
963
+ }
964
+ n = this.read(buf, off, len)
965
+ }
966
+ netClose(loop)
967
+ return n
968
+ }
969
+
970
+ /**
971
+ * Writes all of `data[off .. off + len)` and waits up to `timeoutMs`
972
+ * milliseconds, in a loop of the client's own, until the socket has taken
973
+ * every byte of it: `len`, or — some of it perhaps sent — `TLS_TCP_TIMED_OUT`,
974
+ * -32 when the client had closed or failed before the call, -104 when the
975
+ * connection fails during it, -22 for a window outside `data`, or the
976
+ * loop's failure. It drives the handshake too, so it may follow
977
+ * `new TlsTcpClient` directly. A timed-out call does not say how much was
978
+ * sent, and the same bytes written again would reach the server twice:
979
+ * once the handshake is done, a timeout leaves the stream unusable, and
980
+ * the client is for `close`.
981
+ */
982
+ writeAll(data: u8[], off: i32, len: i32, timeoutMs: i32): i32 {
983
+ const end: i64 = tlsTcpDeadline(timeoutMs)
984
+ let done: i32 = 0
985
+ let loop: i32 = -1
986
+ let result: i32 = 0
987
+ while (true) {
988
+ if (done < len) {
989
+ const n: i32 = this.write(data, off + done, len - done)
990
+ if (n >= 0) {
991
+ done = done + n
992
+ } else if (n !== TLS_RECORD_AGAIN) {
993
+ result = n
994
+ break
995
+ }
996
+ }
997
+ if (done >= len && !this.sending()) {
998
+ break
999
+ }
1000
+ if (loop < 0) {
1001
+ loop = this.ownLoop()
1002
+ if (loop < 0) {
1003
+ result = loop
1004
+ break
1005
+ }
1006
+ }
1007
+ const turned: i32 = this.turn(loop, end)
1008
+ if (turned === TLS_RECORD_DONE) {
1009
+ result = this.ended()
1010
+ break
1011
+ }
1012
+ if (turned < 0) {
1013
+ result = turned
1014
+ break
1015
+ }
1016
+ }
1017
+ if (loop >= 0) {
1018
+ netClose(loop)
1019
+ }
1020
+ return result < 0 ? result : done
1021
+ }
1022
+
1023
+ /** Whether sealed bytes are still waiting for the socket. */
1024
+ sending(): boolean {
1025
+ return this.conn.outputEnd > this.conn.outputStart
1026
+ }
1027
+
1028
+ /** A readiness loop of the client's own holding its socket, for the waiting calls, or the loop's failure. */
1029
+ ownLoop(): i32 {
1030
+ const loop: i32 = pollCreate()
1031
+ if (loop >= 0) {
1032
+ pollAdd(loop, this.socket, this.interest() & TLS_TCP_POLL_EVENTS, TLS_TCP_TOKEN)
1033
+ }
1034
+ return loop
1035
+ }
1036
+
1037
+ /**
1038
+ * One turn of `loop` (from `ownLoop`): waits until the socket is ready, or
1039
+ * until `end` on the monotonic clock, and handles what came. Answers 0,
1040
+ * `TLS_RECORD_DONE` once the connection wants nothing more,
1041
+ * `TLS_TCP_TIMED_OUT`, or the loop's failure.
1042
+ */
1043
+ turn(loop: i32, end: i64): i32 {
1044
+ const wants: i32 = this.interest()
1045
+ if ((wants & TLS_RECORD_DONE) !== 0) {
1046
+ return TLS_RECORD_DONE
1047
+ }
1048
+ const now: i64 = monotonicNanos()
1049
+ if (now >= end) {
1050
+ return TLS_TCP_TIMED_OUT
1051
+ }
1052
+ pollModify(loop, this.socket, wants & TLS_TCP_POLL_EVENTS, TLS_TCP_TOKEN)
1053
+ const ready: i32[] = new Array<i32>(2)
1054
+ const count: i32 = pollWait(
1055
+ loop,
1056
+ ready,
1057
+ toI32((end - now + TLS_TCP_NANOS_PER_MS - 1) / TLS_TCP_NANOS_PER_MS)
1058
+ )
1059
+ if (count < 0) {
1060
+ return count
1061
+ }
1062
+ if (count > 0) {
1063
+ if ((ready[1] & TLS_TCP_READ_EVENTS) !== 0) {
1064
+ this.readable()
1065
+ } else {
1066
+ this.writable()
1067
+ }
1068
+ }
1069
+ return 0
1070
+ }
1071
+
1072
+ /**
1073
+ * Application data for the server, `data[off .. off + len)`: seals as much
1074
+ * as fits and sends what the socket takes, and answers how much was taken,
1075
+ * -11 when nothing fits yet or the handshake is under way, -32 once the
1076
+ * client has closed, as `TlsRecordClient.write` says.
1077
+ */
1078
+ write(data: u8[], off: i32, len: i32): i32 {
1079
+ const n: i32 = this.conn.write(data, off, len)
1080
+ this.flush()
1081
+ return n
1082
+ }
1083
+
1084
+ /** Sends a KeyUpdate, asking the server for one too when `requestPeer`; answers as `TlsRecordClient.keyUpdate`. */
1085
+ keyUpdate(requestPeer: boolean): i32 {
1086
+ const r: i32 = this.conn.keyUpdate(requestPeer)
1087
+ this.flush()
1088
+ return r
1089
+ }
1090
+
1091
+ /**
1092
+ * Closes the client's side (§6.1): a `close_notify` after whatever is
1093
+ * already written, sent as the socket takes it. The client does not wait
1094
+ * for the server's own `close_notify`: data already read off the socket
1095
+ * still reads, and once the `close_notify` is sent the interest says
1096
+ * `TLS_RECORD_DONE` and `readWait` answers -32; call `close` then.
1097
+ * Answers the interest.
1098
+ */
1099
+ shutdown(): i32 {
1100
+ this.conn.close()
1101
+ return this.flush()
1102
+ }
1103
+
1104
+ /**
1105
+ * Ends the connection at once: a `close_notify` unless it already failed
1106
+ * or closed, one attempt to send what is left, every key wiped and the
1107
+ * socket closed, which also takes it out of every loop.
1108
+ */
1109
+ close(): void {
1110
+ if (this.socket < 0) {
1111
+ this.conn.wipe()
1112
+ return
1113
+ }
1114
+ if (!this.connecting) {
1115
+ this.conn.close()
1116
+ this.flush()
1117
+ }
1118
+ netClose(this.socket)
1119
+ this.socket = -1
1120
+ this.conn.wipe()
1121
+ }
1122
+ }
1123
+
1124
+ /** The monotonic clock `timeoutMs` milliseconds from now. */
1125
+ const tlsTcpDeadline = (timeoutMs: i32): i64 => monotonicNanos() + toI64(timeoutMs) * TLS_TCP_NANOS_PER_MS
1126
+
1127
+ /**
1128
+ * Opens a TLS 1.3 connection to the server at `addr` (`nish:net`'s 18-byte
1129
+ * form) for `serverName`, offering `alpn`, authenticated against the
1130
+ * system's roots, and waits in a loop of its own until the handshake is done
1131
+ * and the client's last flight sent, or it fails, or `timeoutMs` milliseconds
1132
+ * pass, reading the roots included. Answers the client either way:
1133
+ * `open()` says whether it opened; otherwise `failure()` says why — the TCP
1134
+ * failure, the alert, or `TLS_TCP_TIMED_OUT`, the client closed. Nothing is
1135
+ * looked up: to reach a name, resolve it first with `nish/net/dns`'s
1136
+ * `resolveHost`; the name the certificate must carry is always `serverName`,
1137
+ * never one taken from `addr`. The open client's socket is in no loop: the
1138
+ * program adds it to its own, or uses `writeAll` and `readWait`. The roots
1139
+ * are read on every call; a program that connects often reads them once with
1140
+ * `tlsClientSystemTrust` and passes them to `tlsConnectWith`.
1141
+ */
1142
+ export const tlsConnect = (addr: u8[], serverName: string, alpn: string[], timeoutMs: i32): TlsTcpClient => {
1143
+ const end: i64 = tlsTcpDeadline(timeoutMs)
1144
+ return tlsConnectBy(addr, new TlsClientConfig(serverName, alpn), tlsClientSystemTrust(), end)
1145
+ }
1146
+
1147
+ /** `tlsConnect` under a `config` and `trust` of the caller's. */
1148
+ export const tlsConnectWith = (
1149
+ addr: u8[],
1150
+ config: TlsClientConfig,
1151
+ trust: TlsClientTrust,
1152
+ timeoutMs: i32
1153
+ ): TlsTcpClient => tlsConnectBy(addr, config, trust, tlsTcpDeadline(timeoutMs))
1154
+
1155
+ /** `tlsConnectWith`, waiting until `end` on the monotonic clock. */
1156
+ const tlsConnectBy = (addr: u8[], config: TlsClientConfig, trust: TlsClientTrust, end: i64): TlsTcpClient => {
1157
+ const client: TlsTcpClient = new TlsTcpClient(addr, config, trust)
1158
+ if (client.fd() < 0) {
1159
+ return client
1160
+ }
1161
+ const loop: i32 = client.ownLoop()
1162
+ if (loop < 0) {
1163
+ client.connectError = loop
1164
+ client.close()
1165
+ return client
1166
+ }
1167
+ // The connection opens on the server's Finished, with the client's own
1168
+ // perhaps still queued: it is sent before the client is handed back.
1169
+ while (!client.open() || client.sending()) {
1170
+ const turned: i32 = client.turn(loop, end)
1171
+ if (turned === TLS_RECORD_DONE) {
1172
+ break
1173
+ }
1174
+ // A refusal whose alert could not be flushed in time is still the
1175
+ // refusal: the timeout is the reason only while the handshake lives.
1176
+ if (turned < 0) {
1177
+ if (client.conn.state !== TLS_RECORD_STATE_FAILED) {
1178
+ client.connectError = turned
1179
+ }
1180
+ break
1181
+ }
1182
+ }
1183
+ netClose(loop)
1184
+ if (!client.open() || client.connectError !== 0) {
1185
+ client.close()
1186
+ }
1187
+ return client
1188
+ }