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.
Files changed (45) hide show
  1. package/CLI.md +189 -0
  2. package/DEVELOPER.md +193 -0
  3. package/LICENSE +674 -0
  4. package/QUICKSTART.md +133 -0
  5. package/README.md +74 -0
  6. package/bin/gnutella.ts +15 -0
  7. package/gnutella.json.example +18 -0
  8. package/package.json +72 -0
  9. package/src/cli.ts +692 -0
  10. package/src/cli_shared.ts +359 -0
  11. package/src/const.ts +138 -0
  12. package/src/gwebcache/bootstrap.ts +491 -0
  13. package/src/gwebcache/response.ts +391 -0
  14. package/src/gwebcache/shared.ts +116 -0
  15. package/src/gwebcache/types.ts +187 -0
  16. package/src/gwebcache_client.ts +13 -0
  17. package/src/protocol/browse_host.ts +552 -0
  18. package/src/protocol/client_blocking.ts +29 -0
  19. package/src/protocol/codec.ts +715 -0
  20. package/src/protocol/content_urn.ts +170 -0
  21. package/src/protocol/core_utils.ts +43 -0
  22. package/src/protocol/file_server.ts +245 -0
  23. package/src/protocol/ggep.ts +168 -0
  24. package/src/protocol/handshake.ts +199 -0
  25. package/src/protocol/http_download_reader.ts +112 -0
  26. package/src/protocol/magnet.ts +176 -0
  27. package/src/protocol/node.ts +416 -0
  28. package/src/protocol/node_handshake.ts +992 -0
  29. package/src/protocol/node_lifecycle.ts +210 -0
  30. package/src/protocol/node_protocol_runtime.ts +949 -0
  31. package/src/protocol/node_qrp_runtime.ts +97 -0
  32. package/src/protocol/node_query_routing.ts +208 -0
  33. package/src/protocol/node_state.ts +745 -0
  34. package/src/protocol/node_tls.ts +257 -0
  35. package/src/protocol/node_topology.ts +141 -0
  36. package/src/protocol/node_transfer.ts +455 -0
  37. package/src/protocol/node_types.ts +106 -0
  38. package/src/protocol/peer_state.ts +675 -0
  39. package/src/protocol/qrp.ts +549 -0
  40. package/src/protocol/query_search.ts +29 -0
  41. package/src/protocol/share_index.ts +131 -0
  42. package/src/protocol/share_library.ts +246 -0
  43. package/src/protocol.ts +36 -0
  44. package/src/shared.ts +236 -0
  45. package/src/types.ts +452 -0
