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,455 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import fsp from "node:fs/promises";
|
|
3
|
+
import net from "node:net";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
import { LOCAL_ROUTE, TYPE } from "../const";
|
|
7
|
+
import { ensureDir, errMsg, fileExists, ts } from "../shared";
|
|
8
|
+
import type { SearchHit } from "../types";
|
|
9
|
+
import {
|
|
10
|
+
buildGetRequest,
|
|
11
|
+
buildUriResRequest,
|
|
12
|
+
encodePush,
|
|
13
|
+
encodeQuery,
|
|
14
|
+
parseHttpDownloadHeader,
|
|
15
|
+
} from "./codec";
|
|
16
|
+
import { findHeaderEnd, socketCanEnd } from "./handshake";
|
|
17
|
+
import { readHttpDownloadSource } from "./http_download_reader";
|
|
18
|
+
import { browsePeer as browsePeerImpl } from "./browse_host";
|
|
19
|
+
import { parseMagnetUri } from "./magnet";
|
|
20
|
+
import type { GnutellaServent } from "./node";
|
|
21
|
+
import type { HttpDownloadState } from "./node_types";
|
|
22
|
+
import { splitQuerySearch } from "./query_search";
|
|
23
|
+
|
|
24
|
+
type OutgoingQueryParts = {
|
|
25
|
+
search: string;
|
|
26
|
+
urns: string[];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function toError(error: unknown): Error {
|
|
30
|
+
return error instanceof Error ? error : new Error(errMsg(error));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function splitOutgoingQuery(search: string): OutgoingQueryParts {
|
|
34
|
+
const magnet = parseMagnetUri(search);
|
|
35
|
+
if (magnet) {
|
|
36
|
+
return {
|
|
37
|
+
search: magnet.search || "",
|
|
38
|
+
urns: magnet.urns,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return splitQuerySearch(search);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function recordDownloadSuccess(
|
|
45
|
+
node: GnutellaServent,
|
|
46
|
+
hit: SearchHit,
|
|
47
|
+
destPath: string,
|
|
48
|
+
mode: "direct" | "push",
|
|
49
|
+
): void {
|
|
50
|
+
node.emitEvent({
|
|
51
|
+
type: "DOWNLOAD_SUCCEEDED",
|
|
52
|
+
at: ts(),
|
|
53
|
+
mode,
|
|
54
|
+
resultNo: hit.resultNo,
|
|
55
|
+
fileName: hit.fileName,
|
|
56
|
+
destPath,
|
|
57
|
+
remoteHost: hit.remoteHost,
|
|
58
|
+
remotePort: hit.remotePort,
|
|
59
|
+
});
|
|
60
|
+
node.downloads.push({
|
|
61
|
+
at: ts(),
|
|
62
|
+
fileName: hit.fileName,
|
|
63
|
+
bytes: hit.fileSize,
|
|
64
|
+
host: hit.remoteHost,
|
|
65
|
+
port: hit.remotePort,
|
|
66
|
+
mode,
|
|
67
|
+
destPath,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function handleIncomingGiv(
|
|
72
|
+
node: GnutellaServent,
|
|
73
|
+
socket: net.Socket,
|
|
74
|
+
giv: string,
|
|
75
|
+
) {
|
|
76
|
+
const text = giv.replace(/\r\n/g, "\n");
|
|
77
|
+
const match = /^GIV\s+\d+:([0-9a-fA-F]{32})\/.+\n\n$/s.exec(text);
|
|
78
|
+
if (!match) {
|
|
79
|
+
socket.destroy();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const serventIdHex = match[1].toLowerCase();
|
|
83
|
+
const pending = node.shiftPendingPush(serventIdHex);
|
|
84
|
+
if (!pending) {
|
|
85
|
+
socket.destroy();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const result = await node.downloadOverSocket(
|
|
90
|
+
socket,
|
|
91
|
+
pending.result.fileIndex,
|
|
92
|
+
pending.result.fileName,
|
|
93
|
+
pending.destPath,
|
|
94
|
+
);
|
|
95
|
+
pending.resolve(result);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
pending.reject(error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function downloadOverSocket(
|
|
102
|
+
node: GnutellaServent,
|
|
103
|
+
socket: net.Socket,
|
|
104
|
+
fileIndex: number,
|
|
105
|
+
fileName: string,
|
|
106
|
+
destPath: string,
|
|
107
|
+
): Promise<unknown> {
|
|
108
|
+
await ensureDir(path.dirname(destPath));
|
|
109
|
+
const existing = (await fileExists(destPath))
|
|
110
|
+
? (await fsp.stat(destPath)).size
|
|
111
|
+
: 0;
|
|
112
|
+
socket.write(
|
|
113
|
+
buildGetRequest(
|
|
114
|
+
fileIndex,
|
|
115
|
+
fileName,
|
|
116
|
+
existing,
|
|
117
|
+
socket.remoteAddress || undefined,
|
|
118
|
+
socket.remotePort || undefined,
|
|
119
|
+
),
|
|
120
|
+
);
|
|
121
|
+
const result = await node.readHttpDownload(
|
|
122
|
+
socket,
|
|
123
|
+
destPath,
|
|
124
|
+
`${socket.remoteAddress || "?"}:${socket.remotePort || "?"}`,
|
|
125
|
+
existing,
|
|
126
|
+
);
|
|
127
|
+
if (socketCanEnd(socket)) socket.end();
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function directDownloadViaRequest(
|
|
132
|
+
node: GnutellaServent,
|
|
133
|
+
host: string,
|
|
134
|
+
port: number,
|
|
135
|
+
request: string,
|
|
136
|
+
destPath: string,
|
|
137
|
+
existing: number,
|
|
138
|
+
): Promise<unknown> {
|
|
139
|
+
const socket = node.createConnection({ host, port });
|
|
140
|
+
socket.setNoDelay(true);
|
|
141
|
+
socket.setTimeout(node.config().downloadTimeoutMs, () =>
|
|
142
|
+
socket.destroy(new Error("download timeout")),
|
|
143
|
+
);
|
|
144
|
+
await new Promise<void>((resolve, reject) => {
|
|
145
|
+
const onError = (error: unknown) => {
|
|
146
|
+
socket.removeListener("connect", onConnect);
|
|
147
|
+
reject(toError(error));
|
|
148
|
+
};
|
|
149
|
+
const onConnect = () => {
|
|
150
|
+
socket.removeListener("error", onError);
|
|
151
|
+
socket.write(request);
|
|
152
|
+
resolve();
|
|
153
|
+
};
|
|
154
|
+
socket.once("error", onError);
|
|
155
|
+
socket.once("connect", onConnect);
|
|
156
|
+
});
|
|
157
|
+
const result = await node.readHttpDownload(
|
|
158
|
+
socket,
|
|
159
|
+
destPath,
|
|
160
|
+
`${host}:${port}`,
|
|
161
|
+
existing,
|
|
162
|
+
);
|
|
163
|
+
if (socketCanEnd(socket)) socket.end();
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export async function directDownload(
|
|
168
|
+
node: GnutellaServent,
|
|
169
|
+
hit: SearchHit,
|
|
170
|
+
destPath: string,
|
|
171
|
+
): Promise<unknown> {
|
|
172
|
+
await ensureDir(path.dirname(destPath));
|
|
173
|
+
const existing = (await fileExists(destPath))
|
|
174
|
+
? (await fsp.stat(destPath)).size
|
|
175
|
+
: 0;
|
|
176
|
+
|
|
177
|
+
if (hit.sha1Urn && node.config().serveUriRes) {
|
|
178
|
+
try {
|
|
179
|
+
return await node.directDownloadViaRequest(
|
|
180
|
+
hit.remoteHost,
|
|
181
|
+
hit.remotePort,
|
|
182
|
+
buildUriResRequest(
|
|
183
|
+
hit.sha1Urn,
|
|
184
|
+
existing,
|
|
185
|
+
hit.remoteHost,
|
|
186
|
+
hit.remotePort,
|
|
187
|
+
),
|
|
188
|
+
destPath,
|
|
189
|
+
existing,
|
|
190
|
+
);
|
|
191
|
+
} catch {
|
|
192
|
+
// fall through to /get/ path
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return await node.directDownloadViaRequest(
|
|
197
|
+
hit.remoteHost,
|
|
198
|
+
hit.remotePort,
|
|
199
|
+
buildGetRequest(
|
|
200
|
+
hit.fileIndex,
|
|
201
|
+
hit.fileName,
|
|
202
|
+
existing,
|
|
203
|
+
hit.remoteHost,
|
|
204
|
+
hit.remotePort,
|
|
205
|
+
),
|
|
206
|
+
destPath,
|
|
207
|
+
existing,
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function initializeHttpDownloadState(
|
|
212
|
+
_node: GnutellaServent,
|
|
213
|
+
state: HttpDownloadState,
|
|
214
|
+
destPath: string,
|
|
215
|
+
requestedStart: number,
|
|
216
|
+
onWriteError: (error: Error) => void,
|
|
217
|
+
): void {
|
|
218
|
+
const raw = state.buf.toString("latin1");
|
|
219
|
+
const cut = findHeaderEnd(raw);
|
|
220
|
+
if (cut === -1) return;
|
|
221
|
+
|
|
222
|
+
state.headerDone = true;
|
|
223
|
+
const parsed = parseHttpDownloadHeader(
|
|
224
|
+
raw.slice(0, cut),
|
|
225
|
+
requestedStart,
|
|
226
|
+
);
|
|
227
|
+
state.remaining = parsed.remaining;
|
|
228
|
+
state.finalStart = parsed.finalStart;
|
|
229
|
+
state.ws = fs.createWriteStream(destPath, {
|
|
230
|
+
flags: state.finalStart > 0 ? "r+" : "w",
|
|
231
|
+
start: state.finalStart,
|
|
232
|
+
});
|
|
233
|
+
state.ws.on("error", onWriteError);
|
|
234
|
+
state.buf = state.buf.subarray(cut);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function writeHttpDownloadBody(
|
|
238
|
+
_node: GnutellaServent,
|
|
239
|
+
state: HttpDownloadState,
|
|
240
|
+
): void {
|
|
241
|
+
if (!state.ws) return;
|
|
242
|
+
const take = Math.min(state.remaining, state.buf.length);
|
|
243
|
+
if (take <= 0) return;
|
|
244
|
+
const chunkOut = state.buf.subarray(0, take);
|
|
245
|
+
state.ws.write(chunkOut);
|
|
246
|
+
state.bodyBytes += chunkOut.length;
|
|
247
|
+
state.remaining -= take;
|
|
248
|
+
state.buf = state.buf.subarray(take);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function consumeHttpDownloadChunk(
|
|
252
|
+
node: GnutellaServent,
|
|
253
|
+
state: HttpDownloadState,
|
|
254
|
+
destPath: string,
|
|
255
|
+
requestedStart: number,
|
|
256
|
+
onWriteError: (error: Error) => void,
|
|
257
|
+
chunk: Buffer,
|
|
258
|
+
): void {
|
|
259
|
+
state.buf = Buffer.concat([state.buf, chunk]);
|
|
260
|
+
if (!state.headerDone) {
|
|
261
|
+
node.initializeHttpDownloadState(
|
|
262
|
+
state,
|
|
263
|
+
destPath,
|
|
264
|
+
requestedStart,
|
|
265
|
+
onWriteError,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
node.writeHttpDownloadBody(state);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export async function readHttpDownload(
|
|
272
|
+
node: GnutellaServent,
|
|
273
|
+
socket: net.Socket,
|
|
274
|
+
destPath: string,
|
|
275
|
+
label: string,
|
|
276
|
+
requestedStart: number,
|
|
277
|
+
): Promise<unknown> {
|
|
278
|
+
return await readHttpDownloadSource({
|
|
279
|
+
attach: ({ onChunk, onEnd, onError }) => {
|
|
280
|
+
const onData = (chunk: string | Buffer) =>
|
|
281
|
+
onChunk(Buffer.from(chunk));
|
|
282
|
+
socket.on("error", onError);
|
|
283
|
+
socket.on("data", onData);
|
|
284
|
+
socket.on("end", onEnd);
|
|
285
|
+
return () => {
|
|
286
|
+
socket.off("error", onError);
|
|
287
|
+
socket.off("data", onData);
|
|
288
|
+
socket.off("end", onEnd);
|
|
289
|
+
};
|
|
290
|
+
},
|
|
291
|
+
consumeChunk: (state, targetPath, start, onWriteError, chunk) =>
|
|
292
|
+
node.consumeHttpDownloadChunk(
|
|
293
|
+
state,
|
|
294
|
+
targetPath,
|
|
295
|
+
start,
|
|
296
|
+
onWriteError,
|
|
297
|
+
chunk,
|
|
298
|
+
),
|
|
299
|
+
destPath,
|
|
300
|
+
destroyOnFailure: () => socket.destroy(),
|
|
301
|
+
incompleteMessage: "connection closed before full body received",
|
|
302
|
+
label,
|
|
303
|
+
requestedStart,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export async function sendPush(
|
|
308
|
+
node: GnutellaServent,
|
|
309
|
+
hit: SearchHit,
|
|
310
|
+
destPath: string,
|
|
311
|
+
): Promise<unknown> {
|
|
312
|
+
const route = node.pushRoutes.get(hit.serventIdHex);
|
|
313
|
+
if (!route) throw new Error("no push route for servent");
|
|
314
|
+
const peer = node.peers.get(route.peerKey);
|
|
315
|
+
if (!peer) throw new Error("push route peer not connected");
|
|
316
|
+
|
|
317
|
+
const payload = encodePush(
|
|
318
|
+
node.rawHex16(hit.serventIdHex),
|
|
319
|
+
hit.fileIndex,
|
|
320
|
+
node.currentAdvertisedHost(),
|
|
321
|
+
node.currentAdvertisedPort(),
|
|
322
|
+
);
|
|
323
|
+
const descriptorId = node.randomId16();
|
|
324
|
+
const pending = new Promise((resolve, reject) => {
|
|
325
|
+
node.enqueuePendingPush({
|
|
326
|
+
serventIdHex: hit.serventIdHex,
|
|
327
|
+
result: hit,
|
|
328
|
+
destPath,
|
|
329
|
+
createdAt: node.now(),
|
|
330
|
+
resolve,
|
|
331
|
+
reject,
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
node.sendToPeer(
|
|
335
|
+
peer,
|
|
336
|
+
TYPE.PUSH,
|
|
337
|
+
descriptorId,
|
|
338
|
+
Math.max(1, hit.queryHops + 2),
|
|
339
|
+
0,
|
|
340
|
+
payload,
|
|
341
|
+
);
|
|
342
|
+
return await pending;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export async function downloadResult(
|
|
346
|
+
node: GnutellaServent,
|
|
347
|
+
resultNo: number,
|
|
348
|
+
destOverride?: string,
|
|
349
|
+
): Promise<void> {
|
|
350
|
+
const hit = node.lastResults.find(
|
|
351
|
+
(candidate) => candidate.resultNo === resultNo,
|
|
352
|
+
);
|
|
353
|
+
if (!hit) throw new Error(`no such result ${resultNo}`);
|
|
354
|
+
|
|
355
|
+
const destPath = destOverride
|
|
356
|
+
? path.resolve(destOverride)
|
|
357
|
+
: await node.reserveAutoDownloadPath(hit.fileName);
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
try {
|
|
361
|
+
await node.directDownload(hit, destPath);
|
|
362
|
+
recordDownloadSuccess(node, hit, destPath, "direct");
|
|
363
|
+
return;
|
|
364
|
+
} catch (error) {
|
|
365
|
+
node.emitEvent({
|
|
366
|
+
type: "DOWNLOAD_DIRECT_FAILED",
|
|
367
|
+
at: ts(),
|
|
368
|
+
resultNo: hit.resultNo,
|
|
369
|
+
fileName: hit.fileName,
|
|
370
|
+
destPath,
|
|
371
|
+
remoteHost: hit.remoteHost,
|
|
372
|
+
remotePort: hit.remotePort,
|
|
373
|
+
message: errMsg(error),
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
await node.sendPush(hit, destPath);
|
|
378
|
+
recordDownloadSuccess(node, hit, destPath, "push");
|
|
379
|
+
} finally {
|
|
380
|
+
if (!destOverride) node.activeAutoDownloadPaths.delete(destPath);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export async function browsePeer(
|
|
385
|
+
node: GnutellaServent,
|
|
386
|
+
peerKey: string,
|
|
387
|
+
): Promise<number> {
|
|
388
|
+
return await browsePeerImpl(node, peerKey);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function sendPing(node: GnutellaServent, ttl: number): void {
|
|
392
|
+
if (!node.peers.size) return;
|
|
393
|
+
const descriptorId = node.randomId16();
|
|
394
|
+
const hex = descriptorId.toString("hex");
|
|
395
|
+
node.markSeen(TYPE.PING, hex);
|
|
396
|
+
node.pingRoutes.set(hex, LOCAL_ROUTE);
|
|
397
|
+
const pingTtl = Math.max(0, Math.min(ttl, node.config().maxTtl));
|
|
398
|
+
for (const peer of node.peers.values()) {
|
|
399
|
+
if (node.nodeMode() === "ultrapeer" && node.isLeafPeer(peer)) continue;
|
|
400
|
+
node.sendToPeer(
|
|
401
|
+
peer,
|
|
402
|
+
TYPE.PING,
|
|
403
|
+
descriptorId,
|
|
404
|
+
pingTtl,
|
|
405
|
+
0,
|
|
406
|
+
Buffer.alloc(0),
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
node.emitEvent({
|
|
410
|
+
type: "PING_SENT",
|
|
411
|
+
at: ts(),
|
|
412
|
+
descriptorIdHex: hex,
|
|
413
|
+
ttl,
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function sendQuery(
|
|
418
|
+
node: GnutellaServent,
|
|
419
|
+
search: string,
|
|
420
|
+
ttl = node.config().defaultQueryTtl,
|
|
421
|
+
): void {
|
|
422
|
+
if (!node.peers.size) {
|
|
423
|
+
node.emitEvent({
|
|
424
|
+
type: "QUERY_SKIPPED",
|
|
425
|
+
at: ts(),
|
|
426
|
+
reason: "NO_PEERS_CONNECTED",
|
|
427
|
+
});
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const descriptorId = node.randomId16();
|
|
432
|
+
const hex = descriptorId.toString("hex");
|
|
433
|
+
node.markSeen(TYPE.QUERY, hex);
|
|
434
|
+
node.queryRoutes.set(hex, LOCAL_ROUTE);
|
|
435
|
+
const query = splitOutgoingQuery(search);
|
|
436
|
+
const payload = encodeQuery(query.search, {
|
|
437
|
+
ggepHAllowed: !!node.config().enableGgep,
|
|
438
|
+
maxHits: Math.min(0x1ff, node.config().maxResultsPerQuery),
|
|
439
|
+
urns: query.urns,
|
|
440
|
+
});
|
|
441
|
+
node.broadcastQuery(
|
|
442
|
+
descriptorId,
|
|
443
|
+
Math.min(node.config().maxTtl, ttl),
|
|
444
|
+
0,
|
|
445
|
+
payload,
|
|
446
|
+
search,
|
|
447
|
+
);
|
|
448
|
+
node.emitEvent({
|
|
449
|
+
type: "QUERY_SENT",
|
|
450
|
+
at: ts(),
|
|
451
|
+
descriptorIdHex: hex,
|
|
452
|
+
ttl,
|
|
453
|
+
search,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import net from "node:net";
|
|
3
|
+
import zlib from "node:zlib";
|
|
4
|
+
|
|
5
|
+
import type { PeerCapabilities, PeerRole, RemoteQrpState } from "../types";
|
|
6
|
+
import type { GgepItem } from "./ggep";
|
|
7
|
+
|
|
8
|
+
export type ProbeCtx = {
|
|
9
|
+
socket: net.Socket;
|
|
10
|
+
buf: Buffer;
|
|
11
|
+
receivedBytes: number;
|
|
12
|
+
startedAtMs: number;
|
|
13
|
+
mode: "undecided" | "await-final-0.6" | "done";
|
|
14
|
+
requestHeaders?: Record<string, string>;
|
|
15
|
+
serverHeaders?: Record<string, string>;
|
|
16
|
+
onData?: (chunk: string | Buffer) => void;
|
|
17
|
+
onEnd?: () => void;
|
|
18
|
+
onClose?: (hadError: boolean) => void;
|
|
19
|
+
onError?: (error: unknown) => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type HttpSession = {
|
|
23
|
+
socket: net.Socket;
|
|
24
|
+
buf: Buffer;
|
|
25
|
+
busy: boolean;
|
|
26
|
+
closed: boolean;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type HttpSessionRequest = {
|
|
30
|
+
head: string;
|
|
31
|
+
body: Buffer;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type ExistingGetRequest = {
|
|
35
|
+
method: string;
|
|
36
|
+
responseVersion: string;
|
|
37
|
+
headers: Record<string, string>;
|
|
38
|
+
keepAlive: boolean;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type HttpDownloadState = {
|
|
42
|
+
buf: Buffer;
|
|
43
|
+
headerDone: boolean;
|
|
44
|
+
remaining: number;
|
|
45
|
+
ws: fs.WriteStream | null;
|
|
46
|
+
finalStart: number;
|
|
47
|
+
bodyBytes: number;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type Peer = {
|
|
51
|
+
key: string;
|
|
52
|
+
socket: net.Socket;
|
|
53
|
+
buf: Buffer;
|
|
54
|
+
outbound: boolean;
|
|
55
|
+
dialTarget?: string;
|
|
56
|
+
remoteLabel: string;
|
|
57
|
+
role: PeerRole;
|
|
58
|
+
capabilities: PeerCapabilities;
|
|
59
|
+
inflater?: zlib.Inflate;
|
|
60
|
+
deflater?: zlib.Deflate;
|
|
61
|
+
remoteQrp: RemoteQrpState;
|
|
62
|
+
lastPingAt: number;
|
|
63
|
+
connectedAt: number;
|
|
64
|
+
closingAfterBye?: boolean;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type DescriptorHeader = {
|
|
68
|
+
descriptorId: Buffer;
|
|
69
|
+
descriptorIdHex: string;
|
|
70
|
+
payloadType: number;
|
|
71
|
+
ttl: number;
|
|
72
|
+
hops: number;
|
|
73
|
+
payloadLength: number;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type QueryHitResult = {
|
|
77
|
+
fileIndex: number;
|
|
78
|
+
fileSize: number;
|
|
79
|
+
fileName: string;
|
|
80
|
+
urns: string[];
|
|
81
|
+
metadata: string[];
|
|
82
|
+
rawExtension: Buffer;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export type QueryHitEncodeOptions = {
|
|
86
|
+
vendorCode?: string;
|
|
87
|
+
push?: boolean;
|
|
88
|
+
busy?: boolean;
|
|
89
|
+
haveUploaded?: boolean;
|
|
90
|
+
measuredSpeed?: boolean;
|
|
91
|
+
ggepHashes?: boolean;
|
|
92
|
+
browseHost?: boolean;
|
|
93
|
+
privateGgepItems?: GgepItem[];
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type QueryEncodeOptions = {
|
|
97
|
+
requesterFirewalled?: boolean;
|
|
98
|
+
wantsXml?: boolean;
|
|
99
|
+
leafGuidedDynamic?: boolean;
|
|
100
|
+
ggepHAllowed?: boolean;
|
|
101
|
+
outOfBand?: boolean;
|
|
102
|
+
maxHits?: number;
|
|
103
|
+
urns?: string[];
|
|
104
|
+
xmlBlocks?: string[];
|
|
105
|
+
ggepItems?: GgepItem[];
|
|
106
|
+
};
|