@amritk/nish-x86_64-linux 0.32.0 → 0.34.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.
- package/INSTALL.md +10 -8
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/core.d.ts +83 -6
- package/runtime/core.mjs +90 -0
- package/runtime/nish.d.ts +83 -6
- package/runtime/nish.h +27 -4
- package/runtime/runtime-chacha20.c +366 -0
- package/scripts/build.sh +12 -9
- package/std/README.md +2 -1
- package/std/crypto/bcrypt-batch.ts +702 -0
- package/std/net/http1-codec.ts +81 -41
- package/std/net/http1-server.ts +62 -47
package/std/net/http1-codec.ts
CHANGED
|
@@ -506,17 +506,19 @@ const http1StartRequest = (p: Http1Parser): void => {
|
|
|
506
506
|
}
|
|
507
507
|
|
|
508
508
|
/**
|
|
509
|
-
*
|
|
510
|
-
*
|
|
511
|
-
*
|
|
512
|
-
*
|
|
513
|
-
* `
|
|
514
|
-
*
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
*
|
|
509
|
+
* The bytes of `x` below 32 or equal to 127 — a control byte or DEL, which
|
|
510
|
+
* `http1TakeLine` stops at — as their high bits: zero when there is none,
|
|
511
|
+
* and else the lowest set bit is the first such byte's. Two of the bit
|
|
512
|
+
* tricks of "Bit Twiddling Hacks" (S. E. Anderson, public domain): a byte
|
|
513
|
+
* below `n` leaves its high bit set in `(x - n * ones) & ~x`, for `n` up to
|
|
514
|
+
* 128, and `127` is the byte that is zero in `x ^ 127 * ones`. A byte from
|
|
515
|
+
* 128 up has its high bit set in `x`, so `~x` clears it: obs-text never
|
|
516
|
+
* matches. A borrow only runs up from a byte that matched, so the bits above
|
|
517
|
+
* the first match may be false and the lowest is exact, in each mask and so
|
|
518
|
+
* in their union. Each subtraction is masked to 64 bits because a `u64` is a
|
|
519
|
+
* BigInt under Node, which does not wrap; natively the mask is a no-op.
|
|
518
520
|
*/
|
|
519
|
-
const
|
|
521
|
+
const http1ControlBits = (x: u64): u64 => {
|
|
520
522
|
const ones32: u64 = toU64(16843009)
|
|
521
523
|
const ones: u64 = (ones32 << toU64(32)) | ones32
|
|
522
524
|
const low32: u64 = 0xffffffff
|
|
@@ -525,7 +527,18 @@ const http1WordHasControl = (x: u64): boolean => {
|
|
|
525
527
|
const below: u64 = (x - ones * toU64(32)) & all & ~x & high
|
|
526
528
|
const y: u64 = x ^ (ones * toU64(127))
|
|
527
529
|
const del: u64 = (y - ones) & all & ~y & high
|
|
528
|
-
return
|
|
530
|
+
return below | del
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/** Which byte of a word `bits`'s lowest set bit is in, 0 to 7; `bits` is not zero. */
|
|
534
|
+
const http1FirstByte = (bits: u64): i32 => {
|
|
535
|
+
const mask32: u64 = 0xffffffff
|
|
536
|
+
const lo: u32 = toU32(bits & mask32)
|
|
537
|
+
if (lo !== toU32(0)) {
|
|
538
|
+
return (toI32(31) - toI32(Math.clz32(lo & (toU32(0) - lo)))) >> toI32(3)
|
|
539
|
+
}
|
|
540
|
+
const hi: u32 = toU32(bits >> toU64(32))
|
|
541
|
+
return toI32(4) + ((toI32(31) - toI32(Math.clz32(hi & (toU32(0) - hi)))) >> toI32(3))
|
|
529
542
|
}
|
|
530
543
|
|
|
531
544
|
/**
|
|
@@ -585,22 +598,39 @@ const http1TakeLine = (p: Http1Parser, limit: i32, overStatus: i32): i32 => {
|
|
|
585
598
|
}
|
|
586
599
|
while (i < stop) {
|
|
587
600
|
// Most bytes are printable: sixteen at a time while a whole vector is,
|
|
588
|
-
//
|
|
589
|
-
//
|
|
590
|
-
|
|
601
|
+
// and the vector that is not answers where its first control byte is.
|
|
602
|
+
// The bytes after a control byte, and a tail shorter than a vector, go
|
|
603
|
+
// one at a time.
|
|
604
|
+
let found: boolean = false
|
|
605
|
+
while (!found && i >= 0 && i <= stop - 16 && i <= toI32(buf.length) - 16) {
|
|
591
606
|
const v: u8x16 = loadU8x16(buf, i)
|
|
592
|
-
|
|
593
|
-
|
|
607
|
+
const low: u64 = http1ControlBits(http1Lanes(v, 0))
|
|
608
|
+
if (low !== toU64(0)) {
|
|
609
|
+
i += http1FirstByte(low)
|
|
610
|
+
found = true
|
|
611
|
+
} else {
|
|
612
|
+
const high: u64 = http1ControlBits(http1Lanes(v, 8))
|
|
613
|
+
if (high !== toU64(0)) {
|
|
614
|
+
i += 8 + http1FirstByte(high)
|
|
615
|
+
found = true
|
|
616
|
+
} else {
|
|
617
|
+
i += 16
|
|
618
|
+
}
|
|
594
619
|
}
|
|
595
|
-
i += 16
|
|
596
620
|
}
|
|
597
|
-
while (i >= 0 && i < stop && i < toI32(buf.length) && buf[i] >= 32 && buf[i] !== 127) {
|
|
621
|
+
while (!found && i >= 0 && i < stop && i < toI32(buf.length) && buf[i] >= 32 && buf[i] !== 127) {
|
|
598
622
|
i += 1
|
|
599
623
|
}
|
|
600
624
|
if (i < 0 || i >= stop || i >= toI32(buf.length)) {
|
|
601
625
|
break
|
|
602
626
|
}
|
|
603
|
-
|
|
627
|
+
let c: i32 = toI32(buf[i])
|
|
628
|
+
if (c === 13 && i + 1 < stop && i + 1 < toI32(buf.length) && buf[i + 1] === 10) {
|
|
629
|
+
// CRLF, the end of almost every line: its LF is the next byte, so it
|
|
630
|
+
// is taken here rather than by another trip through the scan.
|
|
631
|
+
i += 1
|
|
632
|
+
c = 10
|
|
633
|
+
}
|
|
604
634
|
if (c === 10) {
|
|
605
635
|
if (i === p.start || buf[i - 1] !== 13) {
|
|
606
636
|
return http1Fail(p, 400, "a line ended in a bare LF")
|
|
@@ -636,9 +666,11 @@ const http1TakeLine = (p: Http1Parser, limit: i32, overStatus: i32): i32 => {
|
|
|
636
666
|
* `c - 64` and `c - 96` of each: `!` `#`-`'` `*` `+` `-` `.` and the digits
|
|
637
667
|
* in the first (0x03ff6cfa); the upper-case letters, `^` and `_` in the
|
|
638
668
|
* second (0xc7fffffe); `` ` ``, the lower-case letters, `|` and `~` in the
|
|
639
|
-
* third (0x57ffffff). No byte below 33 or above 126 is a tchar.
|
|
640
|
-
*
|
|
641
|
-
*
|
|
669
|
+
* third (0x57ffffff). No byte below 33 or above 126 is a tchar.
|
|
670
|
+
* `http1IsTokenByte` reads them as two 64-bit masks, one for the bytes 0 to
|
|
671
|
+
* 63 and one for 64 to 127, so the test is a select and a shift with no
|
|
672
|
+
* branch on the byte: a name or method scan is the parser's hottest loop
|
|
673
|
+
* after the line scan (`bench/http1-parse`).
|
|
642
674
|
*/
|
|
643
675
|
const HTTP1_TCHAR_32: i32 = 67071226
|
|
644
676
|
const HTTP1_TCHAR_64: i32 = -939524098
|
|
@@ -646,16 +678,10 @@ const HTTP1_TCHAR_96: i32 = 1476395007
|
|
|
646
678
|
|
|
647
679
|
/** Whether `c` is a tchar of RFC 9110 §5.6.2, the bytes a token is made of. */
|
|
648
680
|
export const http1IsTokenByte = (c: i32): boolean => {
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
if (c < 64) {
|
|
654
|
-
mask = HTTP1_TCHAR_32
|
|
655
|
-
} else if (c < 96) {
|
|
656
|
-
mask = HTTP1_TCHAR_64
|
|
657
|
-
}
|
|
658
|
-
return ((mask >> (c & 31)) & 1) !== 0
|
|
681
|
+
const low: u64 = toU64(toU32(HTTP1_TCHAR_32)) << toU64(32)
|
|
682
|
+
const high: u64 = (toU64(toU32(HTTP1_TCHAR_96)) << toU64(32)) | toU64(toU32(HTTP1_TCHAR_64))
|
|
683
|
+
const word: u64 = c < 64 ? low : high
|
|
684
|
+
return c >= 0 && c < 128 && ((word >> toU64(c & 63)) & toU64(1)) !== toU64(0)
|
|
659
685
|
}
|
|
660
686
|
|
|
661
687
|
/** Whether `c` may appear in a field value (RFC 9110 §5.5): HTAB, SP, VCHAR or obs-text. */
|
|
@@ -978,9 +1004,30 @@ const http1FieldLine = (p: Http1Parser, keep: boolean): i32 => {
|
|
|
978
1004
|
if (a >= 0 && a < b && (buf[a] === 32 || buf[a] === 9)) {
|
|
979
1005
|
return http1Fail(p, 400, "a field line that starts with whitespace (obs-fold)")
|
|
980
1006
|
}
|
|
1007
|
+
// A kept line is copied into the head first, and its name is read there,
|
|
1008
|
+
// each upper-case letter lowered as the scan passes it: one walk over the
|
|
1009
|
+
// name, not a check of `buf` and then a second walk of the copy. The copy
|
|
1010
|
+
// of a line refused below is never read, since a refusal ends the parse.
|
|
1011
|
+
let at: i32 = 0
|
|
981
1012
|
let i: i32 = a
|
|
982
|
-
|
|
983
|
-
|
|
1013
|
+
if (keep) {
|
|
1014
|
+
at = http1HeadAppend(p, a, b)
|
|
1015
|
+
const head: u8[] = p.head
|
|
1016
|
+
let k: i32 = at
|
|
1017
|
+
const stop: i32 = at + (b - a)
|
|
1018
|
+
while (k >= 0 && k < stop && k < toI32(head.length)) {
|
|
1019
|
+
const c: i32 = toI32(head[k])
|
|
1020
|
+
if (!http1IsTokenByte(c)) {
|
|
1021
|
+
break
|
|
1022
|
+
}
|
|
1023
|
+
head[k] = toU8(c >= 65 && c <= 90 ? c + 32 : c)
|
|
1024
|
+
k += 1
|
|
1025
|
+
}
|
|
1026
|
+
i = a + (k - at)
|
|
1027
|
+
} else {
|
|
1028
|
+
while (i >= 0 && i < b && i < toI32(buf.length) && http1IsTokenByte(toI32(buf[i]))) {
|
|
1029
|
+
i += 1
|
|
1030
|
+
}
|
|
984
1031
|
}
|
|
985
1032
|
if (i === a || i >= b || buf[i] !== 58) {
|
|
986
1033
|
return http1Fail(p, 400, "a malformed field name")
|
|
@@ -1005,13 +1052,6 @@ const http1FieldLine = (p: Http1Parser, keep: boolean): i32 => {
|
|
|
1005
1052
|
while (valueEnd > valueStart && valueEnd - 1 >= 0 && http1IsOws(toI32(buf[valueEnd - 1]))) {
|
|
1006
1053
|
valueEnd -= 1
|
|
1007
1054
|
}
|
|
1008
|
-
const at: i32 = http1HeadAppend(p, a, b)
|
|
1009
|
-
const head: u8[] = p.head
|
|
1010
|
-
for (let k: i32 = at; k < at + (nameEnd - a) && k < toI32(head.length); k += 1) {
|
|
1011
|
-
if (k >= 0 && head[k] >= 65 && head[k] <= 90) {
|
|
1012
|
-
head[k] = head[k] + 32
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
1055
|
const base: i32 = 4 * p.headerCount
|
|
1016
1056
|
http1SpanSet(p, base + 3, at + (valueEnd - a))
|
|
1017
1057
|
// The last of the four is set first, so the array has room for them all.
|
package/std/net/http1-server.ts
CHANGED
|
@@ -525,28 +525,19 @@ export class Http1OutputPool {
|
|
|
525
525
|
* `output[outputStart .. outputEnd)` for its carrier to send.
|
|
526
526
|
*/
|
|
527
527
|
export class Http1Connection {
|
|
528
|
-
//
|
|
529
|
-
//
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
* put another, to share it or to pin its clock.
|
|
536
|
-
*/
|
|
537
|
-
date: HttpDate
|
|
528
|
+
// What a request reads and writes comes first, so that `inputRoom`, `feed`,
|
|
529
|
+
// `next`, `respond`, `write`, `end`, `consume`, `release` and `park` touch
|
|
530
|
+
// the front of the object: the parser, the output and its pool, the clocks,
|
|
531
|
+
// the state and the flags. What a WebSocket, an error or a configured cap
|
|
532
|
+
// needs follows. Within each part the layout packs as widest first does:
|
|
533
|
+
// the narrow fields of the front end on a 4-byte boundary and the cold
|
|
534
|
+
// 32-bit fields bring the references behind them to an 8-byte one.
|
|
538
535
|
/**
|
|
539
536
|
* The request parser: its head (`methodIs`, `targetIs`, `header`, spans) is
|
|
540
537
|
* the current request's from its `H1_REQUEST` until its response is done,
|
|
541
538
|
* `H1_END` included, however much of a pipelined request is fed behind it.
|
|
542
539
|
*/
|
|
543
540
|
parser: Http1Parser
|
|
544
|
-
/**
|
|
545
|
-
* The frame decoder: made when the slot first switches to WebSocket, so a
|
|
546
|
-
* slot that only ever speaks HTTP never holds its buffers, then kept for
|
|
547
|
-
* the slot's later peers.
|
|
548
|
-
*/
|
|
549
|
-
ws: WsDecoder | null
|
|
550
541
|
/**
|
|
551
542
|
* Bytes to send are `output[outputStart .. outputEnd)`. Empty until the
|
|
552
543
|
* connection first needs room to write: then its own buffer, made once, or
|
|
@@ -560,27 +551,17 @@ export class Http1Connection {
|
|
|
560
551
|
/** Where the parser's input and head buffers go while the slot rests, with `Http1Config.idleInput`; null otherwise. */
|
|
561
552
|
inputs: Http1OutputPool | null
|
|
562
553
|
heads: Http1OutputPool | null
|
|
563
|
-
/**
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
554
|
+
/**
|
|
555
|
+
* The `Date` every final response carries. The connection makes its own;
|
|
556
|
+
* `Http1Server` and `Http1TlsServer` put theirs here, so that every slot of a
|
|
557
|
+
* pool shares one and the pool formats at most once a second. A program may
|
|
558
|
+
* put another, to share it or to pin its clock.
|
|
559
|
+
*/
|
|
560
|
+
date: HttpDate
|
|
569
561
|
/** When the carrier last moved a byte for the connection, in `monotonicNanos`. */
|
|
570
562
|
lastActive: i64 = 0
|
|
571
563
|
/** When the head being read began to arrive, in `monotonicNanos`; meaningful while `headTimed`. */
|
|
572
564
|
headSince: i64 = 0
|
|
573
|
-
outputStart: i32 = 0
|
|
574
|
-
outputEnd: i32 = 0
|
|
575
|
-
dataStart: i32 = 0
|
|
576
|
-
dataLength: i32 = 0
|
|
577
|
-
/** `H1_WS_MESSAGE`'s opcode: text or binary. */
|
|
578
|
-
opcode: i32 = 0
|
|
579
|
-
/** `H1_ERROR`'s HTTP status or WebSocket close code, or `H1_WS_CLOSE`'s close code. */
|
|
580
|
-
status: i32 = 0
|
|
581
|
-
/** The most input the connection buffers, unread: the cap `inputRoom` holds it to. */
|
|
582
|
-
bufferLimit: i32 = 0
|
|
583
|
-
pongLength: i32 = 0
|
|
584
565
|
state: i32 = 0
|
|
585
566
|
response: i32 = 0
|
|
586
567
|
/**
|
|
@@ -590,12 +571,23 @@ export class Http1Connection {
|
|
|
590
571
|
responseFraming: i32 = 0
|
|
591
572
|
/** A response with a length: the bytes it still owes. */
|
|
592
573
|
responseLeft: i32 = 0
|
|
593
|
-
|
|
594
|
-
|
|
574
|
+
outputStart: i32 = 0
|
|
575
|
+
outputEnd: i32 = 0
|
|
576
|
+
/**
|
|
577
|
+
* `output.length`, kept beside the offsets so that `acquire` and `release`
|
|
578
|
+
* learn whether the connection holds a buffer without reading an array:
|
|
579
|
+
* the empty one is out of cache by the time a resting connection's next
|
|
580
|
+
* request comes. Only the connection assigns `output`, and with it this.
|
|
581
|
+
*/
|
|
582
|
+
outputCap: i32 = 0
|
|
583
|
+
/** The most input the connection buffers, unread: the cap `inputRoom` holds it to. */
|
|
584
|
+
bufferLimit: i32 = 0
|
|
595
585
|
/** A request's head has been read and its exchange is not over. */
|
|
596
586
|
requestOpen: boolean = false
|
|
597
587
|
/** The request's body has been read to its end. */
|
|
598
588
|
requestEnded: boolean = false
|
|
589
|
+
/** The response carries no body (HEAD, 204, 304): what is written is dropped. */
|
|
590
|
+
responseEmpty: boolean = false
|
|
599
591
|
/** The connection closes once this exchange is over. */
|
|
600
592
|
closeAfter: boolean = false
|
|
601
593
|
/** The program accepted the request's WebSocket handshake. */
|
|
@@ -607,6 +599,13 @@ export class Http1Connection {
|
|
|
607
599
|
inputEnded: boolean = false
|
|
608
600
|
/** A head has begun to arrive and is not read whole yet: `headSince` is its deadline's start. */
|
|
609
601
|
headTimed: boolean = false
|
|
602
|
+
dataStart: i32 = 0
|
|
603
|
+
dataLength: i32 = 0
|
|
604
|
+
/** `H1_WS_MESSAGE`'s opcode: text or binary. */
|
|
605
|
+
opcode: i32 = 0
|
|
606
|
+
/** `H1_ERROR`'s HTTP status or WebSocket close code, or `H1_WS_CLOSE`'s close code. */
|
|
607
|
+
status: i32 = 0
|
|
608
|
+
pongLength: i32 = 0
|
|
610
609
|
/**
|
|
611
610
|
* The TCP maximum segment size the program sets for this connection, for
|
|
612
611
|
* `respondEncoded` under `HttpCompressor.segmentMode`; 0, the default,
|
|
@@ -616,6 +615,19 @@ export class Http1Connection {
|
|
|
616
615
|
mss: i32 = 0
|
|
617
616
|
/** The MSS `Http1Server.accept` read from this connection's socket (`netMss`), or 0 when it reports none. */
|
|
618
617
|
socketMss: i32 = 0
|
|
618
|
+
config: Http1Config
|
|
619
|
+
/**
|
|
620
|
+
* The frame decoder: made when the slot first switches to WebSocket, so a
|
|
621
|
+
* slot that only ever speaks HTTP never holds its buffers, then kept for
|
|
622
|
+
* the slot's later peers.
|
|
623
|
+
*/
|
|
624
|
+
ws: WsDecoder | null
|
|
625
|
+
/** `H1_BODY`'s and `H1_WS_MESSAGE`'s bytes. */
|
|
626
|
+
data: u8[]
|
|
627
|
+
/** A ping's payload, kept until its pong fits: empty until the first ping. */
|
|
628
|
+
pong: u8[]
|
|
629
|
+
/** No fields, for the responses the connection writes itself: made once. */
|
|
630
|
+
noFields: u8[][]
|
|
619
631
|
|
|
620
632
|
/** A connection under `config`. A cap outside what the connection can work with panics. */
|
|
621
633
|
constructor(config: Http1Config) {
|
|
@@ -759,10 +771,15 @@ export class Http1Connection {
|
|
|
759
771
|
if (this.writeHeld || this.inputEnded || this.state === H1_S_CLOSING || this.state === H1_S_FAILED) {
|
|
760
772
|
return 0
|
|
761
773
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
774
|
+
let room: i32 = 0
|
|
775
|
+
if (this.state === H1_S_WEBSOCKET) {
|
|
776
|
+
// Only a WebSocket reads the decoder and the configured message cap,
|
|
777
|
+
// so a request's check stays on the connection's front fields.
|
|
778
|
+
const ws: WsDecoder | null = this.ws
|
|
779
|
+
room = this.inputLimit() - (ws !== null ? ws.end - ws.start : this.parser.buffered())
|
|
780
|
+
} else {
|
|
781
|
+
room = this.bufferLimit - this.parser.buffered()
|
|
782
|
+
}
|
|
766
783
|
return room > 0 ? room : 0
|
|
767
784
|
}
|
|
768
785
|
|
|
@@ -809,11 +826,13 @@ export class Http1Connection {
|
|
|
809
826
|
* the pool has no free buffer, or for a connection's own, once.
|
|
810
827
|
*/
|
|
811
828
|
acquire(): void {
|
|
812
|
-
if (
|
|
829
|
+
if (this.outputCap > 0) {
|
|
813
830
|
return
|
|
814
831
|
}
|
|
815
832
|
const pool: Http1OutputPool | null = this.pool
|
|
816
|
-
|
|
833
|
+
const output: u8[] = pool !== null ? pool.take() : new Array<u8>(this.config.outputSize)
|
|
834
|
+
this.output = output
|
|
835
|
+
this.outputCap = toI32(output.length)
|
|
817
836
|
this.outputStart = 0
|
|
818
837
|
this.outputEnd = 0
|
|
819
838
|
}
|
|
@@ -825,16 +844,12 @@ export class Http1Connection {
|
|
|
825
844
|
*/
|
|
826
845
|
release(): void {
|
|
827
846
|
const pool: Http1OutputPool | null = this.pool
|
|
828
|
-
if (
|
|
829
|
-
pool === null ||
|
|
830
|
-
toI32(this.output.length) === 0 ||
|
|
831
|
-
this.outputEnd > this.outputStart ||
|
|
832
|
-
this.writeHeld
|
|
833
|
-
) {
|
|
847
|
+
if (pool === null || this.outputCap === 0 || this.outputEnd > this.outputStart || this.writeHeld) {
|
|
834
848
|
return
|
|
835
849
|
}
|
|
836
850
|
pool.give(this.output)
|
|
837
851
|
this.output = this.noOutput
|
|
852
|
+
this.outputCap = 0
|
|
838
853
|
this.outputStart = 0
|
|
839
854
|
this.outputEnd = 0
|
|
840
855
|
}
|