@@ -0,0 +1,257 @@
1
+ import net from "node:net";
2
+ import tls from "node:tls";
3
+
4
+ import { ENABLE_TLS } from "../const";
5
+ import { errMsg } from "../shared";
6
+ import type { GnutellaServent } from "./node";
7
+
8
+ const TLS_UPGRADE_TOKEN = "TLS/1.0";
9
+ const TLS_TIMEOUT_MS = 5000;
10
+
11
+ // Opportunistic peer TLS does not authenticate remote identity, so a
12
+ // bundled self-signed certificate is sufficient for server-side handshakes.
13
+ const TLS_KEY_PEM = `-----BEGIN PRIVATE KEY-----
14
+ MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQD4hBqeBHY43TlX
15
+ zVeqDytDe9/8aJGk4DL9RXWdU6fExvMySt5l30OVBH/WqrAtSsxTzYF6Ara8erXG
16
+ vPQq0HbtbnAGzB7VOcadYCYFJFiNFQoR50U4vq0MHE/4QEHsRGZFtB1HRIrHEqJp
17
+ m9TYbaL6/Nw4atzcvEWcjok5rpvy2NHyI1QueixOb7KeukSrgR6WUNCldi0YPuXP
18
+ kePe48nifnzlbw2MqYP5u+TdThpyu5KGLwsvHOUUhr7HrEGpcl8FHpAhsWE2HzVE
19
+ 5LGLLK3goASjJOYokl56yzLPNC4PgsmdgpykiMHqDXEwAfaM7dRb2BhAzx4jpxbv
20
+ jwbbJJtPAgMBAAECggEAAOBWD8JTM4UKhR9L71Ei/o2MT3ZhSxKjbYw/0qjG/K0I
21
+ 3+hInEmnkMB3aqS1m7Xib0XpgSSKgmJjD/EIcG/lOCL3zIIzz38FK5fIbqxbAfDM
22
+ kwyzHBXt7apTBC4rFZxwhCzvMpjLU+u884mQL1K3d8vPjhCFJNJ/8WVa+0ulJyp+
23
+ otlQVzKWH5T/jjUlTxnaImsIdI8grqaBSC5Fd5Dnay0qUNYx3t5UFlkAZzWc/Uua
24
+ EuDvid1fsFGHbkXrh9jFLLKd+7MnNjCeQf56xzbN8mvFntnnqCJm3AywMGEQwwHB
25
+ 84e/2jEeJBQsLuft1qAk2Wr3gKBkF8vcooDoArFpKQKBgQD/GIGsJGcfT5JnMobP
26
+ 6Dv7UEIasv5MUZ6PSvB4bLk/AItnAms27No28w32S5X5f19Wm8+ff7AWdEjbs/XP
27
+ rgnLV6QL0e8pQCNBT5kfXidydrZrH8rbSV0h3Ius+UQSEMNnjBaJ8mgPUsH//jBh
28
+ dM077jSgoP3KHjQk2UHp9+p0yQKBgQD5ZaBjdQemDeI6D8Jzowjrr4IT0Y9wrT0u
29
+ O8noRMDxJM1BfFxOB/yrM+mMVEapOFuR7qTcHuJFUUSktROlhwvDHFSXEPs9/0Vy
30
+ d8FFgbd0OF4+Hdg5yk0F7LjgXh982Dc2H363vlPiOZ8BklTX73bnlw9WvgBDxqri
31
+ H7JW92wTVwKBgQD0W0tL1IsbySNay2GsIq/iat0HqlJCVSTn6kczdCJ3IVRn1j9R
32
+ m8zkOisztO/y0XpIAnT+Olg5CicInfhnejVTnZ483FqWTyP2WgM5sv1ifij7sLan
33
+ HD2kRBlgFl6IV5p2xBCLD7NyijnfuGQr1rEKKYIsJEs3o3sbmSm0r5DdUQKBgQCm
34
+ F5ZvZjtHzatCO8imtod0XxhkFoZO5jD+n3biJxfQAVBpMmdO2Gbfpdz+RgohHJVv
35
+ ZN2Kc08CFxN+FdIVxRCCSlXTnc2VBnK7vyGKJs+EqR2qhLnCEwak0Xh2hHi37k8m
36
+ zmbX+/tliDZrF4dFoAcySRpADJ2khaS8n5tn67OgVQKBgQDyHZ4zruZo5b+GQC+5
37
+ IN/FrtviKu0+W2dA+PGwxMcUF+373IDq7Kp+8A8j+QtG0uPYMZiFrWb10Aqp5yjZ
38
+ PH6GWdABuZ61cWEdbFCgG6/NCOWB84iPSHkttZPi0r+EbspHeIlLixUIFSVKUeLf
39
+ jlzk/A25vxdNIA4F+dVdBBy/Pg==
40
+ -----END PRIVATE KEY-----
41
+ `;
42
+
43
+ const TLS_CERT_PEM = `-----BEGIN CERTIFICATE-----
44
+ MIIDDzCCAfegAwIBAgIURECewiM0o9kR9MKlXP0py8dw0XYwDQYJKoZIhvcNAQEL
45
+ BQAwFzEVMBMGA1UEAwwMZ251dGVsbGEtYnVuMB4XDTI2MDMyODA2NDMwMVoXDTM2
46
+ MDMyNTA2NDMwMVowFzEVMBMGA1UEAwwMZ251dGVsbGEtYnVuMIIBIjANBgkqhkiG
47
+ 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+IQangR2ON05V81Xqg8rQ3vf/GiRpOAy/UV1
48
+ nVOnxMbzMkreZd9DlQR/1qqwLUrMU82BegK2vHq1xrz0KtB27W5wBswe1TnGnWAm
49
+ BSRYjRUKEedFOL6tDBxP+EBB7ERmRbQdR0SKxxKiaZvU2G2i+vzcOGrc3LxFnI6J
50
+ Oa6b8tjR8iNULnosTm+ynrpEq4EellDQpXYtGD7lz5Hj3uPJ4n585W8NjKmD+bvk
51
+ 3U4acruShi8LLxzlFIa+x6xBqXJfBR6QIbFhNh81ROSxiyyt4KAEoyTmKJJeessy
52
+ zzQuD4LJnYKcpIjB6g1xMAH2jO3UW9gYQM8eI6cW748G2ySbTwIDAQABo1MwUTAd
53
+ BgNVHQ4EFgQUmmjFyMO+Yec6uc5oDZ32eQZpTVcwHwYDVR0jBBgwFoAUmmjFyMO+
54
+ Yec6uc5oDZ32eQZpTVcwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
55
+ AQEA1GGX8oZEKsOrC5vo2wtkEuA6A4jnc6+l6E2wHNRhBpK7Pi77i/IijMoAvW/d
56
+ 6C+6vLNogZEsavsi9zfwxjCmu6eJO4QW7/Jpoev8ZPQQE0cDZUQigF5B6Qy8HQ1L
57
+ PT836ReE3X2e7TPVwrhtDC8hNSRar+Wh0VZXHvwhEkyfuea75QgYE8jyqpAviYZT
58
+ jH7OHoVlXZyuRBQOh03PJt+ZZyJLG172C39dmeNXC/N5a07PvDH0TRVlvdbnQVfE
59
+ J7/6IhSn3XbO000zgwcdGjZbPVym/xNiidJCPxrqF7ZaGWfWLmNXLnuGrOm0FusM
60
+ x+z+mIKYbuX4pACxG4t1ycRfOQ==
61
+ -----END CERTIFICATE-----
62
+ `;
63
+
64
+ let secureContext: tls.SecureContext | undefined;
65
+ let tlsContextError: Error | undefined;
66
+
67
+ function hasHeaderToken(
68
+ value: string | undefined,
69
+ token: string,
70
+ ): boolean {
71
+ if (!value) return false;
72
+ return value
73
+ .split(",")
74
+ .map((part) => part.trim().toLowerCase())
75
+ .includes(token.toLowerCase());
76
+ }
77
+
78
+ function tlsContext(): tls.SecureContext {
79
+ if (secureContext) return secureContext;
80
+ if (tlsContextError) throw tlsContextError;
81
+ try {
82
+ secureContext = tls.createSecureContext({
83
+ key: TLS_KEY_PEM,
84
+ cert: TLS_CERT_PEM,
85
+ minVersion: "TLSv1.2",
86
+ });
87
+ return secureContext;
88
+ } catch (error) {
89
+ tlsContextError =
90
+ error instanceof Error ? error : new Error(errMsg(error));
91
+ throw tlsContextError;
92
+ }
93
+ }
94
+
95
+ function secureEventNames(mode: "client" | "server"): string[] {
96
+ return mode === "client"
97
+ ? ["secureConnect", "secure"]
98
+ : ["secure", "secureConnect"];
99
+ }
100
+
101
+ function maybeStartTlsSocket(
102
+ socket: tls.TLSSocket,
103
+ mode: "client" | "server",
104
+ ): void {
105
+ // `tls.connect()` already starts the client handshake in Bun. Calling
106
+ // the internal starter again throws and aborts the upgrade.
107
+ if (mode !== "server") return;
108
+ const starter = socket as tls.TLSSocket & { _start?: () => void };
109
+ if (typeof starter._start === "function") starter._start();
110
+ }
111
+
112
+ function waitForSecureTlsSocket(
113
+ node: GnutellaServent,
114
+ socket: tls.TLSSocket,
115
+ mode: "client" | "server",
116
+ timeoutMs: number,
117
+ start?: () => void,
118
+ ): Promise<tls.TLSSocket> {
119
+ return new Promise((resolve, reject) => {
120
+ let settled = false;
121
+ const cleanup = () => {
122
+ node.collaborators.scheduler.clearTimeout(timer);
123
+ for (const event of secureEventNames(mode)) {
124
+ socket.off(event, onSecure);
125
+ }
126
+ socket.off("error", onError);
127
+ socket.off("close", onClose);
128
+ };
129
+ const finish = (value: tls.TLSSocket) => {
130
+ if (settled) return;
131
+ settled = true;
132
+ cleanup();
133
+ resolve(value);
134
+ };
135
+ const fail = (error: unknown) => {
136
+ if (settled) return;
137
+ settled = true;
138
+ cleanup();
139
+ reject(error instanceof Error ? error : new Error(errMsg(error)));
140
+ };
141
+ const onSecure = () => finish(socket);
142
+ const onError = (error: unknown) => fail(error);
143
+ const onClose = () => fail(new Error("TLS socket closed"));
144
+ const timer = node.collaborators.scheduler.setTimeout(() => {
145
+ fail(new Error("TLS handshake timeout"));
146
+ }, timeoutMs);
147
+
148
+ for (const event of secureEventNames(mode)) {
149
+ socket.once(event, onSecure);
150
+ }
151
+ socket.once("error", onError);
152
+ socket.once("close", onClose);
153
+ try {
154
+ start?.();
155
+ } catch (error) {
156
+ fail(error);
157
+ }
158
+ });
159
+ }
160
+
161
+ export function tlsEnabled(node: GnutellaServent): boolean {
162
+ if (!ENABLE_TLS) return false;
163
+ if (!node.config().enableTls) return false;
164
+ try {
165
+ tlsContext();
166
+ return true;
167
+ } catch {
168
+ return false;
169
+ }
170
+ }
171
+
172
+ export function socketUsesTls(
173
+ _node: GnutellaServent,
174
+ socket: net.Socket,
175
+ ): boolean {
176
+ return (
177
+ socket instanceof tls.TLSSocket ||
178
+ !!(socket as net.Socket & { encrypted?: boolean }).encrypted
179
+ );
180
+ }
181
+
182
+ export function canUpgradeSocketToTls(
183
+ _node: GnutellaServent,
184
+ socket: net.Socket,
185
+ ): boolean {
186
+ return socket instanceof net.Socket;
187
+ }
188
+
189
+ export function peerRequestedTlsUpgrade(
190
+ _node: GnutellaServent,
191
+ headers: Record<string, string>,
192
+ ): boolean {
193
+ return hasHeaderToken(headers["upgrade"], TLS_UPGRADE_TOKEN);
194
+ }
195
+
196
+ export function peerAcceptedTlsUpgrade(
197
+ _node: GnutellaServent,
198
+ headers: Record<string, string>,
199
+ ): boolean {
200
+ return (
201
+ hasHeaderToken(headers["upgrade"], TLS_UPGRADE_TOKEN) &&
202
+ hasHeaderToken(headers["connection"], "upgrade")
203
+ );
204
+ }
205
+
206
+ export function clientAcceptedTlsUpgrade(
207
+ _node: GnutellaServent,
208
+ headers: Record<string, string>,
209
+ ): boolean {
210
+ return hasHeaderToken(headers["connection"], "upgrade");
211
+ }
212
+
213
+ export function tlsUpgradeToken(_node: GnutellaServent): string {
214
+ return TLS_UPGRADE_TOKEN;
215
+ }
216
+
217
+ export async function upgradeSocketToTls(
218
+ node: GnutellaServent,
219
+ socket: net.Socket,
220
+ mode: "client" | "server",
221
+ initialBuf: Uint8Array = Buffer.alloc(0),
222
+ ): Promise<tls.TLSSocket> {
223
+ if (!node.tlsEnabled()) throw new Error("TLS unavailable");
224
+ if (initialBuf.length) socket.unshift(initialBuf);
225
+ socket.setTimeout(0);
226
+ socket.pause();
227
+
228
+ const timeoutMs = Math.max(
229
+ TLS_TIMEOUT_MS,
230
+ node.config().connectTimeoutMs,
231
+ );
232
+ const upgraded =
233
+ mode === "client"
234
+ ? tls.connect({
235
+ socket,
236
+ secureContext: tlsContext(),
237
+ rejectUnauthorized: false,
238
+ minVersion: "TLSv1.2",
239
+ })
240
+ : new tls.TLSSocket(socket, {
241
+ isServer: true,
242
+ secureContext: tlsContext(),
243
+ requestCert: false,
244
+ rejectUnauthorized: false,
245
+ });
246
+ return await waitForSecureTlsSocket(
247
+ node,
248
+ upgraded,
249
+ mode,
250
+ timeoutMs,
251
+ () => {
252
+ upgraded.setNoDelay(true);
253
+ maybeStartTlsSocket(upgraded, mode);
254
+ upgraded.resume();
255
+ },
256
+ );
257
+ }
@@ -0,0 +1,141 @@
1
+ import type { PeerCapabilities, PeerRole } from "../types";
2
+ import type { GnutellaServent } from "./node";
3
+ import type { Peer } from "./node_types";
4
+
5
+ function isMeshPeerRole(role: PeerRole): boolean {
6
+ return role !== "leaf";
7
+ }
8
+
9
+ export function nodeMode(node: GnutellaServent) {
10
+ return node.config().nodeMode;
11
+ }
12
+
13
+ export function classifyPeerRole(
14
+ node: GnutellaServent,
15
+ capabilities: PeerCapabilities,
16
+ ): PeerRole {
17
+ const remoteIsUltrapeer = capabilities.isUltrapeer;
18
+ const mode = node.nodeMode();
19
+
20
+ if (mode === "ultrapeer") {
21
+ return remoteIsUltrapeer === true ? "ultrapeer" : "leaf";
22
+ }
23
+
24
+ return remoteIsUltrapeer === true ? "ultrapeer" : "leaf";
25
+ }
26
+
27
+ export function peerRole(
28
+ _node: GnutellaServent,
29
+ peer: Pick<Peer, "role">,
30
+ ): PeerRole {
31
+ return peer.role;
32
+ }
33
+
34
+ export function countPeersByRole(
35
+ node: GnutellaServent,
36
+ role: PeerRole,
37
+ ): number {
38
+ let count = 0;
39
+ for (const peer of node.peers.values()) {
40
+ if (peer.role === role) count++;
41
+ }
42
+ return count;
43
+ }
44
+
45
+ export function connectedLeafCount(node: GnutellaServent): number {
46
+ return node.countPeersByRole("leaf");
47
+ }
48
+
49
+ export function connectedMeshPeerCount(node: GnutellaServent): number {
50
+ let count = 0;
51
+ for (const peer of node.peers.values()) {
52
+ if (isMeshPeerRole(peer.role)) count++;
53
+ }
54
+ return count;
55
+ }
56
+
57
+ export function availableDialSlots(node: GnutellaServent): number {
58
+ const c = node.config();
59
+ if (node.nodeMode() === "ultrapeer") {
60
+ return Math.max(
61
+ 0,
62
+ c.maxUltrapeerConnections -
63
+ node.connectedMeshPeerCount() -
64
+ node.dialing.size,
65
+ );
66
+ }
67
+ return Math.max(
68
+ 0,
69
+ c.maxUltrapeerConnections -
70
+ node.connectedMeshPeerCount() -
71
+ node.dialing.size,
72
+ );
73
+ }
74
+
75
+ export function canAcceptPeerRole(
76
+ node: GnutellaServent,
77
+ role: PeerRole,
78
+ ): { ok: true } | { ok: false; code: number; reason: string } {
79
+ const c = node.config();
80
+ const mode = node.nodeMode();
81
+
82
+ if (mode === "leaf") {
83
+ if (role === "leaf") {
84
+ return {
85
+ ok: false,
86
+ code: 503,
87
+ reason: `Shielded leaf node (${c.maxUltrapeerConnections} ultrapeers max)`,
88
+ };
89
+ }
90
+ if (node.connectedMeshPeerCount() >= c.maxUltrapeerConnections) {
91
+ return {
92
+ ok: false,
93
+ code: 503,
94
+ reason: `Too many ultrapeer connections (${c.maxUltrapeerConnections} max)`,
95
+ };
96
+ }
97
+ return { ok: true };
98
+ }
99
+
100
+ if (role === "leaf") {
101
+ if (node.connectedLeafCount() >= c.maxLeafConnections) {
102
+ return {
103
+ ok: false,
104
+ code: 503,
105
+ reason: `Too many leaf connections (${c.maxLeafConnections} max)`,
106
+ };
107
+ }
108
+ return { ok: true };
109
+ }
110
+
111
+ if (node.connectedMeshPeerCount() >= c.maxUltrapeerConnections) {
112
+ return {
113
+ ok: false,
114
+ code: 503,
115
+ reason: `Too many ultrapeer connections (${c.maxUltrapeerConnections} max)`,
116
+ };
117
+ }
118
+ return { ok: true };
119
+ }
120
+
121
+ export function shouldRelayQueries(node: GnutellaServent): boolean {
122
+ return node.nodeMode() === "ultrapeer";
123
+ }
124
+
125
+ export function shouldRelayPings(node: GnutellaServent): boolean {
126
+ return node.nodeMode() === "ultrapeer";
127
+ }
128
+
129
+ export function isLeafPeer(
130
+ _node: GnutellaServent,
131
+ peer: Pick<Peer, "role">,
132
+ ): boolean {
133
+ return peer.role === "leaf";
134
+ }
135
+
136
+ export function isMeshPeer(
137
+ _node: GnutellaServent,
138
+ peer: Pick<Peer, "role">,
139
+ ): boolean {
140
+ return isMeshPeerRole(peer.role);
141
+ }