socketduct 0.1.25 → 0.1.26
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/AGENTS.md +2 -2
- package/changelog.d/20261010130509-bound-log-frame-compression-memory.md +1 -0
- package/docs/runbooks/jsonl-integrity-rollout.md +8 -6
- package/package.json +2 -2
- package/scripts/test.js +9 -14
- package/src/logging/compressed-log-writer.js +1 -1
- package/src/logging/zstd-frame-codec.js +15 -1
- package/test/e2e/gateway-process.test.js +93 -92
- package/test/e2e/logger-shutdown-process.test.js +40 -37
- package/test/e2e/reverse-client-recovery-process.test.js +107 -103
- package/test/e2e/reverse-gateway-process.test.js +328 -318
- package/test/integration/initial-target-recovery.test.js +333 -334
- package/test/integration/logging-cli.test.js +121 -119
- package/test/integration/logging-compressed-recovery.test.js +96 -95
- package/test/integration/multiplexed-compatibility.test.js +178 -177
- package/test/integration/multiplexed-corrections.test.js +360 -359
- package/test/integration/multiplexed-failures.test.js +253 -251
- package/test/integration/multiplexed-transport-set.test.js +334 -322
- package/test/integration/multiplexed-transport.test.js +135 -134
- package/test/integration/reverse-gateway-stream-id-reset.test.js +90 -89
- package/test/integration/reverse-gateway.test.js +2309 -2218
- package/test/integration/reverse-resume-continuity.test.js +219 -218
- package/test/integration/reverse-route-class.test.js +358 -341
- package/test/integration/socketduct-http-agent.test.js +314 -312
- package/test/integration/socketduct-https-agent.test.js +516 -485
- package/test/integration/stream-compression.test.js +862 -824
- package/test/integration/tcp-forwarding.test.js +2550 -2447
- package/test/integration/tls-forwarding.test.js +593 -592
- package/test/unit/auth/auth.test.js +43 -44
- package/test/unit/auth/static-token-store.test.js +9 -8
- package/test/unit/client/connect.test.js +41 -40
- package/test/unit/client/deferred-virtual-socket.test.js +95 -94
- package/test/unit/client/multiplexed-spool-budget.test.js +155 -154
- package/test/unit/client/multiplexed-transport-set.test.js +830 -748
- package/test/unit/client/socketduct-socket.test.js +768 -771
- package/test/unit/client/virtual-socket.test.js +198 -168
- package/test/unit/config/docker-gateway-files.test.js +224 -224
- package/test/unit/config/environment.test.js +472 -492
- package/test/unit/index.test.js +53 -52
- package/test/unit/logging/compressed-log-worker.test.js +23 -22
- package/test/unit/logging/compressed-log-writer.test.js +155 -154
- package/test/unit/logging/log-retention.test.js +52 -47
- package/test/unit/logging/log-segment-index.test.js +84 -81
- package/test/unit/logging/zstd-frame-codec.test.js +28 -19
- package/test/unit/protocol/compression.test.js +254 -298
- package/test/unit/protocol/frame.test.js +280 -276
- package/test/unit/protocol/v3-compression.test.js +21 -23
- package/test/unit/protocol/v3-frame.test.js +42 -41
- package/test/unit/release-configuration.test.js +15 -12
- package/test/unit/reverse/disconnect-attribution.test.js +65 -64
- package/test/unit/reverse-terminal-outcomes.test.js +53 -50
- package/test/unit/server/control-handler.test.js +232 -231
- package/test/unit/server/primary.test.js +51 -49
- package/test/unit/server/relay-runtime.test.js +280 -294
- package/test/unit/sessions/session.test.js +48 -62
- package/test/unit/spool/spool-file.test.js +401 -385
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/* eslint-disable jsdoc/escape-inline-tags, jsdoc/require-param-type, jsdoc/require-returns */
|
|
2
|
-
import
|
|
2
|
+
import {describe, expect, it, withTestCleanup} from "@velocious/testing"
|
|
3
3
|
import {createConnection, createServer} from "node:net"
|
|
4
4
|
import {Duplex} from "node:stream"
|
|
5
|
-
import test from "node:test"
|
|
6
5
|
|
|
7
6
|
import {StaticTokenStore} from "../../src/auth/static-token-store.js"
|
|
8
7
|
import {MultiplexedSocketductConnection} from "../../src/client/multiplexed-connection.js"
|
|
@@ -25,7 +24,7 @@ async function listen(server) {
|
|
|
25
24
|
server.listen(0, "127.0.0.1", () => { server.off("error", reject); resolve(undefined) })
|
|
26
25
|
})
|
|
27
26
|
const address = server.address()
|
|
28
|
-
|
|
27
|
+
if (address === null || typeof address === "string") throw new Error("Expected a bound TCP address")
|
|
29
28
|
return address.port
|
|
30
29
|
}
|
|
31
30
|
|
|
@@ -77,263 +76,266 @@ function tokens() {
|
|
|
77
76
|
return new StaticTokenStore([{username: "edge", token: "valid-token"}])
|
|
78
77
|
}
|
|
79
78
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const invalidConnection = new MultiplexedSocketductConnection({
|
|
94
|
-
relay: {host: "127.0.0.1", port: relay.port, token: "wrong-token"},
|
|
95
|
-
maxReconnectAttempts: 2
|
|
96
|
-
})
|
|
97
|
-
t.after(() => invalidConnection.close())
|
|
98
|
-
const invalid = invalidConnection.createConnection({host: "127.0.0.1", port: 9})
|
|
99
|
-
const authError = await nextError(invalid)
|
|
100
|
-
assert(authError instanceof SocketductSessionLostError)
|
|
101
|
-
assert.equal(authError.code, "AUTH_REQUIRED")
|
|
102
|
-
await new Promise((resolve) => setTimeout(resolve, 30))
|
|
103
|
-
assert.equal(invalidConnection.status().reconnectAttempts, 0)
|
|
104
|
-
})
|
|
79
|
+
describe("multiplexed failures", () => {
|
|
80
|
+
it("authentication and exact-target authorization fail closed", async () => withTestCleanup(async (after) => {
|
|
81
|
+
const relay = await startMultiplexedTcpRelay({tokens: tokens(), authorizeTarget: () => false})
|
|
82
|
+
after(() => relay.close())
|
|
83
|
+
const unauthorizedConnection = new MultiplexedSocketductConnection({
|
|
84
|
+
relay: {host: "127.0.0.1", port: relay.port, token: "valid-token"}
|
|
85
|
+
})
|
|
86
|
+
after(() => unauthorizedConnection.close())
|
|
87
|
+
const denied = unauthorizedConnection.createConnection({host: "127.0.0.1", port: 9})
|
|
88
|
+
const deniedError = await nextError(denied)
|
|
89
|
+
expect(deniedError instanceof SocketductStreamError).toBeTruthy()
|
|
90
|
+
expect(deniedError.code).toBe("TARGET_UNAUTHORIZED")
|
|
91
|
+
expect(relay.status().activeTargets).toBe(0)
|
|
105
92
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const connection = new MultiplexedSocketductConnection({relay: {host: "127.0.0.1", port: relay.port, token: "valid-token"}})
|
|
119
|
-
t.after(() => connection.close())
|
|
120
|
-
const failed = connection.createConnection({host: "127.0.0.1", port: failedPort})
|
|
121
|
-
const sibling = connection.createConnection({host: "127.0.0.1", port: echoPort})
|
|
122
|
-
const failure = await nextError(failed)
|
|
123
|
-
assert.equal(failure.code, "TARGET_OPEN_FAILED")
|
|
124
|
-
await onceEvent(sibling, "connect")
|
|
125
|
-
const response = readText(sibling)
|
|
126
|
-
sibling.write("still-open")
|
|
127
|
-
assert.equal(await response, "still-open")
|
|
128
|
-
assert.equal(relay.status().physicalConnections, 1)
|
|
129
|
-
sibling.destroy()
|
|
130
|
-
})
|
|
93
|
+
const invalidConnection = new MultiplexedSocketductConnection({
|
|
94
|
+
relay: {host: "127.0.0.1", port: relay.port, token: "wrong-token"},
|
|
95
|
+
maxReconnectAttempts: 2
|
|
96
|
+
})
|
|
97
|
+
after(() => invalidConnection.close())
|
|
98
|
+
const invalid = invalidConnection.createConnection({host: "127.0.0.1", port: 9})
|
|
99
|
+
const authError = await nextError(invalid)
|
|
100
|
+
expect(authError instanceof SocketductSessionLostError).toBeTruthy()
|
|
101
|
+
expect(authError.code).toBe("AUTH_REQUIRED")
|
|
102
|
+
await new Promise((resolve) => setTimeout(resolve, 30))
|
|
103
|
+
expect(invalidConnection.status().reconnectAttempts).toBe(0)
|
|
104
|
+
}))
|
|
131
105
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const survivorReady = onceEvent(survivor, "ready")
|
|
158
|
-
await relay.closeActiveTransport()
|
|
159
|
-
const targetToLose = accepted[0]
|
|
160
|
-
assert(targetToLose !== undefined)
|
|
161
|
-
targetToLose.destroy()
|
|
162
|
-
const error = await lostError
|
|
163
|
-
assert(error instanceof SocketductSessionLostError)
|
|
164
|
-
assert.equal(error.code, "RESUME_SESSION_LOST")
|
|
165
|
-
await survivorReady
|
|
166
|
-
const response = readText(survivor)
|
|
167
|
-
survivor.write("resumed")
|
|
168
|
-
assert.equal(await response, "resumed")
|
|
169
|
-
assert.equal(connection.status().reconnectAttempts, 1)
|
|
170
|
-
survivor.destroy()
|
|
171
|
-
})
|
|
106
|
+
it("target open failure and stream reset are isolated from siblings", async () => withTestCleanup(async (after) => {
|
|
107
|
+
const echo = createServer((socket) => socket.on("data", (chunk) => socket.write(chunk)))
|
|
108
|
+
const echoPort = await listen(echo)
|
|
109
|
+
after(() => closeServer(echo))
|
|
110
|
+
const unused = createServer()
|
|
111
|
+
const failedPort = await listen(unused)
|
|
112
|
+
await closeServer(unused)
|
|
113
|
+
const relay = await startMultiplexedTcpRelay({
|
|
114
|
+
tokens: tokens(),
|
|
115
|
+
authorizeTarget: ({target}) => target.host === "127.0.0.1" && (target.port === echoPort || target.port === failedPort)
|
|
116
|
+
})
|
|
117
|
+
after(() => relay.close())
|
|
118
|
+
const connection = new MultiplexedSocketductConnection({relay: {host: "127.0.0.1", port: relay.port, token: "valid-token"}})
|
|
119
|
+
after(() => connection.close())
|
|
120
|
+
const failed = connection.createConnection({host: "127.0.0.1", port: failedPort})
|
|
121
|
+
const sibling = connection.createConnection({host: "127.0.0.1", port: echoPort})
|
|
122
|
+
const failure = await nextError(failed)
|
|
123
|
+
expect(failure.code).toBe("TARGET_OPEN_FAILED")
|
|
124
|
+
await onceEvent(sibling, "connect")
|
|
125
|
+
const response = readText(sibling)
|
|
126
|
+
sibling.write("still-open")
|
|
127
|
+
expect(await response).toBe("still-open")
|
|
128
|
+
expect(relay.status().physicalConnections).toBe(1)
|
|
129
|
+
sibling.destroy()
|
|
130
|
+
}))
|
|
172
131
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
132
|
+
it("resume loss resets only the affected stream while its sibling resumes", async () => withTestCleanup(async (after) => {
|
|
133
|
+
/** @type {import('node:net').Socket[]} */
|
|
134
|
+
const accepted = []
|
|
135
|
+
const target = createServer((socket) => {
|
|
136
|
+
accepted.push(socket)
|
|
137
|
+
socket.on("data", (chunk) => socket.write(chunk))
|
|
138
|
+
})
|
|
139
|
+
const targetPort = await listen(target)
|
|
140
|
+
after(() => closeServer(target))
|
|
141
|
+
const relay = await startMultiplexedTcpRelay({
|
|
142
|
+
tokens: tokens(),
|
|
143
|
+
authorizeTarget: ({target: requested}) => requested.port === targetPort,
|
|
144
|
+
resumeWindowMs: 2_000
|
|
145
|
+
})
|
|
146
|
+
after(() => relay.close())
|
|
147
|
+
const connection = new MultiplexedSocketductConnection({
|
|
148
|
+
relay: {host: "127.0.0.1", port: relay.port, token: "valid-token"},
|
|
149
|
+
reconnectDelayMs: 40
|
|
150
|
+
})
|
|
151
|
+
after(() => connection.close())
|
|
152
|
+
const lost = connection.createConnection({host: "127.0.0.1", port: targetPort, sessionName: "lost"})
|
|
153
|
+
const survivor = connection.createConnection({host: "127.0.0.1", port: targetPort, sessionName: "survivor"})
|
|
154
|
+
await Promise.all([onceEvent(lost, "connect"), onceEvent(survivor, "connect")])
|
|
155
|
+
expect(accepted.length).toBe(2)
|
|
156
|
+
const lostError = nextError(lost)
|
|
157
|
+
const survivorReady = onceEvent(survivor, "ready")
|
|
158
|
+
await relay.closeActiveTransport()
|
|
159
|
+
const targetToLose = accepted[0]
|
|
160
|
+
expect(targetToLose).toBeDefined()
|
|
161
|
+
if (targetToLose === undefined) throw new Error("Expected an accepted socket")
|
|
162
|
+
targetToLose.destroy()
|
|
163
|
+
const error = await lostError
|
|
164
|
+
expect(error instanceof SocketductSessionLostError).toBeTruthy()
|
|
165
|
+
expect(error.code).toBe("RESUME_SESSION_LOST")
|
|
166
|
+
await survivorReady
|
|
167
|
+
const response = readText(survivor)
|
|
168
|
+
survivor.write("resumed")
|
|
169
|
+
expect(await response).toBe("resumed")
|
|
170
|
+
expect(connection.status().reconnectAttempts).toBe(1)
|
|
171
|
+
survivor.destroy()
|
|
172
|
+
}))
|
|
204
173
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
174
|
+
it("a drop between OPEN and OPENED resumes the same stream ID safely", async () => withTestCleanup(async (after) => {
|
|
175
|
+
/** @type {Duplex | undefined} */
|
|
176
|
+
let delayedTarget
|
|
177
|
+
const relay = await startMultiplexedTcpRelay({
|
|
178
|
+
tokens: tokens(),
|
|
179
|
+
authorizeTarget: () => true,
|
|
180
|
+
createTargetConnection: () => {
|
|
181
|
+
delayedTarget = new Duplex({
|
|
182
|
+
read() {},
|
|
183
|
+
write(_chunk, _encoding, callback) { callback() }
|
|
184
|
+
})
|
|
185
|
+
return /** @type {import('node:net').Socket} */ (/** @type {unknown} */ (delayedTarget))
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
after(() => relay.close())
|
|
189
|
+
const connection = new MultiplexedSocketductConnection({
|
|
190
|
+
relay: {host: "127.0.0.1", port: relay.port, token: "valid-token"},
|
|
191
|
+
reconnectDelayMs: 30
|
|
192
|
+
})
|
|
193
|
+
after(() => connection.close())
|
|
194
|
+
const socket = connection.createConnection({host: "target.internal", port: 1234})
|
|
195
|
+
const connected = onceEvent(socket, "connect")
|
|
196
|
+
await waitUntil(() => delayedTarget !== undefined)
|
|
197
|
+
await relay.closeActiveTransport()
|
|
198
|
+
delayedTarget?.emit("connect")
|
|
199
|
+
await connected
|
|
200
|
+
expect(socket.streamId).toBe(1)
|
|
201
|
+
expect(connection.status().reconnectAttempts).toBe(1)
|
|
202
|
+
expect(relay.status().resumeSuccesses).toBe(1)
|
|
203
|
+
socket.destroy()
|
|
204
|
+
}))
|
|
231
205
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
206
|
+
it("bounded per-stream and aggregate queues fail only the offending stream", async () => withTestCleanup(async (after) => {
|
|
207
|
+
const target = createServer((socket) => socket.pause())
|
|
208
|
+
const targetPort = await listen(target)
|
|
209
|
+
after(() => closeServer(target))
|
|
210
|
+
const relay = await startMultiplexedTcpRelay({tokens: tokens(), authorizeTarget: ({target: requested}) => requested.port === targetPort})
|
|
211
|
+
after(() => relay.close())
|
|
212
|
+
const connection = new MultiplexedSocketductConnection({
|
|
213
|
+
relay: {host: "127.0.0.1", port: relay.port, token: "valid-token"},
|
|
214
|
+
maxStreamBufferBytes: 32,
|
|
215
|
+
maxConnectionBufferBytes: 48,
|
|
216
|
+
maxFrameBytes: 16,
|
|
217
|
+
initialWindowBytes: 32
|
|
218
|
+
})
|
|
219
|
+
after(() => connection.close())
|
|
220
|
+
const offender = connection.createConnection({host: "127.0.0.1", port: targetPort})
|
|
221
|
+
const sibling = connection.createConnection({host: "127.0.0.1", port: targetPort})
|
|
222
|
+
await Promise.all([onceEvent(offender, "connect"), onceEvent(sibling, "connect")])
|
|
223
|
+
const errorPromise = nextError(offender)
|
|
224
|
+
offender.write(Buffer.alloc(33))
|
|
225
|
+
const error = await errorPromise
|
|
226
|
+
expect(error.code).toBe("SOCKETDUCT_BUFFER_LIMIT")
|
|
227
|
+
expect(sibling.destroyed).toBe(false)
|
|
228
|
+
expect(connection.status().dataQueuedBytes <= 48).toBeTruthy()
|
|
229
|
+
expect(connection.status().controlQueuedBytes <= 1024 * 1024).toBeTruthy()
|
|
230
|
+
sibling.destroy()
|
|
239
231
|
}))
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
232
|
+
|
|
233
|
+
it("round-robin data scheduling lets a small control flow pass a large upload", async () => withTestCleanup(async (after) => {
|
|
234
|
+
let largeBytesObserved = 0
|
|
235
|
+
let largeCompleted = false
|
|
236
|
+
let smallObservedAfterLargeCompleted
|
|
237
|
+
const largeTarget = createServer((socket) => socket.on("data", (chunk) => {
|
|
238
|
+
largeBytesObserved += chunk.length
|
|
239
|
+
if (largeBytesObserved === 64 * 1024) largeCompleted = true
|
|
240
|
+
}))
|
|
241
|
+
const largePort = await listen(largeTarget)
|
|
242
|
+
after(() => closeServer(largeTarget))
|
|
243
|
+
const smallTarget = createServer((socket) => socket.on("data", (chunk) => {
|
|
244
|
+
smallObservedAfterLargeCompleted = largeCompleted
|
|
245
|
+
socket.write(chunk)
|
|
246
|
+
}))
|
|
247
|
+
const smallPort = await listen(smallTarget)
|
|
248
|
+
after(() => closeServer(smallTarget))
|
|
249
|
+
const relay = await startMultiplexedTcpRelay({
|
|
250
|
+
tokens: tokens(),
|
|
251
|
+
authorizeTarget: ({target}) => target.port === largePort || target.port === smallPort,
|
|
252
|
+
maxFrameBytes: 1024
|
|
253
|
+
})
|
|
254
|
+
after(() => relay.close())
|
|
255
|
+
const connection = new MultiplexedSocketductConnection({
|
|
256
|
+
relay: {host: "127.0.0.1", port: relay.port, token: "valid-token"},
|
|
257
|
+
maxFrameBytes: 1024
|
|
258
|
+
})
|
|
259
|
+
after(() => connection.close())
|
|
260
|
+
const large = connection.createConnection({host: "127.0.0.1", port: largePort})
|
|
261
|
+
const small = connection.createConnection({host: "127.0.0.1", port: smallPort})
|
|
262
|
+
await Promise.all([onceEvent(large, "connect"), onceEvent(small, "connect")])
|
|
263
|
+
const smallResponse = readText(small)
|
|
264
|
+
large.write(Buffer.alloc(64 * 1024))
|
|
265
|
+
small.write("control")
|
|
266
|
+
expect(await smallResponse).toBe("control")
|
|
267
|
+
expect(smallObservedAfterLargeCompleted).toBe(false)
|
|
268
|
+
expect(largeBytesObserved < 64 * 1024).toBeTruthy()
|
|
269
|
+
large.destroy()
|
|
270
|
+
small.destroy()
|
|
245
271
|
}))
|
|
246
|
-
const smallPort = await listen(smallTarget)
|
|
247
|
-
t.after(() => closeServer(smallTarget))
|
|
248
|
-
const relay = await startMultiplexedTcpRelay({
|
|
249
|
-
tokens: tokens(),
|
|
250
|
-
authorizeTarget: ({target}) => target.port === largePort || target.port === smallPort,
|
|
251
|
-
maxFrameBytes: 1024
|
|
252
|
-
})
|
|
253
|
-
t.after(() => relay.close())
|
|
254
|
-
const connection = new MultiplexedSocketductConnection({
|
|
255
|
-
relay: {host: "127.0.0.1", port: relay.port, token: "valid-token"},
|
|
256
|
-
maxFrameBytes: 1024
|
|
257
|
-
})
|
|
258
|
-
t.after(() => connection.close())
|
|
259
|
-
const large = connection.createConnection({host: "127.0.0.1", port: largePort})
|
|
260
|
-
const small = connection.createConnection({host: "127.0.0.1", port: smallPort})
|
|
261
|
-
await Promise.all([onceEvent(large, "connect"), onceEvent(small, "connect")])
|
|
262
|
-
const smallResponse = readText(small)
|
|
263
|
-
large.write(Buffer.alloc(64 * 1024))
|
|
264
|
-
small.write("control")
|
|
265
|
-
assert.equal(await smallResponse, "control")
|
|
266
|
-
assert.equal(smallObservedAfterLargeCompleted, false)
|
|
267
|
-
assert(largeBytesObserved < 64 * 1024)
|
|
268
|
-
large.destroy()
|
|
269
|
-
small.destroy()
|
|
270
|
-
})
|
|
271
272
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
273
|
+
it("per-stream receive windows bound a paused consumer and resume independently", async () => withTestCleanup(async (after) => {
|
|
274
|
+
const payload = Buffer.alloc(4 * 1024, 0x61)
|
|
275
|
+
const target = createServer((socket) => socket.write(payload))
|
|
276
|
+
const targetPort = await listen(target)
|
|
277
|
+
after(() => closeServer(target))
|
|
278
|
+
const relay = await startMultiplexedTcpRelay({
|
|
279
|
+
tokens: tokens(),
|
|
280
|
+
authorizeTarget: ({target: requested}) => requested.port === targetPort,
|
|
281
|
+
initialWindowBytes: 1024,
|
|
282
|
+
maxFrameBytes: 512,
|
|
283
|
+
maxStreamBufferBytes: 8 * 1024
|
|
284
|
+
})
|
|
285
|
+
after(() => relay.close())
|
|
286
|
+
const connection = new MultiplexedSocketductConnection({
|
|
287
|
+
relay: {host: "127.0.0.1", port: relay.port, token: "valid-token"},
|
|
288
|
+
initialWindowBytes: 1024,
|
|
289
|
+
maxFrameBytes: 512
|
|
290
|
+
})
|
|
291
|
+
after(() => connection.close())
|
|
292
|
+
const paused = connection.createConnection({
|
|
293
|
+
host: "127.0.0.1",
|
|
294
|
+
port: targetPort,
|
|
295
|
+
readableHighWaterMark: 256
|
|
296
|
+
})
|
|
297
|
+
await onceEvent(paused, "connect")
|
|
298
|
+
await waitUntil(() => relay.status().queuedBytes === payload.length)
|
|
299
|
+
expect(relay.status().resets).toBe(0)
|
|
300
|
+
expect(relay.status().queuedBytes <= 8 * 1024).toBeTruthy()
|
|
300
301
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
302
|
+
const received = await new Promise((resolve, reject) => {
|
|
303
|
+
/** @type {Buffer[]} */
|
|
304
|
+
const chunks = []
|
|
305
|
+
let bytes = 0
|
|
306
|
+
paused.on("data", (chunk) => {
|
|
307
|
+
chunks.push(chunk)
|
|
308
|
+
bytes += chunk.length
|
|
309
|
+
if (bytes === payload.length) resolve(Buffer.concat(chunks))
|
|
310
|
+
})
|
|
311
|
+
paused.once("error", reject)
|
|
312
|
+
paused.resume()
|
|
309
313
|
})
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
await waitUntil(() => relay.status().queuedBytes === 0)
|
|
315
|
-
paused.destroy()
|
|
316
|
-
})
|
|
314
|
+
expect(received.equals(payload)).toBe(true)
|
|
315
|
+
await waitUntil(() => relay.status().queuedBytes === 0)
|
|
316
|
+
paused.destroy()
|
|
317
|
+
}))
|
|
317
318
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
319
|
+
it("relay accepts local target sockets without creating extra WAN transports", async () => withTestCleanup(async (after) => {
|
|
320
|
+
const target = createServer((socket) => socket.end())
|
|
321
|
+
const port = await listen(target)
|
|
322
|
+
after(() => closeServer(target))
|
|
323
|
+
let localConnects = 0
|
|
324
|
+
const relay = await startMultiplexedTcpRelay({
|
|
325
|
+
tokens: tokens(),
|
|
326
|
+
authorizeTarget: ({target: requested}) => requested.port === port,
|
|
327
|
+
createTargetConnection: (requested) => {
|
|
328
|
+
localConnects += 1
|
|
329
|
+
return createConnection(requested)
|
|
330
|
+
}
|
|
331
|
+
})
|
|
332
|
+
after(() => relay.close())
|
|
333
|
+
const connection = new MultiplexedSocketductConnection({relay: {host: "127.0.0.1", port: relay.port, token: "valid-token"}})
|
|
334
|
+
after(() => connection.close())
|
|
335
|
+
const streams = Array.from({length: 4}, () => connection.createConnection({host: "127.0.0.1", port}))
|
|
336
|
+
await Promise.all(streams.map((socket) => onceEvent(socket, "connect")))
|
|
337
|
+
expect(localConnects).toBe(4)
|
|
338
|
+
expect(relay.status().physicalAccepts).toBe(1)
|
|
339
|
+
streams.forEach((socket) => socket.destroy())
|
|
340
|
+
}))
|
|
339
341
|
})
|