| … | … | @@ -1,12 +1,13 @@ |
| 1 | 1 | /** |
| 2 | 2 | * Wire Protocol Messages for hyper-overlay/v2 |
| 3 | 3 | * |
| 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) |
| 8 | 9 | * |
| 9 | | * Tree messages (9-11) are backward-compatible additions — peers without |
| 10 | * Messages 9-12 are backward-compatible additions — peers without |
| 10 | 11 | * this code simply don't register the corresponding protomux slots, and |
| 11 | 12 | * senders see no receiver for those messages. File-level sync (0-8) |
| 12 | 13 | * keeps working unchanged. |
| … | … | @@ -164,23 +165,34 @@ export const fileRequest = { |
| 164 | 165 | // ── Transfer Messages (3-8) ────────────────────────────────── |
| 165 | 166 | |
| 166 | 167 | // 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). |
| 167 | 176 | export const chunkHashes = { |
| 168 | 177 | preencode (state, m) { |
| 169 | 178 | c.string.preencode(state, m.path) |
| 170 | 179 | c.uint8.preencode(state, m.tier) |
| 171 | 180 | chunkInfoArray.preencode(state, m.chunks) |
| 181 | c.uint8.preencode(state, m.more || 0) |
| 172 | 182 | }, |
| 173 | 183 | encode (state, m) { |
| 174 | 184 | c.string.encode(state, m.path) |
| 175 | 185 | c.uint8.encode(state, m.tier) |
| 176 | 186 | chunkInfoArray.encode(state, m.chunks) |
| 187 | c.uint8.encode(state, m.more || 0) |
| 177 | 188 | }, |
| 178 | 189 | 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 } |
| 184 | 196 | } |
| 185 | 197 | } |
| 186 | 198 | |
| … | … | @@ -314,51 +326,119 @@ const treeEntryArray = { |
| 314 | 326 | } |
| 315 | 327 | |
| 316 | 328 | // 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". |
| 317 | 334 | export const treeRequest = { |
| 318 | 335 | preencode (state, m) { |
| 319 | 336 | c.buffer.preencode(state, toBuffer32(m.hash)) |
| 337 | c.uint.preencode(state, m.nonce || 0) |
| 320 | 338 | }, |
| 321 | 339 | encode (state, m) { |
| 322 | 340 | c.buffer.encode(state, toBuffer32(m.hash)) |
| 341 | c.uint.encode(state, m.nonce || 0) |
| 323 | 342 | }, |
| 324 | 343 | 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 } |
| 326 | 347 | } |
| 327 | 348 | } |
| 328 | 349 | |
| 329 | 350 | // 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. |
| 330 | 357 | export const treeResponse = { |
| 331 | 358 | preencode (state, m) { |
| 332 | 359 | c.buffer.preencode(state, toBuffer32(m.hash)) |
| 333 | 360 | treeEntryArray.preencode(state, m.entries) |
| 361 | c.uint8.preencode(state, m.more || 0) |
| 362 | c.uint.preencode(state, m.nonce || 0) |
| 334 | 363 | }, |
| 335 | 364 | encode (state, m) { |
| 336 | 365 | c.buffer.encode(state, toBuffer32(m.hash)) |
| 337 | 366 | treeEntryArray.encode(state, m.entries) |
| 367 | c.uint8.encode(state, m.more || 0) |
| 368 | c.uint.encode(state, m.nonce || 0) |
| 338 | 369 | }, |
| 339 | 370 | 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 } |
| 344 | 376 | } |
| 345 | 377 | } |
| 346 | 378 | |
| 347 | 379 | // Message 11: content-request — fetch a file by its content hash (not path) |
| 348 | 380 | // 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. |
| 349 | 384 | export const contentRequest = { |
| 350 | 385 | preencode (state, m) { |
| 351 | 386 | c.buffer.preencode(state, toBuffer32(m.contentHash)) |
| 352 | 387 | c.buffer.preencode(state, m.chunksHave || Buffer.alloc(0)) |
| 388 | c.string.preencode(state, m.from || '') |
| 353 | 389 | }, |
| 354 | 390 | encode (state, m) { |
| 355 | 391 | c.buffer.encode(state, toBuffer32(m.contentHash)) |
| 356 | 392 | c.buffer.encode(state, m.chunksHave || Buffer.alloc(0)) |
| 393 | c.string.encode(state, m.from || '') |
| 357 | 394 | }, |
| 358 | 395 | decode (state) { |
| 359 | 396 | return { |
| 360 | 397 | 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. |
| 407 | export 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. |
| 429 | export 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) |
| 362 | 442 | } |
| 363 | 443 | } |
| 364 | 444 | } |