@mark1russell7/rigor-proxy 0.1.0 → 0.2.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/README.md +15 -1
- package/build/answer.d.ts +8 -0
- package/build/answer.d.ts.map +1 -0
- package/build/answer.js +25 -0
- package/build/answer.js.map +1 -0
- package/build/applier.d.ts +5 -3
- package/build/applier.d.ts.map +1 -1
- package/build/applier.js +21 -1
- package/build/applier.js.map +1 -1
- package/build/index.d.ts +2 -1
- package/build/index.js +2 -1
- package/build/proxy.js +9 -1
- package/build/proxy.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -36,9 +36,23 @@ A fault with a `frame` point counts frames, and `split`, `reorder` and `duplicat
|
|
|
36
36
|
- The proxy works on TCP. It cannot read a TLS stream, thus a TLS stream has only `tcp` frames.
|
|
37
37
|
- `framers` replaces the framers of `protocol` with your framers.
|
|
38
38
|
|
|
39
|
+
## An answer
|
|
40
|
+
|
|
41
|
+
An `answer` puts an HTTP/1.1 response from the plan in place of the server. The proxy writes it to the client, and ends the connection with a FIN.
|
|
42
|
+
|
|
43
|
+
| Point | What the client and the server see |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `up`, `{ frame: 0 }` | The server does not get the request. The client gets the answer. |
|
|
46
|
+
| `down`, `{ frame: 0 }` | The server gets the request. The client gets the answer in place of the response of the server. |
|
|
47
|
+
| `down`, a later frame | The client gets a part of the response of the server, then the answer. This stream is not valid HTTP. |
|
|
48
|
+
|
|
49
|
+
- The proxy writes the fields in their order, then `content-length` and `connection: close`. A 204 or a 304 has no `content-length`.
|
|
50
|
+
- After an answer, no byte of the server goes to the client. The summary of the connection says `cut`.
|
|
51
|
+
- `answerBytes` gives the bytes of an answer.
|
|
52
|
+
|
|
39
53
|
## The pure parts
|
|
40
54
|
|
|
41
|
-
The framers (`tcpFramers`, `httpFramers`, `webSocketFramer`)
|
|
55
|
+
The framers (`tcpFramers`, `httpFramers`, `webSocketFramer`), the applier (`applierOf`) and `answerBytes` read no clock and write no socket. A test gives them bytes and times, and it examines the writes that they give back.
|
|
42
56
|
|
|
43
57
|
## Tests
|
|
44
58
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AnswerFault } from "@mark1russell7/rigor-chaos";
|
|
2
|
+
/**
|
|
3
|
+
* This function gives the bytes of the HTTP/1.1 response of an `answer`. The status line has the reason of Node.js, or
|
|
4
|
+
* no reason for a status that Node.js does not know. The fields go in their order. Then `content-length` and
|
|
5
|
+
* `connection: close` follow. A response with the status 204 or 304 has no `content-length`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function answerBytes(answer: Omit<AnswerFault, "kind">): Uint8Array;
|
|
8
|
+
//# sourceMappingURL=answer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"answer.d.ts","names":[],"sources":["../src/answer.ts"],"mappings":";;;;;;wBAWgB,YAAY,QAAQ,KAAK,uBAAuB"}
|
package/build/answer.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { STATUS_CODES } from "node:http";
|
|
2
|
+
// src/answer.ts
|
|
3
|
+
/** These statuses have no body, thus their response has no `content-length`. */
|
|
4
|
+
const NO_BODY = /* @__PURE__ */ new Set([204, 304]);
|
|
5
|
+
/**
|
|
6
|
+
* This function gives the bytes of the HTTP/1.1 response of an `answer`. The status line has the reason of Node.js, or
|
|
7
|
+
* no reason for a status that Node.js does not know. The fields go in their order. Then `content-length` and
|
|
8
|
+
* `connection: close` follow. A response with the status 204 or 304 has no `content-length`.
|
|
9
|
+
*/
|
|
10
|
+
function answerBytes(answer) {
|
|
11
|
+
const encoder = new TextEncoder();
|
|
12
|
+
const body = encoder.encode(answer.body ?? "");
|
|
13
|
+
const lines = [`HTTP/1.1 ${answer.status} ${STATUS_CODES[answer.status] ?? ""}`];
|
|
14
|
+
for (const [name, value] of Object.entries(answer.fields ?? {})) lines.push(`${name}: ${value}`);
|
|
15
|
+
if (!NO_BODY.has(answer.status)) lines.push(`content-length: ${body.length}`);
|
|
16
|
+
lines.push("connection: close");
|
|
17
|
+
const head = encoder.encode(`${lines.join("\r\n")}\r\n\r\n`);
|
|
18
|
+
const out = new Uint8Array(head.length + body.length);
|
|
19
|
+
out.set(head);
|
|
20
|
+
out.set(body, head.length);
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
export { answerBytes };
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=answer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"answer.js","names":[],"sources":["../src/answer.ts"],"sourcesContent":["import { STATUS_CODES } from \"node:http\";\nimport type { AnswerFault } from \"@mark1russell7/rigor-chaos\";\n\n/** These statuses have no body, thus their response has no `content-length`. */\nconst NO_BODY: ReadonlySet<number> = new Set([204, 304]);\n\n/**\n * This function gives the bytes of the HTTP/1.1 response of an `answer`. The status line has the reason of Node.js, or\n * no reason for a status that Node.js does not know. The fields go in their order. Then `content-length` and\n * `connection: close` follow. A response with the status 204 or 304 has no `content-length`.\n */\nexport function answerBytes(answer: Omit<AnswerFault, \"kind\">): Uint8Array {\n const encoder = new TextEncoder();\n const body = encoder.encode(answer.body ?? \"\");\n const lines = [`HTTP/1.1 ${answer.status} ${STATUS_CODES[answer.status] ?? \"\"}`];\n for (const [name, value] of Object.entries(answer.fields ?? {})) lines.push(`${name}: ${value}`);\n if (!NO_BODY.has(answer.status)) lines.push(`content-length: ${body.length}`);\n lines.push(\"connection: close\");\n const head = encoder.encode(`${lines.join(\"\\r\\n\")}\\r\\n\\r\\n`);\n const out = new Uint8Array(head.length + body.length);\n out.set(head);\n out.set(body, head.length);\n return out;\n}\n"],"mappings":";;;AAIA,MAAM,0BAA+B,IAAI,IAAI,CAAC,KAAK,GAAG,CAAC;;;;;;AAOvD,SAAgB,YAAY,QAA+C;CACzE,MAAM,UAAU,IAAI,YAAY;CAChC,MAAM,OAAO,QAAQ,OAAO,OAAO,QAAQ,EAAE;CAC7C,MAAM,QAAQ,CAAC,YAAY,OAAO,OAAO,GAAG,aAAa,OAAO,WAAW,IAAI;CAC/E,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,OAAO,UAAU,CAAC,CAAC,GAAG,MAAM,KAAK,GAAG,KAAK,IAAI,OAAO;CAC/F,IAAI,CAAC,QAAQ,IAAI,OAAO,MAAM,GAAG,MAAM,KAAK,mBAAmB,KAAK,QAAQ;CAC5E,MAAM,KAAK,mBAAmB;CAC9B,MAAM,OAAO,QAAQ,OAAO,GAAG,MAAM,KAAK,MAAM,EAAE,SAAS;CAC3D,MAAM,MAAM,IAAI,WAAW,KAAK,SAAS,KAAK,MAAM;CACpD,IAAI,IAAI,IAAI;CACZ,IAAI,IAAI,MAAM,KAAK,MAAM;CACzB,OAAO;AACT"}
|
package/build/applier.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Direction, Fault,
|
|
1
|
+
import { Direction, Fault, FaultPlan } from "@mark1russell7/rigor-chaos";
|
|
2
2
|
/**
|
|
3
3
|
* An applier puts the faults of a plan into the frames of one direction of one connection. It is pure: it reads no
|
|
4
4
|
* clock and writes no socket. Each time is in ms from the start of the connection. The proxy gives it each frame
|
|
@@ -15,14 +15,14 @@ export interface FaultRecord {
|
|
|
15
15
|
readonly index: number;
|
|
16
16
|
readonly connection: number;
|
|
17
17
|
readonly direction: Direction;
|
|
18
|
-
readonly kind:
|
|
18
|
+
readonly kind: Fault["kind"];
|
|
19
19
|
/** The time of the fault, in ms from the start of the connection. */
|
|
20
20
|
readonly at: number;
|
|
21
21
|
/** The bytes of the direction before the fault. */
|
|
22
22
|
readonly byte: number;
|
|
23
23
|
/** The frames of the direction before the fault. */
|
|
24
24
|
readonly frame: number;
|
|
25
|
-
/** The bytes of the frames that a `split`, a `duplicate` or a `reorder` changed
|
|
25
|
+
/** The bytes of the frames that a `split`, a `duplicate` or a `reorder` changed, or the bytes of the response of an `answer`. It is 0 for the other kinds. */
|
|
26
26
|
readonly bytes: number;
|
|
27
27
|
}
|
|
28
28
|
/** The result of one call to an applier. */
|
|
@@ -36,6 +36,8 @@ export interface Step {
|
|
|
36
36
|
readonly kind: "cut" | "reset";
|
|
37
37
|
readonly at: number;
|
|
38
38
|
};
|
|
39
|
+
/** The response of an `answer`, for the client in both directions. After it, no byte goes to the client. A `cut` follows it. */
|
|
40
|
+
readonly answer?: Send;
|
|
39
41
|
}
|
|
40
42
|
/** An applier of one direction of one connection. */
|
|
41
43
|
export interface Applier {
|
package/build/applier.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"applier.d.ts","names":[],"sources":["../src/applier.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"applier.d.ts","names":[],"sources":["../src/applier.ts"],"mappings":";;;;;;;iBAUiB;WACN,OAAO;WACP;;;iBAIM;;WAEN;WACA;WACA,WAAW;WACX,MAAM;;WAEN;;WAEA;;WAEA;;WAEA;;;iBAIM;WACN,gBAAgB;WAChB,kBAAkB;;WAElB;;WAEA;aAAmB;aAAgC;;;WAEnD,SAAS;;;iBAIH;;EAEf,KAAK,OAAO,YAAY,cAAc;;EAEtC,KAAK,cAAc;;EAEnB,IAAI,cAAc;;WAET;;EAET;aAA+B;aAAwB,OAAO;;;;wBA8BhD,WAAW,OAAO,YAAY,iBAAiB;;wBAQ/C,UAAU,MAAM,WAAW,oBAAoB,WAAW,YAAY"}
|
package/build/applier.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { answerBytes } from "./answer.js";
|
|
1
2
|
import { triggerOf } from "@mark1russell7/rigor-chaos";
|
|
2
3
|
// src/applier.ts
|
|
3
4
|
/** This function cuts bytes into `pieces` parts of near the same length. Each part has one byte or more. */
|
|
@@ -36,6 +37,7 @@ function applierOf(plan, connection, direction) {
|
|
|
36
37
|
let records = [];
|
|
37
38
|
let pauseUntil;
|
|
38
39
|
let close;
|
|
40
|
+
let answer;
|
|
39
41
|
const record = (entry, at, bytes, where = {
|
|
40
42
|
byte,
|
|
41
43
|
frame
|
|
@@ -108,6 +110,22 @@ function applierOf(plan, connection, direction) {
|
|
|
108
110
|
frame
|
|
109
111
|
};
|
|
110
112
|
return;
|
|
113
|
+
case "answer": {
|
|
114
|
+
release(now);
|
|
115
|
+
const bytes = answerBytes(fault);
|
|
116
|
+
record(entry, now, bytes.length);
|
|
117
|
+
const at = direction === "down" ? Math.max(now, last) : now;
|
|
118
|
+
answer = {
|
|
119
|
+
bytes,
|
|
120
|
+
at
|
|
121
|
+
};
|
|
122
|
+
closed = true;
|
|
123
|
+
close = {
|
|
124
|
+
kind: "cut",
|
|
125
|
+
at
|
|
126
|
+
};
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
111
129
|
default: armed.push(entry);
|
|
112
130
|
}
|
|
113
131
|
};
|
|
@@ -158,12 +176,14 @@ function applierOf(plan, connection, direction) {
|
|
|
158
176
|
sends,
|
|
159
177
|
records,
|
|
160
178
|
...pauseUntil === void 0 ? {} : { pauseUntil },
|
|
161
|
-
...close === void 0 ? {} : { close }
|
|
179
|
+
...close === void 0 ? {} : { close },
|
|
180
|
+
...answer === void 0 ? {} : { answer }
|
|
162
181
|
};
|
|
163
182
|
sends = [];
|
|
164
183
|
records = [];
|
|
165
184
|
pauseUntil = void 0;
|
|
166
185
|
close = void 0;
|
|
186
|
+
answer = void 0;
|
|
167
187
|
return out;
|
|
168
188
|
};
|
|
169
189
|
return {
|
package/build/applier.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"applier.js","names":[],"sources":["../src/applier.ts"],"sourcesContent":["import { type Direction, type Fault, type FaultKind, type FaultPlan, triggerOf } from \"@mark1russell7/rigor-chaos\";\n\n/**\n * An applier puts the faults of a plan into the frames of one direction of one connection. It is pure: it reads no\n * clock and writes no socket. Each time is in ms from the start of the connection. The proxy gives it each frame\n * with the time of the read, and it gives back the writes with their times.\n */\n\n/** One write of bytes at a time. The time of each write of one direction is the same as, or more than, the time of the write before it. */\nexport interface Send {\n readonly bytes: Uint8Array;\n readonly at: number;\n}\n\n/** A record of one fault that the proxy applied. */\nexport interface FaultRecord {\n /** The position of the fault in the list of the plan. */\n readonly index: number;\n readonly connection: number;\n readonly direction: Direction;\n readonly kind: FaultKind;\n /** The time of the fault, in ms from the start of the connection. */\n readonly at: number;\n /** The bytes of the direction before the fault. */\n readonly byte: number;\n /** The frames of the direction before the fault. */\n readonly frame: number;\n /** The bytes of the frames that a `split`, a `duplicate` or a `reorder` changed. It is 0 for the other kinds. */\n readonly bytes: number;\n}\n\n/** The result of one call to an applier. */\nexport interface Step {\n readonly sends: readonly Send[];\n readonly records: readonly FaultRecord[];\n /** The direction stops reading until this time. */\n readonly pauseUntil?: number;\n /** The connection closes. A `cut` closes after the writes before it, and a `reset` closes at once. */\n readonly close?: { readonly kind: \"cut\" | \"reset\"; readonly at: number };\n}\n\n/** An applier of one direction of one connection. */\nexport interface Applier {\n /** This method takes one frame that the proxy read at the time `now`. */\n push(frame: Uint8Array, now: number): Step;\n /** This method applies the faults that are due at the time `now`. The proxy uses it at the time `wake`. */\n tick(now: number): Step;\n /** This method sends the frames that a `reorder` holds, at the end of the stream. */\n end(now: number): Step;\n /** The next time that `tick` has work to do, or `undefined`. */\n readonly wake: number | undefined;\n /** The faults of this direction that did not occur, with their positions in the plan. */\n pending(): readonly { readonly index: number; readonly fault: Fault }[];\n}\n\ninterface Entry {\n readonly index: number;\n readonly fault: Fault;\n readonly value: number;\n}\n\ninterface Piece {\n readonly bytes: Uint8Array;\n readonly offset: number;\n}\n\ninterface Unit {\n readonly pieces: readonly Piece[];\n readonly base: number;\n}\n\ninterface Hold {\n readonly entry: Entry;\n readonly units: Unit[];\n left: number;\n readonly until: number;\n readonly at: number;\n readonly byte: number;\n readonly frame: number;\n}\n\n/** This function cuts bytes into `pieces` parts of near the same length. Each part has one byte or more. */\nexport function splitBytes(bytes: Uint8Array, pieces: number): Uint8Array[] {\n const count = Math.max(1, Math.min(pieces, bytes.length));\n const out: Uint8Array[] = [];\n for (let i = 0; i < count; i += 1) out.push(bytes.subarray(Math.floor((i * bytes.length) / count), Math.floor(((i + 1) * bytes.length) / count)));\n return out;\n}\n\n/** This function gives the applier of one direction of one connection of a plan. */\nexport function applierOf(plan: FaultPlan, connection: number, direction: Direction): Applier {\n const lists: Record<\"ms\" | \"byte\" | \"frame\", Entry[]> = { ms: [], byte: [], frame: [] };\n plan.faults.forEach((fault, index) => {\n if (fault.connection !== connection || fault.direction !== direction) return;\n const trigger = triggerOf(fault.at);\n if (trigger !== undefined) lists[trigger.measure].push({ index, fault, value: trigger.value });\n });\n for (const list of Object.values(lists)) list.sort((a, b) => a.value - b.value);\n\n const applied = new Set<number>();\n let byte = 0;\n let frame = 0;\n let last = 0;\n let closed = false;\n let delay: { readonly until: number; readonly ms: number } | undefined;\n let armed: Entry[] = [];\n let hold: Hold | undefined;\n\n let sends: Send[] = [];\n let records: FaultRecord[] = [];\n let pauseUntil: number | undefined;\n let close: Step[\"close\"];\n\n const record = (entry: Entry, at: number, bytes: number, where: { byte: number; frame: number } = { byte, frame }): void => {\n applied.add(entry.index);\n records.push({ index: entry.index, connection, direction, kind: entry.fault.kind, at, byte: where.byte, frame: where.frame, bytes });\n };\n\n const schedule = (unit: Unit, now: number): void => {\n const base = Math.max(now, unit.base);\n for (const piece of unit.pieces) {\n last = Math.max(last, base + piece.offset);\n sends.push({ bytes: piece.bytes, at: last });\n }\n };\n\n const release = (now: number): void => {\n if (hold === undefined) return;\n const held = hold;\n hold = undefined;\n const total = held.units.reduce((sum, unit) => sum + unit.pieces.reduce((s, piece) => s + piece.bytes.length, 0), 0);\n record(held.entry, held.at, total, held);\n for (const unit of [...held.units].reverse()) schedule(unit, now);\n };\n\n /** This function applies one fault at the time `now`. */\n const fire = (entry: Entry, now: number): void => {\n const fault = entry.fault;\n switch (fault.kind) {\n case \"cut\":\n case \"reset\":\n release(now);\n record(entry, now, 0);\n closed = true;\n close = { kind: fault.kind, at: fault.kind === \"cut\" ? Math.max(now, last) : now };\n return;\n case \"stall\":\n record(entry, now, 0);\n pauseUntil = Math.max(pauseUntil ?? 0, now + fault.ms);\n last = Math.max(last, now + fault.ms);\n return;\n case \"delay\":\n record(entry, now, 0);\n delay = { until: now + fault.forMs, ms: Math.max(fault.ms, delay !== undefined && now < delay.until ? delay.ms : 0) };\n return;\n case \"reorder\":\n release(now);\n hold = { entry, units: [], left: fault.count, until: now + fault.holdMs, at: now, byte, frame };\n return;\n default:\n armed.push(entry);\n }\n };\n\n const fireUntil = (list: Entry[], value: number, now: number): void => {\n while (!closed && list.length > 0 && (list[0] as Entry).value <= value) fire(list.shift() as Entry, now);\n };\n\n const fireTimes = (now: number): void => {\n fireUntil(lists.ms, now, now);\n if (!closed && hold !== undefined && now >= hold.until) release(now);\n };\n\n /** This function sends one piece of a frame, with the faults that wait for a frame. */\n const emit = (bytes: Uint8Array, now: number): void => {\n if (delay !== undefined && now >= delay.until) delay = undefined;\n let pieces: Piece[] = [{ bytes, offset: 0 }];\n for (const entry of armed) {\n const fault = entry.fault;\n if (fault.kind === \"split\") {\n const parts = pieces.flatMap((piece) => splitBytes(piece.bytes, fault.pieces));\n pieces = parts.map((part, i) => ({ bytes: part, offset: i * fault.gapMs }));\n } else if (fault.kind === \"duplicate\") {\n const span = pieces.length === 0 ? 0 : (pieces[pieces.length - 1] as Piece).offset;\n pieces = Array.from({ length: fault.copies + 1 }, (_, copy) => pieces.map((piece) => ({ bytes: piece.bytes, offset: piece.offset + copy * span }))).flat();\n }\n record(entry, now, bytes.length);\n }\n armed = [];\n const unit: Unit = { pieces, base: now + (delay?.ms ?? 0) };\n if (hold === undefined) {\n schedule(unit, now);\n return;\n }\n hold.units.push(unit);\n hold.left -= 1;\n if (hold.left === 0) release(now);\n };\n\n const step = (): Step => {\n const out: Step = { sends, records, ...(pauseUntil === undefined ? {} : { pauseUntil }), ...(close === undefined ? {} : { close }) };\n sends = [];\n records = [];\n pauseUntil = undefined;\n close = undefined;\n return out;\n };\n\n return {\n push(bytes, now) {\n if (closed) return step();\n fireTimes(now);\n fireUntil(lists.frame, frame, now);\n let rest = bytes;\n while (!closed && rest.length > 0) {\n fireUntil(lists.byte, byte, now);\n if (closed) break;\n const next = lists.byte[0]?.value;\n const take = next !== undefined && next < byte + rest.length ? next - byte : rest.length;\n emit(rest.subarray(0, take), now);\n byte += take;\n rest = rest.subarray(take);\n }\n frame += 1;\n return step();\n },\n tick(now) {\n if (!closed) fireTimes(now);\n return step();\n },\n end(now) {\n if (!closed) release(now);\n closed = true;\n return step();\n },\n get wake() {\n if (closed) return undefined;\n const times = [lists.ms[0]?.value, hold?.until].filter((time): time is number => time !== undefined);\n return times.length === 0 ? undefined : Math.min(...times);\n },\n pending() {\n return plan.faults.flatMap((fault, index) => (fault.connection === connection && fault.direction === direction && !applied.has(index) ? [{ index, fault }] : []));\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"applier.js","names":[],"sources":["../src/applier.ts"],"sourcesContent":["import { type Direction, type Fault, type FaultPlan, triggerOf } from \"@mark1russell7/rigor-chaos\";\nimport { answerBytes } from \"./answer.ts\";\n\n/**\n * An applier puts the faults of a plan into the frames of one direction of one connection. It is pure: it reads no\n * clock and writes no socket. Each time is in ms from the start of the connection. The proxy gives it each frame\n * with the time of the read, and it gives back the writes with their times.\n */\n\n/** One write of bytes at a time. The time of each write of one direction is the same as, or more than, the time of the write before it. */\nexport interface Send {\n readonly bytes: Uint8Array;\n readonly at: number;\n}\n\n/** A record of one fault that the proxy applied. */\nexport interface FaultRecord {\n /** The position of the fault in the list of the plan. */\n readonly index: number;\n readonly connection: number;\n readonly direction: Direction;\n readonly kind: Fault[\"kind\"];\n /** The time of the fault, in ms from the start of the connection. */\n readonly at: number;\n /** The bytes of the direction before the fault. */\n readonly byte: number;\n /** The frames of the direction before the fault. */\n readonly frame: number;\n /** The bytes of the frames that a `split`, a `duplicate` or a `reorder` changed, or the bytes of the response of an `answer`. It is 0 for the other kinds. */\n readonly bytes: number;\n}\n\n/** The result of one call to an applier. */\nexport interface Step {\n readonly sends: readonly Send[];\n readonly records: readonly FaultRecord[];\n /** The direction stops reading until this time. */\n readonly pauseUntil?: number;\n /** The connection closes. A `cut` closes after the writes before it, and a `reset` closes at once. */\n readonly close?: { readonly kind: \"cut\" | \"reset\"; readonly at: number };\n /** The response of an `answer`, for the client in both directions. After it, no byte goes to the client. A `cut` follows it. */\n readonly answer?: Send;\n}\n\n/** An applier of one direction of one connection. */\nexport interface Applier {\n /** This method takes one frame that the proxy read at the time `now`. */\n push(frame: Uint8Array, now: number): Step;\n /** This method applies the faults that are due at the time `now`. The proxy uses it at the time `wake`. */\n tick(now: number): Step;\n /** This method sends the frames that a `reorder` holds, at the end of the stream. */\n end(now: number): Step;\n /** The next time that `tick` has work to do, or `undefined`. */\n readonly wake: number | undefined;\n /** The faults of this direction that did not occur, with their positions in the plan. */\n pending(): readonly { readonly index: number; readonly fault: Fault }[];\n}\n\ninterface Entry {\n readonly index: number;\n readonly fault: Fault;\n readonly value: number;\n}\n\ninterface Piece {\n readonly bytes: Uint8Array;\n readonly offset: number;\n}\n\ninterface Unit {\n readonly pieces: readonly Piece[];\n readonly base: number;\n}\n\ninterface Hold {\n readonly entry: Entry;\n readonly units: Unit[];\n left: number;\n readonly until: number;\n readonly at: number;\n readonly byte: number;\n readonly frame: number;\n}\n\n/** This function cuts bytes into `pieces` parts of near the same length. Each part has one byte or more. */\nexport function splitBytes(bytes: Uint8Array, pieces: number): Uint8Array[] {\n const count = Math.max(1, Math.min(pieces, bytes.length));\n const out: Uint8Array[] = [];\n for (let i = 0; i < count; i += 1) out.push(bytes.subarray(Math.floor((i * bytes.length) / count), Math.floor(((i + 1) * bytes.length) / count)));\n return out;\n}\n\n/** This function gives the applier of one direction of one connection of a plan. */\nexport function applierOf(plan: FaultPlan, connection: number, direction: Direction): Applier {\n const lists: Record<\"ms\" | \"byte\" | \"frame\", Entry[]> = { ms: [], byte: [], frame: [] };\n plan.faults.forEach((fault, index) => {\n if (fault.connection !== connection || fault.direction !== direction) return;\n const trigger = triggerOf(fault.at);\n if (trigger !== undefined) lists[trigger.measure].push({ index, fault, value: trigger.value });\n });\n for (const list of Object.values(lists)) list.sort((a, b) => a.value - b.value);\n\n const applied = new Set<number>();\n let byte = 0;\n let frame = 0;\n let last = 0;\n let closed = false;\n let delay: { readonly until: number; readonly ms: number } | undefined;\n let armed: Entry[] = [];\n let hold: Hold | undefined;\n\n let sends: Send[] = [];\n let records: FaultRecord[] = [];\n let pauseUntil: number | undefined;\n let close: Step[\"close\"];\n let answer: Send | undefined;\n\n const record = (entry: Entry, at: number, bytes: number, where: { byte: number; frame: number } = { byte, frame }): void => {\n applied.add(entry.index);\n records.push({ index: entry.index, connection, direction, kind: entry.fault.kind, at, byte: where.byte, frame: where.frame, bytes });\n };\n\n const schedule = (unit: Unit, now: number): void => {\n const base = Math.max(now, unit.base);\n for (const piece of unit.pieces) {\n last = Math.max(last, base + piece.offset);\n sends.push({ bytes: piece.bytes, at: last });\n }\n };\n\n const release = (now: number): void => {\n if (hold === undefined) return;\n const held = hold;\n hold = undefined;\n const total = held.units.reduce((sum, unit) => sum + unit.pieces.reduce((s, piece) => s + piece.bytes.length, 0), 0);\n record(held.entry, held.at, total, held);\n for (const unit of [...held.units].reverse()) schedule(unit, now);\n };\n\n /** This function applies one fault at the time `now`. */\n const fire = (entry: Entry, now: number): void => {\n const fault = entry.fault;\n switch (fault.kind) {\n case \"cut\":\n case \"reset\":\n release(now);\n record(entry, now, 0);\n closed = true;\n close = { kind: fault.kind, at: fault.kind === \"cut\" ? Math.max(now, last) : now };\n return;\n case \"stall\":\n record(entry, now, 0);\n pauseUntil = Math.max(pauseUntil ?? 0, now + fault.ms);\n last = Math.max(last, now + fault.ms);\n return;\n case \"delay\":\n record(entry, now, 0);\n delay = { until: now + fault.forMs, ms: Math.max(fault.ms, delay !== undefined && now < delay.until ? delay.ms : 0) };\n return;\n case \"reorder\":\n release(now);\n hold = { entry, units: [], left: fault.count, until: now + fault.holdMs, at: now, byte, frame };\n return;\n case \"answer\": {\n // The frames that a reorder holds came before the point, thus they go out before the answer.\n release(now);\n const bytes = answerBytes(fault);\n record(entry, now, bytes.length);\n const at = direction === \"down\" ? Math.max(now, last) : now;\n answer = { bytes, at };\n closed = true;\n close = { kind: \"cut\", at };\n return;\n }\n default:\n armed.push(entry);\n }\n };\n\n const fireUntil = (list: Entry[], value: number, now: number): void => {\n while (!closed && list.length > 0 && (list[0] as Entry).value <= value) fire(list.shift() as Entry, now);\n };\n\n const fireTimes = (now: number): void => {\n fireUntil(lists.ms, now, now);\n if (!closed && hold !== undefined && now >= hold.until) release(now);\n };\n\n /** This function sends one piece of a frame, with the faults that wait for a frame. */\n const emit = (bytes: Uint8Array, now: number): void => {\n if (delay !== undefined && now >= delay.until) delay = undefined;\n let pieces: Piece[] = [{ bytes, offset: 0 }];\n for (const entry of armed) {\n const fault = entry.fault;\n if (fault.kind === \"split\") {\n const parts = pieces.flatMap((piece) => splitBytes(piece.bytes, fault.pieces));\n pieces = parts.map((part, i) => ({ bytes: part, offset: i * fault.gapMs }));\n } else if (fault.kind === \"duplicate\") {\n const span = pieces.length === 0 ? 0 : (pieces[pieces.length - 1] as Piece).offset;\n pieces = Array.from({ length: fault.copies + 1 }, (_, copy) => pieces.map((piece) => ({ bytes: piece.bytes, offset: piece.offset + copy * span }))).flat();\n }\n record(entry, now, bytes.length);\n }\n armed = [];\n const unit: Unit = { pieces, base: now + (delay?.ms ?? 0) };\n if (hold === undefined) {\n schedule(unit, now);\n return;\n }\n hold.units.push(unit);\n hold.left -= 1;\n if (hold.left === 0) release(now);\n };\n\n const step = (): Step => {\n const out: Step = {\n sends,\n records,\n ...(pauseUntil === undefined ? {} : { pauseUntil }),\n ...(close === undefined ? {} : { close }),\n ...(answer === undefined ? {} : { answer }),\n };\n sends = [];\n records = [];\n pauseUntil = undefined;\n close = undefined;\n answer = undefined;\n return out;\n };\n\n return {\n push(bytes, now) {\n if (closed) return step();\n fireTimes(now);\n fireUntil(lists.frame, frame, now);\n let rest = bytes;\n while (!closed && rest.length > 0) {\n fireUntil(lists.byte, byte, now);\n if (closed) break;\n const next = lists.byte[0]?.value;\n const take = next !== undefined && next < byte + rest.length ? next - byte : rest.length;\n emit(rest.subarray(0, take), now);\n byte += take;\n rest = rest.subarray(take);\n }\n frame += 1;\n return step();\n },\n tick(now) {\n if (!closed) fireTimes(now);\n return step();\n },\n end(now) {\n if (!closed) release(now);\n closed = true;\n return step();\n },\n get wake() {\n if (closed) return undefined;\n const times = [lists.ms[0]?.value, hold?.until].filter((time): time is number => time !== undefined);\n return times.length === 0 ? undefined : Math.min(...times);\n },\n pending() {\n return plan.faults.flatMap((fault, index) => (fault.connection === connection && fault.direction === direction && !applied.has(index) ? [{ index, fault }] : []));\n },\n };\n}\n"],"mappings":"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"}
|
package/build/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
import { answerBytes } from "./answer.js";
|
|
1
2
|
import { Applier, FaultRecord, Send, Step, applierOf, splitBytes } from "./applier.js";
|
|
2
3
|
import { Framer, FramerPair, HttpHead, chunkFramer, httpFramers, readHttpHead, tcpFramers, webSocketFrameLength, webSocketFramer } from "./framers.js";
|
|
3
4
|
import { ConnectionEnd, ConnectionSummary, FaultProxy, Protocol, ProxyOptions, startFaultProxy } from "./proxy.js";
|
|
4
|
-
export { type Applier, type ConnectionEnd, type ConnectionSummary, type FaultProxy, type FaultRecord, type Framer, type FramerPair, type HttpHead, type Protocol, type ProxyOptions, type Send, type Step, applierOf, chunkFramer, httpFramers, readHttpHead, splitBytes, startFaultProxy, tcpFramers, webSocketFrameLength, webSocketFramer };
|
|
5
|
+
export { type Applier, type ConnectionEnd, type ConnectionSummary, type FaultProxy, type FaultRecord, type Framer, type FramerPair, type HttpHead, type Protocol, type ProxyOptions, type Send, type Step, answerBytes, applierOf, chunkFramer, httpFramers, readHttpHead, splitBytes, startFaultProxy, tcpFramers, webSocketFrameLength, webSocketFramer };
|
package/build/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
import { answerBytes } from "./answer.js";
|
|
1
2
|
import { applierOf, splitBytes } from "./applier.js";
|
|
2
3
|
import { chunkFramer, httpFramers, readHttpHead, tcpFramers, webSocketFrameLength, webSocketFramer } from "./framers.js";
|
|
3
4
|
import { startFaultProxy } from "./proxy.js";
|
|
4
|
-
export { applierOf, chunkFramer, httpFramers, readHttpHead, splitBytes, startFaultProxy, tcpFramers, webSocketFrameLength, webSocketFramer };
|
|
5
|
+
export { answerBytes, applierOf, chunkFramer, httpFramers, readHttpHead, splitBytes, startFaultProxy, tcpFramers, webSocketFrameLength, webSocketFramer };
|
package/build/proxy.js
CHANGED
|
@@ -50,6 +50,8 @@ async function startFaultProxy(options) {
|
|
|
50
50
|
const sides = [side("up", client, upstream), side("down", upstream, client)];
|
|
51
51
|
let end;
|
|
52
52
|
let closed = 0;
|
|
53
|
+
/** After an answer, no byte of the server goes to the client. */
|
|
54
|
+
let answered = false;
|
|
53
55
|
const cancelTimers = () => {
|
|
54
56
|
for (const s of sides) {
|
|
55
57
|
s.write?.();
|
|
@@ -86,10 +88,16 @@ async function startFaultProxy(options) {
|
|
|
86
88
|
records.push(record);
|
|
87
89
|
options.onFault?.(record);
|
|
88
90
|
}
|
|
89
|
-
if (step.sends.length > 0) {
|
|
91
|
+
if (step.sends.length > 0 && !(answered && s.direction === "down")) {
|
|
90
92
|
s.queue.push(...step.sends);
|
|
91
93
|
if (s.write === void 0) pump(s);
|
|
92
94
|
}
|
|
95
|
+
if (step.answer !== void 0 && !answered) {
|
|
96
|
+
answered = true;
|
|
97
|
+
const down = sides[1];
|
|
98
|
+
down.queue.push(step.answer);
|
|
99
|
+
if (down.write === void 0) pump(down);
|
|
100
|
+
}
|
|
93
101
|
if (step.pauseUntil !== void 0) {
|
|
94
102
|
s.stalled = true;
|
|
95
103
|
s.from.pause();
|
package/build/proxy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.js","names":[],"sources":["../src/proxy.ts"],"sourcesContent":["import { type AddressInfo, type Socket, connect, createServer } from \"node:net\";\nimport { type Cancel, type Clock, platformClock } from \"@mark1russell7/algebra/clock\";\nimport { type Direction, type Fault, type FaultPlan, checkFaultPlan } from \"@mark1russell7/rigor-chaos\";\nimport { type Applier, type FaultRecord, type Send, type Step, applierOf } from \"./applier.ts\";\nimport { type Framer, type FramerPair, httpFramers, tcpFramers } from \"./framers.ts\";\n\n/** The protocol of the streams. `http` and `websocket` use the same framers: a WebSocket starts as an HTTP upgrade. */\nexport type Protocol = \"tcp\" | \"http\" | \"websocket\";\n\n/** The options of a fault proxy. */\nexport interface ProxyOptions {\n /** The server that the proxy connects each client to. The default host is `127.0.0.1`. */\n readonly target: { readonly host?: string; readonly port: number };\n readonly plan: FaultPlan;\n /** The protocol sets the framers. The default is `tcp`. */\n readonly protocol?: Protocol;\n /** This function makes the framers of each connection. It replaces the framers of `protocol`. */\n readonly framers?: () => FramerPair;\n /** The address of the proxy. The default is `127.0.0.1`, with a free port. */\n readonly listen?: { readonly host?: string; readonly port?: number };\n /** The clock of the times and the timers. The default is `platformClock()`. A test can give `fakeClock()`. */\n readonly clock?: Clock;\n /** This function gets each record when the fault occurs. */\n readonly onFault?: (record: FaultRecord) => void;\n /** This function gets the summary of each connection when the connection closes. */\n readonly onClose?: (summary: ConnectionSummary) => void;\n}\n\n/** How a connection ended. */\nexport type ConnectionEnd = \"close\" | \"cut\" | \"reset\" | \"error\";\n\n/** The summary of one connection. */\nexport interface ConnectionSummary {\n readonly connection: number;\n readonly end: ConnectionEnd;\n /** The bytes that the proxy read in each direction. */\n readonly bytes: { readonly up: number; readonly down: number };\n /** The faults of the connection that did not occur, with their positions in the plan. */\n readonly missed: readonly { readonly index: number; readonly fault: Fault }[];\n}\n\n/** A fault proxy that listens. */\nexport interface FaultProxy {\n readonly host: string;\n readonly port: number;\n /** The records of the faults, in the order that they occurred. */\n readonly records: readonly FaultRecord[];\n /** The summaries of the connections that closed. */\n readonly connections: readonly ConnectionSummary[];\n /** This method stops the proxy and destroys each open connection. */\n close(): Promise<void>;\n}\n\ninterface Side {\n readonly direction: Direction;\n readonly from: Socket;\n readonly to: Socket;\n readonly framer: Framer;\n readonly applier: Applier;\n readonly queue: Send[];\n write: Cancel | undefined;\n wake: Cancel | undefined;\n stall: Cancel | undefined;\n stalled: boolean;\n draining: boolean;\n ending: boolean;\n read: number;\n}\n\n/**\n * This function starts a proxy that puts the faults of a plan into the connections between clients and a target. The\n * proxy numbers the connections from 0, in the order that it accepts them. It throws a `RangeError` for a plan with problems.\n */\nexport async function startFaultProxy(options: ProxyOptions): Promise<FaultProxy> {\n const problems = checkFaultPlan(options.plan);\n if (problems.length > 0) throw new RangeError(`startFaultProxy: ${problems.join(\" \")}`);\n const clock = options.clock ?? platformClock();\n const makeFramers = options.framers ?? (options.protocol === undefined || options.protocol === \"tcp\" ? tcpFramers : httpFramers);\n const records: FaultRecord[] = [];\n const connections: ConnectionSummary[] = [];\n const open = new Set<Socket>();\n let next = 0;\n\n const server = createServer({ allowHalfOpen: true }, (client) => {\n const connection = next;\n next += 1;\n const start = clock.now();\n const now = (): number => clock.now() - start;\n const upstream = connect({ host: options.target.host ?? \"127.0.0.1\", port: options.target.port, allowHalfOpen: true, noDelay: true });\n // Each write goes out at once, thus a split or a gap of the plan stays on the wire.\n client.setNoDelay(true);\n open.add(client);\n open.add(upstream);\n const pair = makeFramers();\n const side = (direction: Direction, from: Socket, to: Socket): Side => ({\n direction,\n from,\n to,\n framer: pair[direction],\n applier: applierOf(options.plan, connection, direction),\n queue: [],\n write: undefined,\n wake: undefined,\n stall: undefined,\n stalled: false,\n draining: false,\n ending: false,\n read: 0,\n });\n const sides = [side(\"up\", client, upstream), side(\"down\", upstream, client)] as const;\n let end: ConnectionEnd | undefined;\n let closed = 0;\n\n const cancelTimers = (): void => {\n for (const s of sides) {\n s.write?.();\n s.wake?.();\n s.stall?.();\n s.write = undefined;\n s.wake = undefined;\n s.stall = undefined;\n }\n };\n\n const resume = (s: Side): void => {\n if (end === undefined && !s.stalled && !s.draining) s.from.resume();\n };\n\n const pump = (s: Side): void => {\n s.write = undefined;\n if (end === \"reset\" || end === \"error\") return;\n const t = now();\n while (s.queue.length > 0 && (s.queue[0] as Send).at <= t) {\n const send = s.queue.shift() as Send;\n if (!s.to.write(send.bytes) && !s.draining) {\n s.draining = true;\n s.from.pause();\n s.to.once(\"drain\", () => {\n s.draining = false;\n resume(s);\n });\n }\n }\n if (s.queue.length > 0) s.write = clock.after((s.queue[0] as Send).at - t, () => pump(s));\n else if (s.ending && !s.to.writableEnded) s.to.end(() => (end === \"cut\" ? s.to.destroy() : undefined));\n };\n\n const apply = (s: Side, step: Step): void => {\n for (const record of step.records) {\n records.push(record);\n options.onFault?.(record);\n }\n if (step.sends.length > 0) {\n s.queue.push(...step.sends);\n if (s.write === undefined) pump(s);\n }\n if (step.pauseUntil !== undefined) {\n s.stalled = true;\n s.from.pause();\n s.stall?.();\n s.stall = clock.after(step.pauseUntil - now(), () => {\n s.stall = undefined;\n s.stalled = false;\n resume(s);\n });\n }\n if (step.close !== undefined) shut(step.close.kind);\n rearm(s);\n };\n\n const rearm = (s: Side): void => {\n s.wake?.();\n s.wake = undefined;\n const wake = s.applier.wake;\n if (wake !== undefined && end === undefined) s.wake = clock.after(Math.max(0, wake - now()), () => apply(s, s.applier.tick(now())));\n };\n\n const finish = (s: Side): void => {\n if (s.ending) return;\n for (const frame of s.framer.end()) apply(s, s.applier.push(frame, now()));\n apply(s, s.applier.end(now()));\n s.ending = true;\n if (s.write === undefined) pump(s);\n };\n\n const shut = (kind: \"cut\" | \"reset\"): void => {\n if (end !== undefined) return;\n end = kind;\n for (const s of sides) s.from.pause();\n if (kind === \"reset\") {\n cancelTimers();\n client.resetAndDestroy();\n upstream.resetAndDestroy();\n return;\n }\n for (const s of sides) {\n s.wake?.();\n s.stall?.();\n apply(s, s.applier.end(now()));\n s.ending = true;\n if (s.write === undefined) pump(s);\n }\n };\n\n for (const s of sides) {\n s.from.on(\"data\", (bytes: Buffer) => {\n if (end !== undefined) return;\n s.read += bytes.length;\n for (const frame of s.framer.push(bytes)) {\n apply(s, s.applier.push(frame, now()));\n if (end !== undefined) return;\n }\n });\n s.from.on(\"end\", () => {\n if (end === undefined) finish(s);\n });\n s.from.on(\"error\", () => {\n end ??= \"error\";\n cancelTimers();\n for (const other of sides) other.from.destroy();\n });\n s.from.on(\"close\", () => {\n open.delete(s.from);\n closed += 1;\n if (closed === 1) {\n // The socket closed fully, thus the other socket gets the bytes that wait for it, and then it closes too.\n if (end === undefined) finish(s);\n if (s.to.writableFinished) s.to.destroy();\n else s.to.once(\"finish\", () => s.to.destroy());\n return;\n }\n cancelTimers();\n const summary: ConnectionSummary = {\n connection,\n end: end ?? \"close\",\n bytes: { up: sides[0].read, down: sides[1].read },\n missed: [...sides[0].applier.pending(), ...sides[1].applier.pending()].sort((a, b) => a.index - b.index),\n };\n connections.push(summary);\n options.onClose?.(summary);\n });\n }\n // A fault at 0 ms occurs before the first byte.\n for (const s of sides) apply(s, s.applier.tick(0));\n });\n\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(options.listen?.port ?? 0, options.listen?.host ?? \"127.0.0.1\", () => {\n server.off(\"error\", reject);\n resolve();\n });\n });\n const address = server.address() as AddressInfo;\n return {\n host: address.address,\n port: address.port,\n records,\n connections,\n close: () =>\n new Promise<void>((resolve) => {\n server.close(() => resolve());\n for (const socket of open) socket.destroy();\n }),\n };\n}\n"],"mappings":";;;;;;;;;;AAyEA,eAAsB,gBAAgB,SAA4C;CAChF,MAAM,WAAW,eAAe,QAAQ,IAAI;CAC5C,IAAI,SAAS,SAAS,GAAG,MAAM,IAAI,WAAW,oBAAoB,SAAS,KAAK,GAAG,GAAG;CACtF,MAAM,QAAQ,QAAQ,SAAS,cAAc;CAC7C,MAAM,cAAc,QAAQ,YAAY,QAAQ,aAAa,KAAA,KAAa,QAAQ,aAAa,QAAQ,aAAa;CACpH,MAAM,UAAyB,CAAC;CAChC,MAAM,cAAmC,CAAC;CAC1C,MAAM,uBAAO,IAAI,IAAY;CAC7B,IAAI,OAAO;CAEX,MAAM,SAAS,aAAa,EAAE,eAAe,KAAK,IAAI,WAAW;EAC/D,MAAM,aAAa;EACnB,QAAQ;EACR,MAAM,QAAQ,MAAM,IAAI;EACxB,MAAM,YAAoB,MAAM,IAAI,IAAI;EACxC,MAAM,WAAW,QAAQ;GAAE,MAAM,QAAQ,OAAO,QAAQ;GAAa,MAAM,QAAQ,OAAO;GAAM,eAAe;GAAM,SAAS;EAAK,CAAC;EAEpI,OAAO,WAAW,IAAI;EACtB,KAAK,IAAI,MAAM;EACf,KAAK,IAAI,QAAQ;EACjB,MAAM,OAAO,YAAY;EACzB,MAAM,QAAQ,WAAsB,MAAc,QAAsB;GACtE;GACA;GACA;GACA,QAAQ,KAAK;GACb,SAAS,UAAU,QAAQ,MAAM,YAAY,SAAS;GACtD,OAAO,CAAC;GACR,OAAO,KAAA;GACP,MAAM,KAAA;GACN,OAAO,KAAA;GACP,SAAS;GACT,UAAU;GACV,QAAQ;GACR,MAAM;EACR;EACA,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,QAAQ,GAAG,KAAK,QAAQ,UAAU,MAAM,CAAC;EAC3E,IAAI;EACJ,IAAI,SAAS;EAEb,MAAM,qBAA2B;GAC/B,KAAK,MAAM,KAAK,OAAO;IACrB,EAAE,QAAQ;IACV,EAAE,OAAO;IACT,EAAE,QAAQ;IACV,EAAE,QAAQ,KAAA;IACV,EAAE,OAAO,KAAA;IACT,EAAE,QAAQ,KAAA;GACZ;EACF;EAEA,MAAM,UAAU,MAAkB;GAChC,IAAI,QAAQ,KAAA,KAAa,CAAC,EAAE,WAAW,CAAC,EAAE,UAAU,EAAE,KAAK,OAAO;EACpE;EAEA,MAAM,QAAQ,MAAkB;GAC9B,EAAE,QAAQ,KAAA;GACV,IAAI,QAAQ,WAAW,QAAQ,SAAS;GACxC,MAAM,IAAI,IAAI;GACd,OAAO,EAAE,MAAM,SAAS,KAAM,EAAE,MAAM,EAAE,CAAU,MAAM,GAAG;IACzD,MAAM,OAAO,EAAE,MAAM,MAAM;IAC3B,IAAI,CAAC,EAAE,GAAG,MAAM,KAAK,KAAK,KAAK,CAAC,EAAE,UAAU;KAC1C,EAAE,WAAW;KACb,EAAE,KAAK,MAAM;KACb,EAAE,GAAG,KAAK,eAAe;MACvB,EAAE,WAAW;MACb,OAAO,CAAC;KACV,CAAC;IACH;GACF;GACA,IAAI,EAAE,MAAM,SAAS,GAAG,EAAE,QAAQ,MAAM,MAAO,EAAE,MAAM,EAAE,CAAU,KAAK,SAAS,KAAK,CAAC,CAAC;QACnF,IAAI,EAAE,UAAU,CAAC,EAAE,GAAG,eAAe,EAAE,GAAG,UAAW,QAAQ,QAAQ,EAAE,GAAG,QAAQ,IAAI,KAAA,CAAU;EACvG;EAEA,MAAM,SAAS,GAAS,SAAqB;GAC3C,KAAK,MAAM,UAAU,KAAK,SAAS;IACjC,QAAQ,KAAK,MAAM;IACnB,QAAQ,UAAU,MAAM;GAC1B;GACA,IAAI,KAAK,MAAM,SAAS,GAAG;IACzB,EAAE,MAAM,KAAK,GAAG,KAAK,KAAK;IAC1B,IAAI,EAAE,UAAU,KAAA,GAAW,KAAK,CAAC;GACnC;GACA,IAAI,KAAK,eAAe,KAAA,GAAW;IACjC,EAAE,UAAU;IACZ,EAAE,KAAK,MAAM;IACb,EAAE,QAAQ;IACV,EAAE,QAAQ,MAAM,MAAM,KAAK,aAAa,IAAI,SAAS;KACnD,EAAE,QAAQ,KAAA;KACV,EAAE,UAAU;KACZ,OAAO,CAAC;IACV,CAAC;GACH;GACA,IAAI,KAAK,UAAU,KAAA,GAAW,KAAK,KAAK,MAAM,IAAI;GAClD,MAAM,CAAC;EACT;EAEA,MAAM,SAAS,MAAkB;GAC/B,EAAE,OAAO;GACT,EAAE,OAAO,KAAA;GACT,MAAM,OAAO,EAAE,QAAQ;GACvB,IAAI,SAAS,KAAA,KAAa,QAAQ,KAAA,GAAW,EAAE,OAAO,MAAM,MAAM,KAAK,IAAI,GAAG,OAAO,IAAI,CAAC,SAAS,MAAM,GAAG,EAAE,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC;EACpI;EAEA,MAAM,UAAU,MAAkB;GAChC,IAAI,EAAE,QAAQ;GACd,KAAK,MAAM,SAAS,EAAE,OAAO,IAAI,GAAG,MAAM,GAAG,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC,CAAC;GACzE,MAAM,GAAG,EAAE,QAAQ,IAAI,IAAI,CAAC,CAAC;GAC7B,EAAE,SAAS;GACX,IAAI,EAAE,UAAU,KAAA,GAAW,KAAK,CAAC;EACnC;EAEA,MAAM,QAAQ,SAAgC;GAC5C,IAAI,QAAQ,KAAA,GAAW;GACvB,MAAM;GACN,KAAK,MAAM,KAAK,OAAO,EAAE,KAAK,MAAM;GACpC,IAAI,SAAS,SAAS;IACpB,aAAa;IACb,OAAO,gBAAgB;IACvB,SAAS,gBAAgB;IACzB;GACF;GACA,KAAK,MAAM,KAAK,OAAO;IACrB,EAAE,OAAO;IACT,EAAE,QAAQ;IACV,MAAM,GAAG,EAAE,QAAQ,IAAI,IAAI,CAAC,CAAC;IAC7B,EAAE,SAAS;IACX,IAAI,EAAE,UAAU,KAAA,GAAW,KAAK,CAAC;GACnC;EACF;EAEA,KAAK,MAAM,KAAK,OAAO;GACrB,EAAE,KAAK,GAAG,SAAS,UAAkB;IACnC,IAAI,QAAQ,KAAA,GAAW;IACvB,EAAE,QAAQ,MAAM;IAChB,KAAK,MAAM,SAAS,EAAE,OAAO,KAAK,KAAK,GAAG;KACxC,MAAM,GAAG,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC,CAAC;KACrC,IAAI,QAAQ,KAAA,GAAW;IACzB;GACF,CAAC;GACD,EAAE,KAAK,GAAG,aAAa;IACrB,IAAI,QAAQ,KAAA,GAAW,OAAO,CAAC;GACjC,CAAC;GACD,EAAE,KAAK,GAAG,eAAe;IACvB,QAAQ;IACR,aAAa;IACb,KAAK,MAAM,SAAS,OAAO,MAAM,KAAK,QAAQ;GAChD,CAAC;GACD,EAAE,KAAK,GAAG,eAAe;IACvB,KAAK,OAAO,EAAE,IAAI;IAClB,UAAU;IACV,IAAI,WAAW,GAAG;KAEhB,IAAI,QAAQ,KAAA,GAAW,OAAO,CAAC;KAC/B,IAAI,EAAE,GAAG,kBAAkB,EAAE,GAAG,QAAQ;UACnC,EAAE,GAAG,KAAK,gBAAgB,EAAE,GAAG,QAAQ,CAAC;KAC7C;IACF;IACA,aAAa;IACb,MAAM,UAA6B;KACjC;KACA,KAAK,OAAO;KACZ,OAAO;MAAE,IAAI,MAAM,EAAE,CAAC;MAAM,MAAM,MAAM,EAAE,CAAC;KAAK;KAChD,QAAQ,CAAC,GAAG,MAAM,EAAE,CAAC,QAAQ,QAAQ,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,QAAQ,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;IACzG;IACA,YAAY,KAAK,OAAO;IACxB,QAAQ,UAAU,OAAO;GAC3B,CAAC;EACH;EAEA,KAAK,MAAM,KAAK,OAAO,MAAM,GAAG,EAAE,QAAQ,KAAK,CAAC,CAAC;CACnD,CAAC;CAED,MAAM,IAAI,SAAe,SAAS,WAAW;EAC3C,OAAO,KAAK,SAAS,MAAM;EAC3B,OAAO,OAAO,QAAQ,QAAQ,QAAQ,GAAG,QAAQ,QAAQ,QAAQ,mBAAmB;GAClF,OAAO,IAAI,SAAS,MAAM;GAC1B,QAAQ;EACV,CAAC;CACH,CAAC;CACD,MAAM,UAAU,OAAO,QAAQ;CAC/B,OAAO;EACL,MAAM,QAAQ;EACd,MAAM,QAAQ;EACd;EACA;EACA,aACE,IAAI,SAAe,YAAY;GAC7B,OAAO,YAAY,QAAQ,CAAC;GAC5B,KAAK,MAAM,UAAU,MAAM,OAAO,QAAQ;EAC5C,CAAC;CACL;AACF"}
|
|
1
|
+
{"version":3,"file":"proxy.js","names":[],"sources":["../src/proxy.ts"],"sourcesContent":["import { type AddressInfo, type Socket, connect, createServer } from \"node:net\";\nimport { type Cancel, type Clock, platformClock } from \"@mark1russell7/algebra/clock\";\nimport { type Direction, type Fault, type FaultPlan, checkFaultPlan } from \"@mark1russell7/rigor-chaos\";\nimport { type Applier, type FaultRecord, type Send, type Step, applierOf } from \"./applier.ts\";\nimport { type Framer, type FramerPair, httpFramers, tcpFramers } from \"./framers.ts\";\n\n/** The protocol of the streams. `http` and `websocket` use the same framers: a WebSocket starts as an HTTP upgrade. */\nexport type Protocol = \"tcp\" | \"http\" | \"websocket\";\n\n/** The options of a fault proxy. */\nexport interface ProxyOptions {\n /** The server that the proxy connects each client to. The default host is `127.0.0.1`. */\n readonly target: { readonly host?: string; readonly port: number };\n readonly plan: FaultPlan;\n /** The protocol sets the framers. The default is `tcp`. */\n readonly protocol?: Protocol;\n /** This function makes the framers of each connection. It replaces the framers of `protocol`. */\n readonly framers?: () => FramerPair;\n /** The address of the proxy. The default is `127.0.0.1`, with a free port. */\n readonly listen?: { readonly host?: string; readonly port?: number };\n /** The clock of the times and the timers. The default is `platformClock()`. A test can give `fakeClock()`. */\n readonly clock?: Clock;\n /** This function gets each record when the fault occurs. */\n readonly onFault?: (record: FaultRecord) => void;\n /** This function gets the summary of each connection when the connection closes. */\n readonly onClose?: (summary: ConnectionSummary) => void;\n}\n\n/** How a connection ended. */\nexport type ConnectionEnd = \"close\" | \"cut\" | \"reset\" | \"error\";\n\n/** The summary of one connection. */\nexport interface ConnectionSummary {\n readonly connection: number;\n readonly end: ConnectionEnd;\n /** The bytes that the proxy read in each direction. */\n readonly bytes: { readonly up: number; readonly down: number };\n /** The faults of the connection that did not occur, with their positions in the plan. */\n readonly missed: readonly { readonly index: number; readonly fault: Fault }[];\n}\n\n/** A fault proxy that listens. */\nexport interface FaultProxy {\n readonly host: string;\n readonly port: number;\n /** The records of the faults, in the order that they occurred. */\n readonly records: readonly FaultRecord[];\n /** The summaries of the connections that closed. */\n readonly connections: readonly ConnectionSummary[];\n /** This method stops the proxy and destroys each open connection. */\n close(): Promise<void>;\n}\n\ninterface Side {\n readonly direction: Direction;\n readonly from: Socket;\n readonly to: Socket;\n readonly framer: Framer;\n readonly applier: Applier;\n readonly queue: Send[];\n write: Cancel | undefined;\n wake: Cancel | undefined;\n stall: Cancel | undefined;\n stalled: boolean;\n draining: boolean;\n ending: boolean;\n read: number;\n}\n\n/**\n * This function starts a proxy that puts the faults of a plan into the connections between clients and a target. The\n * proxy numbers the connections from 0, in the order that it accepts them. It throws a `RangeError` for a plan with problems.\n */\nexport async function startFaultProxy(options: ProxyOptions): Promise<FaultProxy> {\n const problems = checkFaultPlan(options.plan);\n if (problems.length > 0) throw new RangeError(`startFaultProxy: ${problems.join(\" \")}`);\n const clock = options.clock ?? platformClock();\n const makeFramers = options.framers ?? (options.protocol === undefined || options.protocol === \"tcp\" ? tcpFramers : httpFramers);\n const records: FaultRecord[] = [];\n const connections: ConnectionSummary[] = [];\n const open = new Set<Socket>();\n let next = 0;\n\n const server = createServer({ allowHalfOpen: true }, (client) => {\n const connection = next;\n next += 1;\n const start = clock.now();\n const now = (): number => clock.now() - start;\n const upstream = connect({ host: options.target.host ?? \"127.0.0.1\", port: options.target.port, allowHalfOpen: true, noDelay: true });\n // Each write goes out at once, thus a split or a gap of the plan stays on the wire.\n client.setNoDelay(true);\n open.add(client);\n open.add(upstream);\n const pair = makeFramers();\n const side = (direction: Direction, from: Socket, to: Socket): Side => ({\n direction,\n from,\n to,\n framer: pair[direction],\n applier: applierOf(options.plan, connection, direction),\n queue: [],\n write: undefined,\n wake: undefined,\n stall: undefined,\n stalled: false,\n draining: false,\n ending: false,\n read: 0,\n });\n const sides = [side(\"up\", client, upstream), side(\"down\", upstream, client)] as const;\n let end: ConnectionEnd | undefined;\n let closed = 0;\n /** After an answer, no byte of the server goes to the client. */\n let answered = false;\n\n const cancelTimers = (): void => {\n for (const s of sides) {\n s.write?.();\n s.wake?.();\n s.stall?.();\n s.write = undefined;\n s.wake = undefined;\n s.stall = undefined;\n }\n };\n\n const resume = (s: Side): void => {\n if (end === undefined && !s.stalled && !s.draining) s.from.resume();\n };\n\n const pump = (s: Side): void => {\n s.write = undefined;\n if (end === \"reset\" || end === \"error\") return;\n const t = now();\n while (s.queue.length > 0 && (s.queue[0] as Send).at <= t) {\n const send = s.queue.shift() as Send;\n if (!s.to.write(send.bytes) && !s.draining) {\n s.draining = true;\n s.from.pause();\n s.to.once(\"drain\", () => {\n s.draining = false;\n resume(s);\n });\n }\n }\n if (s.queue.length > 0) s.write = clock.after((s.queue[0] as Send).at - t, () => pump(s));\n else if (s.ending && !s.to.writableEnded) s.to.end(() => (end === \"cut\" ? s.to.destroy() : undefined));\n };\n\n const apply = (s: Side, step: Step): void => {\n for (const record of step.records) {\n records.push(record);\n options.onFault?.(record);\n }\n if (step.sends.length > 0 && !(answered && s.direction === \"down\")) {\n s.queue.push(...step.sends);\n if (s.write === undefined) pump(s);\n }\n if (step.answer !== undefined && !answered) {\n answered = true;\n const down = sides[1];\n down.queue.push(step.answer);\n if (down.write === undefined) pump(down);\n }\n if (step.pauseUntil !== undefined) {\n s.stalled = true;\n s.from.pause();\n s.stall?.();\n s.stall = clock.after(step.pauseUntil - now(), () => {\n s.stall = undefined;\n s.stalled = false;\n resume(s);\n });\n }\n if (step.close !== undefined) shut(step.close.kind);\n rearm(s);\n };\n\n const rearm = (s: Side): void => {\n s.wake?.();\n s.wake = undefined;\n const wake = s.applier.wake;\n if (wake !== undefined && end === undefined) s.wake = clock.after(Math.max(0, wake - now()), () => apply(s, s.applier.tick(now())));\n };\n\n const finish = (s: Side): void => {\n if (s.ending) return;\n for (const frame of s.framer.end()) apply(s, s.applier.push(frame, now()));\n apply(s, s.applier.end(now()));\n s.ending = true;\n if (s.write === undefined) pump(s);\n };\n\n const shut = (kind: \"cut\" | \"reset\"): void => {\n if (end !== undefined) return;\n end = kind;\n for (const s of sides) s.from.pause();\n if (kind === \"reset\") {\n cancelTimers();\n client.resetAndDestroy();\n upstream.resetAndDestroy();\n return;\n }\n for (const s of sides) {\n s.wake?.();\n s.stall?.();\n apply(s, s.applier.end(now()));\n s.ending = true;\n if (s.write === undefined) pump(s);\n }\n };\n\n for (const s of sides) {\n s.from.on(\"data\", (bytes: Buffer) => {\n if (end !== undefined) return;\n s.read += bytes.length;\n for (const frame of s.framer.push(bytes)) {\n apply(s, s.applier.push(frame, now()));\n if (end !== undefined) return;\n }\n });\n s.from.on(\"end\", () => {\n if (end === undefined) finish(s);\n });\n s.from.on(\"error\", () => {\n end ??= \"error\";\n cancelTimers();\n for (const other of sides) other.from.destroy();\n });\n s.from.on(\"close\", () => {\n open.delete(s.from);\n closed += 1;\n if (closed === 1) {\n // The socket closed fully, thus the other socket gets the bytes that wait for it, and then it closes too.\n if (end === undefined) finish(s);\n if (s.to.writableFinished) s.to.destroy();\n else s.to.once(\"finish\", () => s.to.destroy());\n return;\n }\n cancelTimers();\n const summary: ConnectionSummary = {\n connection,\n end: end ?? \"close\",\n bytes: { up: sides[0].read, down: sides[1].read },\n missed: [...sides[0].applier.pending(), ...sides[1].applier.pending()].sort((a, b) => a.index - b.index),\n };\n connections.push(summary);\n options.onClose?.(summary);\n });\n }\n // A fault at 0 ms occurs before the first byte.\n for (const s of sides) apply(s, s.applier.tick(0));\n });\n\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(options.listen?.port ?? 0, options.listen?.host ?? \"127.0.0.1\", () => {\n server.off(\"error\", reject);\n resolve();\n });\n });\n const address = server.address() as AddressInfo;\n return {\n host: address.address,\n port: address.port,\n records,\n connections,\n close: () =>\n new Promise<void>((resolve) => {\n server.close(() => resolve());\n for (const socket of open) socket.destroy();\n }),\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@mark1russell7/rigor-proxy",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"description": "A TCP proxy that applies a seeded fault plan to TCP, HTTP and WebSocket streams, and records each fault.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"proxy",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@mark1russell7/algebra": "^0.8.4",
|
|
41
|
-
"@mark1russell7/rigor-chaos": "^0.
|
|
41
|
+
"@mark1russell7/rigor-chaos": "^0.2.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@mark1russell7/cue": "^2.0.0",
|