gnutella 1.0.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/CLI.md +189 -0
- package/DEVELOPER.md +193 -0
- package/LICENSE +674 -0
- package/QUICKSTART.md +133 -0
- package/README.md +74 -0
- package/bin/gnutella.ts +15 -0
- package/gnutella.json.example +18 -0
- package/package.json +72 -0
- package/src/cli.ts +692 -0
- package/src/cli_shared.ts +359 -0
- package/src/const.ts +138 -0
- package/src/gwebcache/bootstrap.ts +491 -0
- package/src/gwebcache/response.ts +391 -0
- package/src/gwebcache/shared.ts +116 -0
- package/src/gwebcache/types.ts +187 -0
- package/src/gwebcache_client.ts +13 -0
- package/src/protocol/browse_host.ts +552 -0
- package/src/protocol/client_blocking.ts +29 -0
- package/src/protocol/codec.ts +715 -0
- package/src/protocol/content_urn.ts +170 -0
- package/src/protocol/core_utils.ts +43 -0
- package/src/protocol/file_server.ts +245 -0
- package/src/protocol/ggep.ts +168 -0
- package/src/protocol/handshake.ts +199 -0
- package/src/protocol/http_download_reader.ts +112 -0
- package/src/protocol/magnet.ts +176 -0
- package/src/protocol/node.ts +416 -0
- package/src/protocol/node_handshake.ts +992 -0
- package/src/protocol/node_lifecycle.ts +210 -0
- package/src/protocol/node_protocol_runtime.ts +949 -0
- package/src/protocol/node_qrp_runtime.ts +97 -0
- package/src/protocol/node_query_routing.ts +208 -0
- package/src/protocol/node_state.ts +745 -0
- package/src/protocol/node_tls.ts +257 -0
- package/src/protocol/node_topology.ts +141 -0
- package/src/protocol/node_transfer.ts +455 -0
- package/src/protocol/node_types.ts +106 -0
- package/src/protocol/peer_state.ts +675 -0
- package/src/protocol/qrp.ts +549 -0
- package/src/protocol/query_search.ts +29 -0
- package/src/protocol/share_index.ts +131 -0
- package/src/protocol/share_library.ts +246 -0
- package/src/protocol.ts +36 -0
- package/src/shared.ts +236 -0
- package/src/types.ts +452 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { errMsg } from "./shared";
|
|
2
|
+
import { RESULT_NAME_WIDTH_MAX } from "./const";
|
|
3
|
+
import type { CliNode, ParsedCli } from "./types";
|
|
4
|
+
import { buildMagnetUri } from "./protocol/magnet";
|
|
5
|
+
|
|
6
|
+
const SIZE_FORMAT = new Intl.NumberFormat("en-US", {
|
|
7
|
+
maximumFractionDigits: 1,
|
|
8
|
+
});
|
|
9
|
+
const SIZE_UNITS = ["B", "KB", "MB", "GB", "TB", "PB"];
|
|
10
|
+
const RESULT_COUNT_DISPLAY_MAX = 999;
|
|
11
|
+
const PEER_TABLE_WIDTH_MAX = 80;
|
|
12
|
+
|
|
13
|
+
type ResultInfo = ReturnType<CliNode["getResults"]>[number];
|
|
14
|
+
|
|
15
|
+
function formatSize(bytes: number): string {
|
|
16
|
+
const safeBytes =
|
|
17
|
+
Number.isFinite(bytes) && bytes > 0 ? Math.floor(bytes) : 0;
|
|
18
|
+
let value = safeBytes;
|
|
19
|
+
let unitIndex = 0;
|
|
20
|
+
while (value >= 1024 && unitIndex < SIZE_UNITS.length - 1) {
|
|
21
|
+
value /= 1024;
|
|
22
|
+
unitIndex++;
|
|
23
|
+
}
|
|
24
|
+
return `${SIZE_FORMAT.format(value)}${SIZE_UNITS[unitIndex]}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function displayResultCount(count: number): number {
|
|
28
|
+
const safeCount =
|
|
29
|
+
Number.isFinite(count) && count > 0 ? Math.floor(count) : 0;
|
|
30
|
+
return Math.min(RESULT_COUNT_DISPLAY_MAX, safeCount);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function sanitizeTableCell(
|
|
34
|
+
value: string | undefined,
|
|
35
|
+
fallback = "",
|
|
36
|
+
): string {
|
|
37
|
+
const safe = (value || "").replace(/[\r\n\t]/g, " ").trim();
|
|
38
|
+
return safe || fallback;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function fitTableCell(value: string, width: number): string {
|
|
42
|
+
if (value.length <= width) return value.padEnd(width, " ");
|
|
43
|
+
if (width <= 2) return value.slice(0, width);
|
|
44
|
+
const kept = width - 2;
|
|
45
|
+
const head = Math.floor(kept / 2);
|
|
46
|
+
const tail = kept - head;
|
|
47
|
+
return `${value.slice(0, head)}..${value.slice(-tail)}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function peerFlags(peer: ReturnType<CliNode["getPeers"]>[number]): string {
|
|
51
|
+
return `${peer.outbound ? "O" : "I"}${peer.compression ? "Z" : "-"}${peer.tls ? "L" : "-"}${peer.role === "ultrapeer" ? "U" : "-"}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function valueOrDash(value: string | number | undefined): string {
|
|
55
|
+
return value == null || value === "" ? "-" : String(value);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function boolOrDash(value: boolean | undefined): string {
|
|
59
|
+
return value == null ? "-" : String(value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function speedOrDash(speedKBps: number | undefined): string {
|
|
63
|
+
return speedKBps == null ? "-" : `${speedKBps}KB/s`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function appendValueList(
|
|
67
|
+
lines: string[],
|
|
68
|
+
label: string,
|
|
69
|
+
values: string[],
|
|
70
|
+
): void {
|
|
71
|
+
if (!values.length) {
|
|
72
|
+
lines.push(`${label}: -`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
lines.push(`${label}:`);
|
|
76
|
+
for (const value of values) lines.push(` ${value}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function otherUrns(result: ResultInfo): string[] {
|
|
80
|
+
return (result.urns || []).filter((urn) => urn !== result.sha1Urn);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function formatResultInfoLines(result: ResultInfo): string[] {
|
|
84
|
+
const lines = [
|
|
85
|
+
`result: #${result.resultNo}`,
|
|
86
|
+
`file: ${JSON.stringify(result.fileName)}`,
|
|
87
|
+
`size: ${formatSize(result.fileSize)} (${result.fileSize}B)`,
|
|
88
|
+
`remote: ${result.remoteHost}:${result.remotePort}`,
|
|
89
|
+
`speed: ${speedOrDash(result.speedKBps)}`,
|
|
90
|
+
`file index: ${valueOrDash(result.fileIndex)}`,
|
|
91
|
+
`servent id: ${result.serventIdHex}`,
|
|
92
|
+
`query id: ${valueOrDash(result.queryIdHex)}`,
|
|
93
|
+
`query hops: ${valueOrDash(result.queryHops)}`,
|
|
94
|
+
`via peer: ${valueOrDash(result.viaPeerKey)}`,
|
|
95
|
+
`sha1 urn: ${valueOrDash(result.sha1Urn)}`,
|
|
96
|
+
];
|
|
97
|
+
appendValueList(lines, "other urns", otherUrns(result));
|
|
98
|
+
appendValueList(lines, "metadata", result.metadata || []);
|
|
99
|
+
lines.push(`vendor: ${valueOrDash(result.vendorCode)}`);
|
|
100
|
+
lines.push(`needs push: ${boolOrDash(result.needsPush)}`);
|
|
101
|
+
lines.push(`busy: ${boolOrDash(result.busy)}`);
|
|
102
|
+
return lines;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function resultMagnetUri(result: ResultInfo): string {
|
|
106
|
+
return buildMagnetUri({
|
|
107
|
+
fileName: result.fileName,
|
|
108
|
+
fileSize: result.fileSize,
|
|
109
|
+
urns: result.urns,
|
|
110
|
+
sha1Urn: result.sha1Urn,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function findResult(node: CliNode, resultNo: number): ResultInfo {
|
|
115
|
+
const result = node
|
|
116
|
+
.getResults()
|
|
117
|
+
.find((candidate) => candidate.resultNo === resultNo);
|
|
118
|
+
if (!result) throw new Error(`no such result ${resultNo}`);
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function printStatus(
|
|
123
|
+
node: CliNode,
|
|
124
|
+
log: (msg: string) => void,
|
|
125
|
+
): void {
|
|
126
|
+
const status = node.getStatus();
|
|
127
|
+
log(
|
|
128
|
+
`peers=${status.peers} shares=${status.shares} results=${displayResultCount(status.results)} knownPeers=${status.knownPeers}`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function printPeers(
|
|
133
|
+
node: CliNode,
|
|
134
|
+
log: (msg: string) => void,
|
|
135
|
+
): void {
|
|
136
|
+
const peers = node.getPeers();
|
|
137
|
+
if (!peers.length) {
|
|
138
|
+
log("no peers");
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const rows = peers.map((peer) => ({
|
|
142
|
+
key: sanitizeTableCell(peer.key, "?"),
|
|
143
|
+
flags: peerFlags(peer),
|
|
144
|
+
remoteLabel: sanitizeTableCell(peer.remoteLabel, "?"),
|
|
145
|
+
userAgent: sanitizeTableCell(peer.userAgent, "-"),
|
|
146
|
+
}));
|
|
147
|
+
|
|
148
|
+
const key = Math.max("Id".length, ...rows.map((row) => row.key.length));
|
|
149
|
+
const flags = Math.max(
|
|
150
|
+
"Flags".length,
|
|
151
|
+
...rows.map((row) => row.flags.length),
|
|
152
|
+
);
|
|
153
|
+
const desiredRemoteLabel = Math.max(
|
|
154
|
+
"Peer".length,
|
|
155
|
+
...rows.map((row) => row.remoteLabel.length),
|
|
156
|
+
);
|
|
157
|
+
const desiredUserAgent = Math.max(
|
|
158
|
+
"Agent".length,
|
|
159
|
+
...rows.map((row) => row.userAgent.length),
|
|
160
|
+
);
|
|
161
|
+
const available = Math.max(
|
|
162
|
+
"Peer".length + "Agent".length,
|
|
163
|
+
PEER_TABLE_WIDTH_MAX - key - flags - 6,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
let remoteLabel = Math.min(
|
|
167
|
+
desiredRemoteLabel,
|
|
168
|
+
Math.floor(available / 2),
|
|
169
|
+
);
|
|
170
|
+
let userAgent = Math.min(desiredUserAgent, available - remoteLabel);
|
|
171
|
+
let remaining = available - remoteLabel - userAgent;
|
|
172
|
+
if (remaining > 0 && desiredUserAgent > userAgent) {
|
|
173
|
+
const extra = Math.min(remaining, desiredUserAgent - userAgent);
|
|
174
|
+
userAgent += extra;
|
|
175
|
+
remaining -= extra;
|
|
176
|
+
}
|
|
177
|
+
if (remaining > 0 && desiredRemoteLabel > remoteLabel) {
|
|
178
|
+
remoteLabel += Math.min(remaining, desiredRemoteLabel - remoteLabel);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const widths = {
|
|
182
|
+
key,
|
|
183
|
+
flags,
|
|
184
|
+
remoteLabel,
|
|
185
|
+
userAgent,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const line = (
|
|
189
|
+
key: string,
|
|
190
|
+
flags: string,
|
|
191
|
+
remoteLabel: string,
|
|
192
|
+
userAgent: string,
|
|
193
|
+
) =>
|
|
194
|
+
`${key.padEnd(widths.key, " ")} ${flags.padEnd(widths.flags, " ")} ${fitTableCell(remoteLabel, widths.remoteLabel)} ${fitTableCell(userAgent, widths.userAgent)}`.trimEnd();
|
|
195
|
+
|
|
196
|
+
log(
|
|
197
|
+
[
|
|
198
|
+
line("Id", "Flags", "Peer", "Agent"),
|
|
199
|
+
line(
|
|
200
|
+
"-".repeat(widths.key),
|
|
201
|
+
"-".repeat(widths.flags),
|
|
202
|
+
"-".repeat(widths.remoteLabel),
|
|
203
|
+
"-".repeat(widths.userAgent),
|
|
204
|
+
),
|
|
205
|
+
...rows.map((row) =>
|
|
206
|
+
line(row.key, row.flags, row.remoteLabel, row.userAgent),
|
|
207
|
+
),
|
|
208
|
+
].join("\n"),
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function printShares(
|
|
213
|
+
node: CliNode,
|
|
214
|
+
log: (msg: string) => void,
|
|
215
|
+
): void {
|
|
216
|
+
const shares = node.getShares();
|
|
217
|
+
if (!shares.length) {
|
|
218
|
+
log("no shared files");
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
for (const f of shares)
|
|
222
|
+
log(`#${f.index} ${f.size}B ${JSON.stringify(f.rel)}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function printResults(
|
|
226
|
+
node: CliNode,
|
|
227
|
+
log: (msg: string) => void,
|
|
228
|
+
): void {
|
|
229
|
+
const results = node.getResults();
|
|
230
|
+
if (!results.length) {
|
|
231
|
+
log("no results");
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const rows = [...results]
|
|
235
|
+
.sort(
|
|
236
|
+
(a, b) =>
|
|
237
|
+
a.resultNo - b.resultNo ||
|
|
238
|
+
a.fileName.localeCompare(b.fileName) ||
|
|
239
|
+
a.fileSize - b.fileSize ||
|
|
240
|
+
a.remoteHost.localeCompare(b.remoteHost) ||
|
|
241
|
+
a.remotePort - b.remotePort,
|
|
242
|
+
)
|
|
243
|
+
.map((result) => ({
|
|
244
|
+
resultNo: String(result.resultNo),
|
|
245
|
+
fileName: sanitizeTableCell(result.fileName),
|
|
246
|
+
fileSize: formatSize(result.fileSize),
|
|
247
|
+
remote: sanitizeTableCell(
|
|
248
|
+
`${result.remoteHost}:${result.remotePort}`,
|
|
249
|
+
),
|
|
250
|
+
}));
|
|
251
|
+
|
|
252
|
+
const widths = {
|
|
253
|
+
resultNo: Math.max(
|
|
254
|
+
"No".length,
|
|
255
|
+
...rows.map((row) => row.resultNo.length),
|
|
256
|
+
),
|
|
257
|
+
fileName: RESULT_NAME_WIDTH_MAX,
|
|
258
|
+
fileSize: Math.max(
|
|
259
|
+
"Size".length,
|
|
260
|
+
...rows.map((row) => row.fileSize.length),
|
|
261
|
+
),
|
|
262
|
+
remote: Math.max("IP".length, ...rows.map((row) => row.remote.length)),
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const line = (
|
|
266
|
+
resultNo: string,
|
|
267
|
+
fileName: string,
|
|
268
|
+
fileSize: string,
|
|
269
|
+
remote: string,
|
|
270
|
+
) =>
|
|
271
|
+
`${resultNo.padStart(widths.resultNo, " ")} ${fileName} ${fileSize.padStart(widths.fileSize, " ")} ${remote}`.trimEnd();
|
|
272
|
+
|
|
273
|
+
const fitName = (fileName: string): string => {
|
|
274
|
+
if (fileName.length <= widths.fileName - 2)
|
|
275
|
+
return fileName.padEnd(widths.fileName, " ");
|
|
276
|
+
if (widths.fileName <= 2) return fileName.slice(0, widths.fileName);
|
|
277
|
+
const kept = widths.fileName - 2;
|
|
278
|
+
const head = Math.floor(kept / 2);
|
|
279
|
+
const tail = kept - head;
|
|
280
|
+
return `${fileName.slice(0, head)}..${fileName.slice(-tail)}`;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
log(
|
|
284
|
+
[
|
|
285
|
+
line("No", "File".padEnd(widths.fileName, " "), "Size", "IP"),
|
|
286
|
+
line(
|
|
287
|
+
"-".repeat(widths.resultNo),
|
|
288
|
+
"-".repeat(widths.fileName),
|
|
289
|
+
"-".repeat(widths.fileSize),
|
|
290
|
+
"-".repeat(widths.remote),
|
|
291
|
+
),
|
|
292
|
+
...rows.map((row) =>
|
|
293
|
+
line(
|
|
294
|
+
row.resultNo,
|
|
295
|
+
fitName(row.fileName),
|
|
296
|
+
row.fileSize,
|
|
297
|
+
row.remote,
|
|
298
|
+
),
|
|
299
|
+
),
|
|
300
|
+
].join("\n"),
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function printResultInfo(
|
|
305
|
+
node: CliNode,
|
|
306
|
+
resultNo: number,
|
|
307
|
+
log: (msg: string) => void,
|
|
308
|
+
): void {
|
|
309
|
+
const result = findResult(node, resultNo);
|
|
310
|
+
log(formatResultInfoLines(result).join("\n"));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export function printResultMagnet(
|
|
314
|
+
node: CliNode,
|
|
315
|
+
resultNo: number,
|
|
316
|
+
log: (msg: string) => void,
|
|
317
|
+
): void {
|
|
318
|
+
log(resultMagnetUri(findResult(node, resultNo)));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function parseCli(
|
|
322
|
+
argv: string[],
|
|
323
|
+
defaultConfig: string,
|
|
324
|
+
): ParsedCli {
|
|
325
|
+
let config = defaultConfig;
|
|
326
|
+
const exec: string[] = [];
|
|
327
|
+
let command = "run";
|
|
328
|
+
for (let i = 0; i < argv.length; i++) {
|
|
329
|
+
const a = argv[i];
|
|
330
|
+
if (a === "--config") config = argv[++i] || config;
|
|
331
|
+
else if (a === "--exec") exec.push(argv[++i] || "");
|
|
332
|
+
else if (!a.startsWith("-") && command === "run") command = a;
|
|
333
|
+
}
|
|
334
|
+
return { config, exec, command };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export function runExecCommands(
|
|
338
|
+
execCmds: string[],
|
|
339
|
+
log: (msg: string) => void,
|
|
340
|
+
sleep: (ms: number) => Promise<void>,
|
|
341
|
+
runCommand: (line: string) => Promise<boolean>,
|
|
342
|
+
formatError: (e: unknown) => string,
|
|
343
|
+
): void {
|
|
344
|
+
if (!execCmds.length) return;
|
|
345
|
+
void (async () => {
|
|
346
|
+
await sleep(500);
|
|
347
|
+
for (const cmd of execCmds) {
|
|
348
|
+
log(`exec> ${cmd}`);
|
|
349
|
+
try {
|
|
350
|
+
const keep = await runCommand(cmd);
|
|
351
|
+
if (!keep) return;
|
|
352
|
+
} catch (e) {
|
|
353
|
+
log(`command failed: ${formatError(e)}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
})();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export { errMsg };
|
package/src/const.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
export const HEADER_LEN = 23;
|
|
2
|
+
export const LOCAL_ROUTE = "__local__";
|
|
3
|
+
export const DEFAULT_QRP_TABLE_SIZE = 65536;
|
|
4
|
+
export const DEFAULT_QRP_INFINITY = 7;
|
|
5
|
+
export const DEFAULT_QRP_ENTRY_BITS = 1;
|
|
6
|
+
export const DEFAULT_LISTEN_HOST = "0.0.0.0";
|
|
7
|
+
export const DEFAULT_LISTEN_PORT_MIN = 20000;
|
|
8
|
+
export const DEFAULT_LISTEN_PORT_MAX = 29999;
|
|
9
|
+
export const DEFAULT_USER_AGENT = "GnutellaBun/1.0.0";
|
|
10
|
+
export const DEFAULT_VENDOR_CODE = "GBUN";
|
|
11
|
+
export const DATA_DOWNLOADS_DIRNAME = "downloads";
|
|
12
|
+
export const MAX_ULTRAPEER_CONNECTIONS = 64;
|
|
13
|
+
export const MAX_LEAF_CONNECTIONS = 64;
|
|
14
|
+
export const CONNECT_TIMEOUT_MS = 5000;
|
|
15
|
+
export const PING_INTERVAL_SEC = 60;
|
|
16
|
+
export const RECONNECT_INTERVAL_SEC = 15;
|
|
17
|
+
export const RESCAN_SHARES_SEC = 30;
|
|
18
|
+
export const ROUTE_TTL_SEC = 600;
|
|
19
|
+
export const SEEN_TTL_SEC = 600;
|
|
20
|
+
export const MAX_PAYLOAD_BYTES = 1024 * 1024;
|
|
21
|
+
export const MAX_TTL = 7;
|
|
22
|
+
export const DEFAULT_PING_TTL = 1;
|
|
23
|
+
export const DEFAULT_QUERY_TTL = 4;
|
|
24
|
+
export const ADVERTISED_SPEED_KBPS = 512;
|
|
25
|
+
export const DOWNLOAD_TIMEOUT_MS = 15000;
|
|
26
|
+
export const PUSH_WAIT_MS = 15000;
|
|
27
|
+
export const MAX_RESULTS_PER_QUERY = 50;
|
|
28
|
+
export const MAX_TRACKED_PEERS = 40;
|
|
29
|
+
export const PEER_SEEN_THRESHOLD_SEC = 60;
|
|
30
|
+
export const MAX_PEER_AGE_SEC = 7 * 24 * 60 * 60;
|
|
31
|
+
export const GWEBCACHE_REPORT_DELAY_SEC = 5 * 60;
|
|
32
|
+
export const DEFAULT_QUERY_ROUTING_VERSION = "0.2";
|
|
33
|
+
export const ENABLE_COMPRESSION = true;
|
|
34
|
+
export const ENABLE_TLS = true;
|
|
35
|
+
export const ENABLE_QRP = true;
|
|
36
|
+
export const ENABLE_BYE = true;
|
|
37
|
+
export const ENABLE_PONG_CACHING = true;
|
|
38
|
+
export const ENABLE_GGEP = true;
|
|
39
|
+
export const SERVE_URI_RES = true;
|
|
40
|
+
export const QRP_COMPRESSOR_NONE = 0;
|
|
41
|
+
export const QRP_COMPRESSOR_DEFLATE = 1;
|
|
42
|
+
export const MAX_XTRY = 10;
|
|
43
|
+
export const BYE_DEFAULT_CODE = 200;
|
|
44
|
+
export const BOOTSTRAP_CONNECT_CONCURRENCY = 8;
|
|
45
|
+
export const BOOTSTRAP_CONNECT_TIMEOUT_DIVISOR = 2;
|
|
46
|
+
|
|
47
|
+
export const TYPE = {
|
|
48
|
+
PING: 0x00,
|
|
49
|
+
PONG: 0x01,
|
|
50
|
+
BYE: 0x02,
|
|
51
|
+
ROUTE_TABLE_UPDATE: 0x30,
|
|
52
|
+
PUSH: 0x40,
|
|
53
|
+
QUERY: 0x80,
|
|
54
|
+
QUERY_HIT: 0x81,
|
|
55
|
+
} as const;
|
|
56
|
+
|
|
57
|
+
export const TYPE_NAME: Record<number, string> = {
|
|
58
|
+
[TYPE.PING]: "PING",
|
|
59
|
+
[TYPE.PONG]: "PONG",
|
|
60
|
+
[TYPE.BYE]: "BYE",
|
|
61
|
+
[TYPE.ROUTE_TABLE_UPDATE]: "ROUTE_TABLE_UPDATE",
|
|
62
|
+
[TYPE.PUSH]: "PUSH",
|
|
63
|
+
[TYPE.QUERY]: "QUERY",
|
|
64
|
+
[TYPE.QUERY_HIT]: "QUERY_HIT",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const CANONICAL_HEADER_NAMES: Record<string, string> = {
|
|
68
|
+
"user-agent": "User-Agent",
|
|
69
|
+
"x-ultrapeer": "X-Ultrapeer",
|
|
70
|
+
"x-ultrapeer-needed": "X-Ultrapeer-Needed",
|
|
71
|
+
"x-query-routing": "X-Query-Routing",
|
|
72
|
+
"x-ultrapeer-query-routing": "X-Ultrapeer-Query-Routing",
|
|
73
|
+
"x-dynamic-querying": "X-Dynamic-Querying",
|
|
74
|
+
"x-ext-probes": "X-Ext-Probes",
|
|
75
|
+
"x-degree": "X-Degree",
|
|
76
|
+
"accept-encoding": "Accept-Encoding",
|
|
77
|
+
connection: "Connection",
|
|
78
|
+
"content-encoding": "Content-Encoding",
|
|
79
|
+
upgrade: "Upgrade",
|
|
80
|
+
"listen-ip": "Listen-IP",
|
|
81
|
+
"remote-ip": "Remote-IP",
|
|
82
|
+
"pong-caching": "Pong-Caching",
|
|
83
|
+
ggep: "GGEP",
|
|
84
|
+
"bye-packet": "Bye-Packet",
|
|
85
|
+
"x-try": "X-Try",
|
|
86
|
+
"x-try-ultrapeers": "X-Try-Ultrapeers",
|
|
87
|
+
"x-max-ttl": "X-Max-TTL",
|
|
88
|
+
"private-data": "Private-Data",
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const INTERESTING_HANDSHAKE_HEADERS = [
|
|
92
|
+
"server",
|
|
93
|
+
"user-agent",
|
|
94
|
+
"x-try",
|
|
95
|
+
"x-try-ultrapeers",
|
|
96
|
+
"x-ultrapeer",
|
|
97
|
+
"x-ultrapeer-needed",
|
|
98
|
+
"x-dynamic-querying",
|
|
99
|
+
"x-ext-probes",
|
|
100
|
+
"x-degree",
|
|
101
|
+
"upgrade",
|
|
102
|
+
"connection",
|
|
103
|
+
"listen-ip",
|
|
104
|
+
"remote-ip",
|
|
105
|
+
] as const;
|
|
106
|
+
|
|
107
|
+
export const QRP_HASH_MULTIPLIER = 0x4f1bbcdc;
|
|
108
|
+
export const BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
109
|
+
|
|
110
|
+
export const PROMPT_THROBBER_FRAMES = ["*", "o", ".", " "] as const;
|
|
111
|
+
export const PROMPT_THROBBER_INTERVAL_MS = 120;
|
|
112
|
+
export const CLI_SHUTDOWN_TIMEOUT_MS = 3000;
|
|
113
|
+
|
|
114
|
+
export const CLI_HELP_LINES = [
|
|
115
|
+
"help",
|
|
116
|
+
"monitor",
|
|
117
|
+
"status",
|
|
118
|
+
"peers",
|
|
119
|
+
"blocked",
|
|
120
|
+
"block <ipv4>",
|
|
121
|
+
"unblock <ipv4>",
|
|
122
|
+
"connect <ip:port>",
|
|
123
|
+
"shares",
|
|
124
|
+
"results",
|
|
125
|
+
"clear",
|
|
126
|
+
"ping [ttl]",
|
|
127
|
+
"query <search terms...>",
|
|
128
|
+
"browse <peerKey|ip:port>",
|
|
129
|
+
"info <resultNo>",
|
|
130
|
+
"magnet <resultNo>",
|
|
131
|
+
"download <resultNo> [destPath]",
|
|
132
|
+
"rescan",
|
|
133
|
+
"save",
|
|
134
|
+
"quit",
|
|
135
|
+
"sleep",
|
|
136
|
+
] as const;
|
|
137
|
+
|
|
138
|
+
export const RESULT_NAME_WIDTH_MAX = 48;
|