OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-overlay commits / 132af81

port(0.3.0): adopt v3 messages (more/nonce/from + msgs 12/13)
Superset of 0.2.14 messages-v2: appended-last fields decode-guarded, new transferControl(12)/transferProgress(13). Round-trip verified incl appended-field defaults (wire back-compat). Old protocol-v2 still valid. Increment 2. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
132af81ad7b35ca60a6addd53b3f4024e619fec5
by plankatron <fleeky@gmail.com> · 2026-07-11T13:48:57.000Z

Changed files (1) +96 -16

lib/messages-v2.js +96 -16
……@@ -1,12 +1,13 @@
11/**
22 * Wire Protocol Messages for hyper-overlay/v2
33 *
4 * 12 message types for sync + transfer + trees (0.5a extension):
5 * 0-2: Sync messages (state exchange, file offers/requests)
6 * 3-8: Transfer messages (chunk hashes, data, completion)
7 * 9-11: Tree messages (tree-request, tree-response, content-request)
4 * 13 message types for sync + transfer + trees (0.5a extension):
5 * 0-2: Sync messages (state exchange, file offers/requests)
6 * 3-8: Transfer messages (chunk hashes, data, completion)
7 * 9-11: Tree messages (tree-request, tree-response, content-request)
8 * 12: transfer-control (downloader→holder pause/stop notice)
89 *
9 * Tree messages (9-11) are backward-compatible additions — peers without
10 * Messages 9-12 are backward-compatible additions — peers without
1011 * this code simply don't register the corresponding protomux slots, and
1112 * senders see no receiver for those messages. File-level sync (0-8)
1213 * keeps working unchanged.
……@@ -164,23 +165,34 @@ export const fileRequest = {
164165// ── Transfer Messages (3-8) ──────────────────────────────────
165166
166167// Message 3: chunk-hashes — chunk hash list for a requested file
168//
169// `more` (paging flag) is appended last. A very large file's
170// chunk list serializes past the 16 MiB-1 Noise frame limit
171// (@hyperswarm/secret-stream MAX_ATOMIC_WRITE), so the protocol splits it into
172// several chunkHashes frames: every page but the last sets more:1, the final
173// page sets more:0. The receiver concatenates pages (in arrival order, keyed by
174// path) before dispatching. Appended last so a pre-paging peer that omits it
175// decodes more:0 — i.e. as a single, complete page (back-compatible).
167176export const chunkHashes = {
168177 preencode (state, m) {
169178 c.string.preencode(state, m.path)
170179 c.uint8.preencode(state, m.tier)
171180 chunkInfoArray.preencode(state, m.chunks)
181 c.uint8.preencode(state, m.more || 0)
172182 },
173183 encode (state, m) {
174184 c.string.encode(state, m.path)
175185 c.uint8.encode(state, m.tier)
176186 chunkInfoArray.encode(state, m.chunks)
187 c.uint8.encode(state, m.more || 0)
177188 },
178189 decode (state) {
179 return {
180 path: c.string.decode(state),
181 tier: c.uint8.decode(state),
182 chunks: chunkInfoArray.decode(state)
183 }
190 const path = c.string.decode(state)
191 const tier = c.uint8.decode(state)
192 const chunks = chunkInfoArray.decode(state)
193 // A frame from a pre-paging peer has no trailing byte → more:0.
194 const more = state.start < state.end ? c.uint8.decode(state) : 0
195 return { path, tier, chunks, more }
184196 }
185197}
186198
……@@ -314,51 +326,119 @@ const treeEntryArray = {
314326}
315327
316328// Message 9: tree-request — ask peer for a tree by its content hash
329//
330// `nonce` (per-request id) is appended last; the holder echoes it in every
331// treeResponse page so the requester can drop pages from a superseded attempt.
332// A pre-nonce peer omits it and decodes 0, which the requester treats as
333// "unverifiable".
317334export const treeRequest = {
318335 preencode (state, m) {
319336 c.buffer.preencode(state, toBuffer32(m.hash))
337 c.uint.preencode(state, m.nonce || 0)
320338 },
321339 encode (state, m) {
322340 c.buffer.encode(state, toBuffer32(m.hash))
341 c.uint.encode(state, m.nonce || 0)
323342 },
324343 decode (state) {
325 return { hash: c.buffer.decode(state).toString('hex') }
344 const hash = c.buffer.decode(state).toString('hex')
345 const nonce = state.start < state.end ? c.uint.decode(state) : 0
346 return { hash, nonce }
326347 }
327348}
328349
329350// Message 10: tree-response — return a tree's entries to the requester
351//
352// `more` (paging flag) mirrors chunkHashes: a tree too large for one 16 MiB-1
353// Noise frame is split into several frames, every page but the last more:1.
354// `nonce` echoes the requester's per-request id so pages from a superseded
355// attempt are dropped. Both are appended last; a pre-paging peer omits them and
356// decodes 0.
330357export const treeResponse = {
331358 preencode (state, m) {
332359 c.buffer.preencode(state, toBuffer32(m.hash))
333360 treeEntryArray.preencode(state, m.entries)
361 c.uint8.preencode(state, m.more || 0)
362 c.uint.preencode(state, m.nonce || 0)
334363 },
335364 encode (state, m) {
336365 c.buffer.encode(state, toBuffer32(m.hash))
337366 treeEntryArray.encode(state, m.entries)
367 c.uint8.encode(state, m.more || 0)
368 c.uint.encode(state, m.nonce || 0)
338369 },
339370 decode (state) {
340 return {
341 hash: c.buffer.decode(state).toString('hex'),
342 entries: treeEntryArray.decode(state)
343 }
371 const hash = c.buffer.decode(state).toString('hex')
372 const entries = treeEntryArray.decode(state)
373 const more = state.start < state.end ? c.uint8.decode(state) : 0
374 const nonce = state.start < state.end ? c.uint.decode(state) : 0
375 return { hash, entries, more, nonce }
344376 }
345377}
346378
347379// Message 11: content-request — fetch a file by its content hash (not path)
348380// Sender locates any local file matching the hash and serves its chunks.
381// `from` carries the requester's profile-key (hex) so the
382// holder's serveAuthorizer can authenticate the asker. Appended last → an
383// overlay peer that predates this field decodes '' (empty), never crashes.
349384export const contentRequest = {
350385 preencode (state, m) {
351386 c.buffer.preencode(state, toBuffer32(m.contentHash))
352387 c.buffer.preencode(state, m.chunksHave || Buffer.alloc(0))
388 c.string.preencode(state, m.from || '')
353389 },
354390 encode (state, m) {
355391 c.buffer.encode(state, toBuffer32(m.contentHash))
356392 c.buffer.encode(state, m.chunksHave || Buffer.alloc(0))
393 c.string.encode(state, m.from || '')
357394 },
358395 decode (state) {
359396 return {
360397 contentHash: c.buffer.decode(state).toString('hex'),
361 chunksHave: c.buffer.decode(state)
398 chunksHave: c.buffer.decode(state),
399 from: c.string.decode(state)
400 }
401 }
402}
403
404// Message 12: transfer-control — downloader→holder pause/stop notice for a
405// content-addressed fetch. state: 0 = paused, 1 = stopped. Appended last so a
406// holder that predates this never registers slot 12 and silently ignores it.
407export const transferControl = {
408 preencode (state, m) {
409 c.buffer.preencode(state, toBuffer32(m.contentHash))
410 c.uint8.preencode(state, m.state)
411 },
412 encode (state, m) {
413 c.buffer.encode(state, toBuffer32(m.contentHash))
414 c.uint8.encode(state, m.state)
415 },
416 decode (state) {
417 return {
418 contentHash: c.buffer.decode(state).toString('hex'),
419 state: c.uint8.decode(state)
420 }
421 }
422}
423
424// Message 13: transfer-progress — downloader→holder one-shot have-baseline for a
425// resumed content-addressed fetch. `have` = bytes the downloader already holds on
426// disk at resume, so the holder's "who is downloading" bar reflects the downloader's
427// TRUE completion, not only the bytes it re-serves. Appended last so a holder that
428// predates this never registers slot 13 and silently ignores it.
429export const transferProgress = {
430 preencode (state, m) {
431 c.buffer.preencode(state, toBuffer32(m.contentHash))
432 c.uint.preencode(state, m.have || 0)
433 },
434 encode (state, m) {
435 c.buffer.encode(state, toBuffer32(m.contentHash))
436 c.uint.encode(state, m.have || 0)
437 },
438 decode (state) {
439 return {
440 contentHash: c.buffer.decode(state).toString('hex'),
441 have: c.uint.decode(state)
362442 }
363443 }
364444}