teyyare 0.2.7 → 0.2.9
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 +97 -57
- package/bin/teyyare.js +36 -3
- package/package.json +6 -2
- package/src/core/protocol.js +39 -6
- package/src/index.js +119 -57
- package/src/receiver/pipeline.js +272 -57
- package/src/sender/pipeline.js +51 -9
- package/test/e2e/e2e.js +276 -0
- package/test/integration/pull.test.js +51 -0
- package/test/integration/resume.test.js +62 -0
- package/test/integration/single-file.test.js +85 -0
- package/test/unit/protocol.test.js +20 -0
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ Teyyare is the first real integration workload for both projects, proving that a
|
|
|
26
26
|
replication / transport
|
|
27
27
|
│
|
|
28
28
|
▼
|
|
29
|
-
TCP
|
|
29
|
+
TCP (Multi-Channel)
|
|
30
30
|
│
|
|
31
31
|
▼
|
|
32
32
|
Remote Teyyare
|
|
@@ -37,7 +37,7 @@ Teyyare is the first real integration workload for both projects, proving that a
|
|
|
37
37
|
flush
|
|
38
38
|
│
|
|
39
39
|
▼
|
|
40
|
-
Disk
|
|
40
|
+
Disk (writev + pre-allocation)
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
---
|
|
@@ -52,9 +52,9 @@ Teyyare is the first real integration workload for both projects, proving that a
|
|
|
52
52
|
|
|
53
53
|
---
|
|
54
54
|
|
|
55
|
-
##
|
|
55
|
+
## Architecture & Systems Optimizations
|
|
56
56
|
|
|
57
|
-
### Sender Hot Path
|
|
57
|
+
### 1. Sender Hot Path
|
|
58
58
|
```text
|
|
59
59
|
FileHandle.read()
|
|
60
60
|
│
|
|
@@ -76,29 +76,46 @@ MutableGeneration
|
|
|
76
76
|
FrozenGeneration [metaBuf, payloadArena]
|
|
77
77
|
│
|
|
78
78
|
▼
|
|
79
|
-
Raptiye.submitBatch() (
|
|
79
|
+
Raptiye.submitBatch() (In-Flight Sliding Window up to 128 MB)
|
|
80
80
|
│
|
|
81
81
|
▼
|
|
82
|
-
TCP (
|
|
82
|
+
Multi-Channel TCP Pool (Zero-Allocation Round-Robin sendv)
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
### Receiver Hot Path
|
|
85
|
+
### 2. Receiver Hot Path
|
|
86
86
|
```text
|
|
87
|
-
TCP
|
|
87
|
+
Multi-Channel TCP
|
|
88
88
|
│
|
|
89
89
|
▼
|
|
90
|
-
|
|
90
|
+
Zero-Copy Pointer Framer (O(1) cursor, no shift(), scratch header reuse)
|
|
91
|
+
│
|
|
92
|
+
▼
|
|
93
|
+
Raptiye (Frame decode + verification)
|
|
91
94
|
│
|
|
92
95
|
▼
|
|
93
96
|
FrozenGeneration (O(1) zero-copy view via FrozenArenaStore.fromBuffers)
|
|
94
97
|
│
|
|
95
98
|
▼
|
|
96
|
-
|
|
99
|
+
Chunk Coalescing Engine (Merges contiguous chunk runs)
|
|
100
|
+
│
|
|
101
|
+
▼
|
|
102
|
+
FileHandle.writev(buffers, startOffset) (Direct native scatter-gather writes)
|
|
97
103
|
│
|
|
98
104
|
▼
|
|
99
|
-
|
|
105
|
+
Debounced Asynchronous State Persistence (.teyyare-state)
|
|
100
106
|
```
|
|
101
107
|
|
|
108
|
+
### 3. Key Systems Optimizations Implemented
|
|
109
|
+
|
|
110
|
+
- **Disk Pre-Allocation (`FileHandle.truncate(targetSize)`)**: Zero-fragmentation disk pre-allocation upon receiving `FILE_BEGIN`, eliminating filesystem dynamic block allocation latency during high-speed writes.
|
|
111
|
+
- **Native Scatter-Gather Writes (`writev`)**: Receiver coalesces contiguous chunks arriving in the same generation batch into a single system call via `writev`, reducing write syscalls by ~87.5%.
|
|
112
|
+
- **Debounced Asynchronous State Saving**: `.teyyare-state` persistence is debounced to disk (every 250ms or 32 chunks) and drained cleanly before final file rename, completely eliminating blocking JSON serialization from the hot-path.
|
|
113
|
+
- **In-Flight ACK Pipelining (Sliding Window)**: Supports up to 32 concurrent batches and 128 MB in-flight capacity. Senders stream generations continuously across 4–8 parallel TCP channels without stalling on single-batch ACKs.
|
|
114
|
+
- **Pointer-Based Zero-Copy TCP Framer**: Eliminated $O(N)$ array `shift()` operations and repeated `Buffer.allocUnsafe` calls on the TCP stream reader using an $O(1)$ cursor and amortized array compaction.
|
|
115
|
+
- **Zero-Allocation Channel Selection**: Eliminated `pool.filter` garbage collector churn in `TCPTransport.prototype.sendv` and `send` using an in-place circular lookup.
|
|
116
|
+
- **Dynamic WAN Tuning**: `PULL_REQUEST` carries client-specified `--chunk-size` (e.g. 2 MB) and `--batch-size` (e.g. 16 MB) to saturate high-latency, high-bandwidth WAN connections.
|
|
117
|
+
- **Multi-Channel Out-of-Order Packet Resilience**: Resilient state reconciliation for `TRANSFER_BEGIN`, `FILE_BEGIN`, `FILE_END`, and `TRANSFER_END` packets arriving out-of-order across parallel TCP sockets.
|
|
118
|
+
|
|
102
119
|
---
|
|
103
120
|
|
|
104
121
|
## CLI Usage
|
|
@@ -109,72 +126,95 @@ FileHandle.write(payload, 0, length, offset) (Direct random-access write)
|
|
|
109
126
|
teyyare serve --port 7421 --dir /destination/folder
|
|
110
127
|
```
|
|
111
128
|
|
|
112
|
-
### Send File or Directory
|
|
129
|
+
### Send File or Directory (Push)
|
|
113
130
|
```bash
|
|
114
|
-
# Single file transfer
|
|
115
|
-
teyyare send ./large-file.bin 127.0.0.1:7421:/tmp/large-file.bin
|
|
131
|
+
# Single file transfer with 4 parallel channels
|
|
132
|
+
teyyare send ./large-file.bin 127.0.0.1:7421:/tmp/large-file.bin -c 4
|
|
116
133
|
|
|
117
|
-
# Directory transfer with nested
|
|
118
|
-
teyyare send ./dist server.local:7421:/var/www/app
|
|
134
|
+
# Directory transfer with nested hierarchy
|
|
135
|
+
teyyare send ./dist server.local:7421:/var/www/app -c 4
|
|
119
136
|
```
|
|
120
137
|
|
|
121
|
-
###
|
|
122
|
-
```
|
|
123
|
-
|
|
138
|
+
### Pull File or Directory (Download)
|
|
139
|
+
```bash
|
|
140
|
+
# Single file download from remote server with 8 channels and 2MB WAN chunks
|
|
141
|
+
teyyare pull 38.242.216.102:7421:/root/cdn/app.zip ./ -c 8 --chunk-size 2M --batch-size 16M
|
|
142
|
+
|
|
143
|
+
# Directory pull
|
|
144
|
+
teyyare pull server:7421:/backup/db.tar.zst ./ -c 4
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### CLI Options
|
|
148
|
+
|
|
149
|
+
| Flag | Description | Default |
|
|
150
|
+
|---|---|---|
|
|
151
|
+
| `-c, --channels <n>` | Number of parallel TCP socket channels | `4` (remote), `1` (local) |
|
|
152
|
+
| `--chunk-size <size>` | Chunk size, e.g. `512K`, `1M`, `2M`, `4M` | `2M` (remote), `1M` (local) |
|
|
153
|
+
| `--batch-size <size>` | Generation batch size, e.g. `8M`, `16M` | `8x chunk size` |
|
|
154
|
+
| `--port <port>` | Server port number | `7421` |
|
|
155
|
+
| `--dir <path>` | Root directory for `serve` command | Current directory (`./`) |
|
|
156
|
+
|
|
157
|
+
---
|
|
124
158
|
|
|
125
|
-
|
|
159
|
+
## Progress Display
|
|
126
160
|
|
|
127
|
-
|
|
128
|
-
|
|
161
|
+
```text
|
|
162
|
+
Teyyare [Download]
|
|
163
|
+
|
|
164
|
+
app.zip
|
|
129
165
|
|
|
130
|
-
|
|
131
|
-
|
|
166
|
+
333.54 MB / 333.54 MB
|
|
167
|
+
100.0%
|
|
132
168
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
169
|
+
network speed 324.5 MB/s
|
|
170
|
+
disk speed 1240.2 MB/s
|
|
171
|
+
channels 8
|
|
172
|
+
ETA --:--
|
|
137
173
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
174
|
+
chunks 167 / 167
|
|
175
|
+
generation 21
|
|
176
|
+
verified CRC32 167
|
|
177
|
+
resume hits 0
|
|
141
178
|
```
|
|
142
179
|
|
|
143
180
|
---
|
|
144
181
|
|
|
145
|
-
##
|
|
182
|
+
## Test Suite & Softscope Profiling
|
|
146
183
|
|
|
147
|
-
|
|
184
|
+
### 1. Unit & Integration Tests
|
|
185
|
+
Run all 24 automated tests covering protocol encoding, generation ownership, backpressure, chaos recovery, and multi-channel transfers:
|
|
148
186
|
```bash
|
|
149
|
-
npm
|
|
187
|
+
npm test
|
|
150
188
|
```
|
|
151
189
|
|
|
152
|
-
###
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
| **C** | fs -> Raptiye -> fs | **85.5 MB/s** | 585ms |
|
|
158
|
-
| **D** | Teyyare (Muttafa + Raptiye + fs) | **38.0 MB/s** | 1320ms |
|
|
190
|
+
### 2. End-to-End (E2E) Test Suite
|
|
191
|
+
Runs 4 comprehensive end-to-end scenarios (Single Upload, Directory Upload, Single Pull, 50% Resumed Transfer):
|
|
192
|
+
```bash
|
|
193
|
+
npm run e2e
|
|
194
|
+
```
|
|
159
195
|
|
|
160
|
-
###
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
196
|
+
### 3. Softscope Profiling
|
|
197
|
+
Profile CPU bottlenecks, hot call paths, and memory allocations using [`softscope`](https://github.com/litepacks/softscope):
|
|
198
|
+
```bash
|
|
199
|
+
# Markdown summary to stdout
|
|
200
|
+
npm run e2e:md
|
|
201
|
+
|
|
202
|
+
# Full interactive terminal dashboard (TUI)
|
|
203
|
+
npm run e2e:profile
|
|
204
|
+
|
|
205
|
+
# Live metrics during execution
|
|
206
|
+
npm run e2e:live
|
|
207
|
+
```
|
|
168
208
|
|
|
169
209
|
---
|
|
170
210
|
|
|
171
|
-
##
|
|
211
|
+
## Benchmark & Throughput Results
|
|
172
212
|
|
|
173
|
-
|
|
174
|
-
```bash
|
|
175
|
-
npm test
|
|
176
|
-
```
|
|
213
|
+
### End-to-End Suite Summary (`test/e2e/e2e.js`)
|
|
177
214
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
215
|
+
| Scenario | Transfer Size | Duration | Throughput | Verification |
|
|
216
|
+
|---|---|---|---|---|
|
|
217
|
+
| **Single File Upload** (4 channels) | 32.0 MB | 121ms | **264.5 MB/s** | SHA-256 Match |
|
|
218
|
+
| **Directory Upload** (Nested tree) | 16.0 MB | 104ms | **153.9 MB/s** | All files verified |
|
|
219
|
+
| **Single File Pull** (4 channels) | 32.0 MB | 89ms | **359.6 MB/s** | SHA-256 Match |
|
|
220
|
+
| **Resumed Transfer** (50% skipped) | 8.0 MB | 49ms | **163.3 MB/s** | 0 retransmitted bytes |
|
package/bin/teyyare.js
CHANGED
|
@@ -19,6 +19,8 @@ function printUsage() {
|
|
|
19
19
|
|
|
20
20
|
\x1b[1mOPTIONS:\x1b[0m
|
|
21
21
|
-c, --channels <n> Parallel TCP connections (default: 4 for remote, 1 for local)
|
|
22
|
+
--chunk-size <size> Chunk size, e.g. 512K, 1M, 2M, 4M (default: 2M remote, 1M local)
|
|
23
|
+
--batch-size <size> Generation batch size, e.g. 8M, 16M (default: 8x chunk size)
|
|
22
24
|
--port <port> Port number (default: 7421)
|
|
23
25
|
--dir <path> Serve root directory (default: current dir)
|
|
24
26
|
|
|
@@ -30,6 +32,15 @@ function printUsage() {
|
|
|
30
32
|
`);
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
function parseByteSize(str) {
|
|
36
|
+
if (!str) return undefined;
|
|
37
|
+
const s = String(str).trim().toLowerCase();
|
|
38
|
+
if (s.endsWith('k') || s.endsWith('kb')) return Math.round(parseFloat(s) * 1024);
|
|
39
|
+
if (s.endsWith('m') || s.endsWith('mb')) return Math.round(parseFloat(s) * 1024 * 1024);
|
|
40
|
+
if (s.endsWith('g') || s.endsWith('gb')) return Math.round(parseFloat(s) * 1024 * 1024 * 1024);
|
|
41
|
+
return Number(str);
|
|
42
|
+
}
|
|
43
|
+
|
|
33
44
|
function parseDestination(target) {
|
|
34
45
|
// Matches host:port:path or host:path or local path
|
|
35
46
|
if (!target.includes(':')) {
|
|
@@ -50,13 +61,13 @@ async function main() {
|
|
|
50
61
|
const args = process.argv.slice(2);
|
|
51
62
|
const command = args[0];
|
|
52
63
|
|
|
53
|
-
if (!command ||
|
|
64
|
+
if (!command || args.includes('--help') || args.includes('-h')) {
|
|
54
65
|
printUsage();
|
|
55
66
|
process.exit(0);
|
|
56
67
|
}
|
|
57
68
|
|
|
58
69
|
if (command === '--version' || command === '-v' || command === 'version') {
|
|
59
|
-
console.log('0.2.
|
|
70
|
+
console.log('0.2.8');
|
|
60
71
|
process.exit(0);
|
|
61
72
|
}
|
|
62
73
|
|
|
@@ -88,10 +99,16 @@ async function main() {
|
|
|
88
99
|
await new Promise(() => {});
|
|
89
100
|
} else if (command === 'send') {
|
|
90
101
|
let channels = undefined;
|
|
102
|
+
let chunkSize = undefined;
|
|
103
|
+
let batchSize = undefined;
|
|
91
104
|
const positional = [];
|
|
92
105
|
for (let i = 1; i < args.length; i++) {
|
|
93
106
|
if ((args[i] === '-c' || args[i] === '--channels') && args[i + 1]) {
|
|
94
107
|
channels = Number(args[++i]);
|
|
108
|
+
} else if ((args[i] === '--chunk-size' || args[i] === '--chunk') && args[i + 1]) {
|
|
109
|
+
chunkSize = parseByteSize(args[++i]);
|
|
110
|
+
} else if ((args[i] === '--batch-size' || args[i] === '--gen-size') && args[i + 1]) {
|
|
111
|
+
batchSize = parseByteSize(args[++i]);
|
|
95
112
|
} else {
|
|
96
113
|
positional.push(args[i]);
|
|
97
114
|
}
|
|
@@ -106,12 +123,17 @@ async function main() {
|
|
|
106
123
|
process.exit(1);
|
|
107
124
|
}
|
|
108
125
|
|
|
126
|
+
const pipelineOptions = {};
|
|
127
|
+
if (chunkSize) pipelineOptions.chunkSize = chunkSize;
|
|
128
|
+
if (batchSize) pipelineOptions.maxBytes = batchSize;
|
|
129
|
+
|
|
109
130
|
const { host, port } = parseDestination(target);
|
|
110
131
|
const metrics = await send({
|
|
111
132
|
sourcePath: source,
|
|
112
133
|
host,
|
|
113
134
|
port,
|
|
114
135
|
channels,
|
|
136
|
+
pipelineOptions,
|
|
115
137
|
showProgress: true
|
|
116
138
|
});
|
|
117
139
|
|
|
@@ -129,10 +151,16 @@ Peak RSS: ${(snap.peakRSS / (1024 * 1024)).toFixed(2)} MB
|
|
|
129
151
|
process.exit(0);
|
|
130
152
|
} else if (command === 'pull') {
|
|
131
153
|
let channels = undefined;
|
|
154
|
+
let chunkSize = undefined;
|
|
155
|
+
let batchSize = undefined;
|
|
132
156
|
const positional = [];
|
|
133
157
|
for (let i = 1; i < args.length; i++) {
|
|
134
158
|
if ((args[i] === '-c' || args[i] === '--channels') && args[i + 1]) {
|
|
135
159
|
channels = Number(args[++i]);
|
|
160
|
+
} else if ((args[i] === '--chunk-size' || args[i] === '--chunk') && args[i + 1]) {
|
|
161
|
+
chunkSize = parseByteSize(args[++i]);
|
|
162
|
+
} else if ((args[i] === '--batch-size' || args[i] === '--gen-size') && args[i + 1]) {
|
|
163
|
+
batchSize = parseByteSize(args[++i]);
|
|
136
164
|
} else {
|
|
137
165
|
positional.push(args[i]);
|
|
138
166
|
}
|
|
@@ -147,6 +175,10 @@ Peak RSS: ${(snap.peakRSS / (1024 * 1024)).toFixed(2)} MB
|
|
|
147
175
|
process.exit(1);
|
|
148
176
|
}
|
|
149
177
|
|
|
178
|
+
const pipelineOptions = {};
|
|
179
|
+
if (chunkSize) pipelineOptions.chunkSize = chunkSize;
|
|
180
|
+
if (batchSize) pipelineOptions.maxBytes = batchSize;
|
|
181
|
+
|
|
150
182
|
const { host, port, destPath: remotePath } = parseDestination(source);
|
|
151
183
|
const metrics = await pull({
|
|
152
184
|
sourceRemotePath: remotePath,
|
|
@@ -154,6 +186,7 @@ Peak RSS: ${(snap.peakRSS / (1024 * 1024)).toFixed(2)} MB
|
|
|
154
186
|
port,
|
|
155
187
|
channels,
|
|
156
188
|
destinationDir: target,
|
|
189
|
+
pipelineOptions,
|
|
157
190
|
showProgress: true
|
|
158
191
|
});
|
|
159
192
|
|
|
@@ -170,7 +203,7 @@ Peak RSS: ${(snap.peakRSS / (1024 * 1024)).toFixed(2)} MB
|
|
|
170
203
|
`);
|
|
171
204
|
process.exit(0);
|
|
172
205
|
} else if (command === 'status') {
|
|
173
|
-
console.log('\x1b[1m\x1b[36mTeyyare\x1b[0m v0.2.
|
|
206
|
+
console.log('\x1b[1m\x1b[36mTeyyare\x1b[0m v0.2.8 status: ready');
|
|
174
207
|
} else {
|
|
175
208
|
console.error(`Unknown command: ${command}`);
|
|
176
209
|
printUsage();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "teyyare",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "High-performance resumable file transfer engine and CLI built on muttafa and raptiye",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "node --test test/**/*.test.js",
|
|
12
|
-
"bench": "node bench/run-all.js"
|
|
12
|
+
"bench": "node bench/run-all.js",
|
|
13
|
+
"e2e": "node test/e2e/e2e.js",
|
|
14
|
+
"e2e:profile": "softscope test/e2e/e2e.js",
|
|
15
|
+
"e2e:live": "softscope --live test/e2e/e2e.js",
|
|
16
|
+
"e2e:md": "softscope --md test/e2e/e2e.js"
|
|
13
17
|
},
|
|
14
18
|
"keywords": [
|
|
15
19
|
"file-transfer",
|
package/src/core/protocol.js
CHANGED
|
@@ -76,10 +76,10 @@ export function unpackChunkMeta(buf, offset = 0) {
|
|
|
76
76
|
* 14..45: sha256 hex (32 bytes raw or 64 hex chars)
|
|
77
77
|
* 46..: path utf8
|
|
78
78
|
*/
|
|
79
|
-
export function packFileBegin({ fileId, fileSize, path, sha256 = '' }) {
|
|
79
|
+
export function packFileBegin({ fileId, fileSize, path, sha256 = '', chunkSize = 1048576 }) {
|
|
80
80
|
const pathBytes = encoder.encode(path);
|
|
81
81
|
const shaBytes = encoder.encode(sha256.padEnd(64, '0').slice(0, 64));
|
|
82
|
-
const total = 14 + 64 + pathBytes.byteLength;
|
|
82
|
+
const total = 14 + 64 + pathBytes.byteLength + 4;
|
|
83
83
|
const buf = new Uint8Array(total);
|
|
84
84
|
const view = new DataView(buf.buffer, buf.byteOffset, total);
|
|
85
85
|
|
|
@@ -88,6 +88,7 @@ export function packFileBegin({ fileId, fileSize, path, sha256 = '' }) {
|
|
|
88
88
|
view.setUint16(12, pathBytes.byteLength, false);
|
|
89
89
|
buf.set(shaBytes, 14);
|
|
90
90
|
buf.set(pathBytes, 14 + 64);
|
|
91
|
+
view.setUint32(14 + 64 + pathBytes.byteLength, chunkSize >>> 0, false);
|
|
91
92
|
return buf;
|
|
92
93
|
}
|
|
93
94
|
|
|
@@ -98,7 +99,12 @@ export function unpackFileBegin(buf) {
|
|
|
98
99
|
const pathLen = view.getUint16(12, false);
|
|
99
100
|
const sha256 = decoder.decode(buf.subarray(14, 14 + 64)).replace(/0+$/, '');
|
|
100
101
|
const path = decoder.decode(buf.subarray(14 + 64, 14 + 64 + pathLen));
|
|
101
|
-
|
|
102
|
+
let chunkSize = 1048576;
|
|
103
|
+
if (buf.byteLength >= 14 + 64 + pathLen + 4) {
|
|
104
|
+
const cs = view.getUint32(14 + 64 + pathLen, false);
|
|
105
|
+
if (cs > 0) chunkSize = cs;
|
|
106
|
+
}
|
|
107
|
+
return { fileId, fileSize, path, sha256, chunkSize };
|
|
102
108
|
}
|
|
103
109
|
|
|
104
110
|
/**
|
|
@@ -167,10 +173,18 @@ export function unpackTransferEnd(buf) {
|
|
|
167
173
|
/**
|
|
168
174
|
* Pack PULL_REQUEST metadata with optional verifiedChunks bitmap/list for resume.
|
|
169
175
|
*/
|
|
170
|
-
export function packPullRequest({
|
|
176
|
+
export function packPullRequest({
|
|
177
|
+
remotePath,
|
|
178
|
+
verifiedChunks = [],
|
|
179
|
+
chunkSize = 0,
|
|
180
|
+
maxEntries = 0,
|
|
181
|
+
maxBytes = 0
|
|
182
|
+
}) {
|
|
171
183
|
const pathBytes = encoder.encode(remotePath);
|
|
172
184
|
const count = verifiedChunks.length;
|
|
173
|
-
const
|
|
185
|
+
const hasTuning = (chunkSize > 0 || maxEntries > 0 || maxBytes > 0);
|
|
186
|
+
const tuningLen = hasTuning ? 10 : 0;
|
|
187
|
+
const buf = new Uint8Array(2 + pathBytes.byteLength + 4 + count * 4 + tuningLen);
|
|
174
188
|
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
175
189
|
view.setUint16(0, pathBytes.byteLength, false);
|
|
176
190
|
buf.set(pathBytes, 2);
|
|
@@ -182,6 +196,13 @@ export function packPullRequest({ remotePath, verifiedChunks = [] }) {
|
|
|
182
196
|
view.setUint32(off, verifiedChunks[i], false);
|
|
183
197
|
off += 4;
|
|
184
198
|
}
|
|
199
|
+
|
|
200
|
+
if (hasTuning) {
|
|
201
|
+
view.setUint32(off, chunkSize >>> 0, false);
|
|
202
|
+
view.setUint16(off + 4, maxEntries & 0xffff, false);
|
|
203
|
+
view.setUint32(off + 6, maxBytes >>> 0, false);
|
|
204
|
+
}
|
|
205
|
+
|
|
185
206
|
return buf;
|
|
186
207
|
}
|
|
187
208
|
|
|
@@ -201,7 +222,19 @@ export function unpackPullRequest(buf) {
|
|
|
201
222
|
}
|
|
202
223
|
}
|
|
203
224
|
|
|
204
|
-
|
|
225
|
+
let chunkSize = undefined;
|
|
226
|
+
let maxEntries = undefined;
|
|
227
|
+
let maxBytes = undefined;
|
|
228
|
+
if (buf.byteLength >= off + 10) {
|
|
229
|
+
const cs = view.getUint32(off, false);
|
|
230
|
+
const me = view.getUint16(off + 4, false);
|
|
231
|
+
const mb = view.getUint32(off + 6, false);
|
|
232
|
+
if (cs > 0) chunkSize = cs;
|
|
233
|
+
if (me > 0) maxEntries = me;
|
|
234
|
+
if (mb > 0) maxBytes = mb;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return { remotePath, verifiedChunks, chunkSize, maxEntries, maxBytes };
|
|
205
238
|
}
|
|
206
239
|
|
|
207
240
|
/**
|