Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
115 commits
Select commit Hold shift + click to select a range
8aa333b
companion: P1.1 — B-tree page encode/decode for Aggregate index torrents
Apr 24, 2026
c0a153c
dhtindex+companion: P2.1 PPMI schema + P1.2 B-tree builder
Apr 24, 2026
13237ea
companion: P1.3 — B-tree reader with prefix-query walker
Apr 24, 2026
ac20327
swarmsearch: P3.1 — rateless IBLT encoder/decoder
Apr 24, 2026
4d7624a
companion+swarmsearch: P5.1 — hashcash PoW mint + misbehavior hooks
Apr 24, 2026
5112c9f
dhtindex: P2.2 — PPMI DHT put/get + memory store coexistence
Apr 24, 2026
0c07af6
dhtindex: P2.3 — Lookup.Query resolves PPMIs, falls back to legacy
Apr 24, 2026
4ab0df1
swarmsearch: P3.2 — sn_search sync-session wire + state machine
Apr 24, 2026
4dba164
daemon: P4.1 — three-channel cold-subscriber bootstrap
Apr 24, 2026
338f16d
daemon: P5.2 HTTPS anchor fallback + Final E2E integration
Apr 24, 2026
5fdcfe9
swarmsearch: wire sync msg_types 4-8 + BitSetReconciliation gate
Apr 24, 2026
9e6f613
swarmsearch: plumb RecordSource so sync replies carry real records
Apr 24, 2026
768fef6
docs: sync §4.3 + BEP-07 draft for Aggregate PPMI inversion
Apr 24, 2026
0b53b43
companion+swarmsearch: SPEC §7 regression benchmarks
Apr 24, 2026
3416638
cli: swartznet aggregate {inspect,find} — Aggregate ops tooling
Apr 24, 2026
b60fbc1
cli: swartznet aggregate build — sign + pack JSONL into a signed index
Apr 24, 2026
6d6d7a6
swarmsearch: RecordCache — in-memory RecordSource for the engine
Apr 24, 2026
fac200d
httpapi: GET /aggregate — Aggregate subsystem introspection
Apr 24, 2026
ec7a791
cli: render Aggregate block in 'swartznet status'
Apr 24, 2026
160b5e6
httpapi/web: render Aggregate card on the Status tab
Apr 24, 2026
fe09827
gui: Aggregate card on the Status tab
Apr 24, 2026
303b484
engine: attach RecordCache as Aggregate RecordSource at startup
Apr 24, 2026
8a62e6c
engine: mint Aggregate records on torrent add
Apr 24, 2026
a36f44b
daemon: wire Aggregate Bootstrap into daemon.New
Apr 24, 2026
6ca5a5b
httpapi+cli: expose Aggregate Bootstrap state on /aggregate and status
Apr 24, 2026
8b4c205
docs: v0.5.0 Aggregate milestone summary
Apr 24, 2026
5439ea6
swarmsearch+daemon: channel-C endorsement gossip → Bootstrap
Apr 24, 2026
9f7ce87
gui: prime Fyne DefaultTheme in TestMain to stabilise -race flake
Apr 24, 2026
5f3117d
swarmsearch+testlab: BitSetReconciliation in DefaultServices + scenario
Apr 24, 2026
33f5a52
swarmsearch: outbound Protocol.StartSync initiator entry point
Apr 24, 2026
2422c52
testlab+swarmsearch: two-engine Aggregate sync round-trip + thread-sa…
Apr 24, 2026
09f3ef0
swarmsearch+engine+testlab: RecordSink — sync_records ingestion lands…
Apr 24, 2026
f956521
swarmsearch+daemon: sync-record ingestion feeds Bootstrap admission
Apr 24, 2026
0c2946a
daemon+testlab: Bootstrap tracks crawl-observed pubkeys + e2e scenario
Apr 24, 2026
d02e5de
all surfaces: expose Bootstrap.PendingCount on /aggregate, CLI, web, GUI
Apr 24, 2026
4bb9f3f
swarmsearch: RecordCache FIFO eviction + PruneOlderThan
Apr 24, 2026
8e4d411
engine+all surfaces: apply DefaultRecordCacheMax, surface cap
Apr 24, 2026
5d0b8d5
swarmsearch: RecordCache eviction-under-load test + benchmarks
Apr 24, 2026
036f1c8
engine: periodic RecordCache prune goroutine + regression test
Apr 24, 2026
6f541d0
dhtindex: BEP-51 sample_infohashes primitive + happy-path test
Apr 24, 2026
842f302
dhtindex: PublisherFromMetainfo classifier for Channel-B crawler
Apr 25, 2026
ef97fad
dhtindex: CrawlOnce glues sample + fetch + classify + sink
Apr 25, 2026
498e5c4
cli: 'swartznet crawl-probe' ops tool + MILESTONE doc sync
Apr 25, 2026
df20f8d
daemon: cover bootstrap_https adapter + Bootstrap counters
Apr 25, 2026
41f0435
engine: cover gossipIndexerSink + Handle.FileEvents aliases
Apr 25, 2026
4fa4d2c
swarmsearch+dhtindex: cover small accessors
Apr 25, 2026
c6c9aef
swarmsearch: cover onSyncRecords + onSyncEnd handler branches
Apr 25, 2026
c597c18
dhtindex: cover Anacrolix PutPPMI/GetPPMI round-trip
Apr 25, 2026
eefe04c
companion: cover compareRecords + EncodeRecord/DecodeRecord error bra…
Apr 25, 2026
e3c4de6
companion+reputation: cover small edge branches
Apr 25, 2026
d0a9880
testlab: remove dead ltepHandshake wrapper
Apr 25, 2026
b74cb99
companion: cover OpenBTree error branches
Apr 25, 2026
32e6a7c
companion: cover Find's MinPoWBits enforcement branch
Apr 25, 2026
a9b136d
companion: cover packInteriorLevel guard branches
Apr 25, 2026
ec80f61
engine: cover FetchCompanionTorrent multi-file rejection
Apr 25, 2026
0dadd19
docs: CHANGELOG sync for crawler primitives + prune goroutine
Apr 25, 2026
806176e
docs: ROADMAP post-Final hardening section + crawler status
Apr 25, 2026
836b7aa
dhtindex: cover Anacrolix BEP-46 pointer Put/Get round-trip
Apr 25, 2026
51405d8
swarmsearch: cover handleSyncFrame cap gate + bad-bencode dispatches
Apr 25, 2026
812cb72
swarmsearch: cover sync_wire encode/decode validation branches
Apr 25, 2026
32d2323
companion: cover atomicWrite Rename failure branch
Apr 25, 2026
61fe5f9
daemon: cover Bootstrap admit + bloomPolicy + countStrongEndorsers
Apr 25, 2026
ff7bd17
daemon: cover Bootstrap IsPending + CandidateFromCrawl + admit branches
Apr 25, 2026
02e29c5
daemon: anchor admit MarkSeeded the tracker, drop placeholder
Apr 25, 2026
3bece85
engine: fix TestSessionRoundTrip flake — upgradeMagnetSession race
Apr 25, 2026
e62bd35
engine: regression test for upgradeMagnetSession race
Apr 25, 2026
deb5e34
engine: hermetic test paths — clear XDG defaults across the board
Apr 25, 2026
b7098d7
daemon: hermetic test paths — match engine-side fix
Apr 25, 2026
4e74d56
docs: MILESTONE — record race + XDG pollution rough edges
Apr 25, 2026
ffc1a1e
cli: cover emitStatusText publisher-keyword rendering
Apr 25, 2026
ba6ff8a
engine: remove dead exports — DHTServerForTest, fileTracker.Events
Apr 25, 2026
4f7dc0e
docs/08-operations: document Aggregate + crawl-probe ops commands
Apr 25, 2026
4ebe754
swarmsearch: cover onSyncSymbols + onSyncNeed unknown-session paths
Apr 25, 2026
d37abc2
swarmsearch: cover ApplyBegin guard branches
Apr 25, 2026
b0b6d28
docs/AGENTS: list aggregate + crawl-probe verbs, document hermetic-te…
Apr 25, 2026
7af136f
daemon: cover bootstrap{EndorsementSink, PublisherObserver} adapters
Apr 25, 2026
9fbccab
reputation: cover optimalSize zero-n fallback
Apr 25, 2026
6000548
engine: cover sortHandlesByQueueOrder all branches
Apr 25, 2026
4b16acf
cli: help text — list 'build' alongside inspect/find
Apr 25, 2026
1cf3c2a
go.mod: promote klauspost/compress to direct dependency
Apr 25, 2026
27539e3
gofmt -s sweep across cmd/ and internal/
Apr 25, 2026
aa46b9f
CHANGELOG: document upgradeMagnetSession race + anchor admit fix
Apr 25, 2026
3ec6ce6
swarmsearch: cover sendSync{Records,Symbols,End} encode-error arms
Apr 25, 2026
681886d
swarmsearch: cover onSyncBegin error + nil-source branches
Apr 25, 2026
8cb7e76
swarmsearch: cover ApplyRecords txid + field-length guards
Apr 25, 2026
d3322b8
swarmsearch: cover SyncSession.Begin role + phase guards
Apr 25, 2026
2d6f806
swarmsearch: cover SendSyncNeed/CloseSync no-sender + bad-phase paths
Apr 25, 2026
aa34a79
swarmsearch: cover EncodeSyncEnd empty-status default
Apr 25, 2026
e1e81dc
gui: cover swartzTheme Color switch + Font/Icon/Size delegates
Apr 25, 2026
d20ed64
gui: lock windowForObject nil-obj contract + signature assertion
Apr 25, 2026
e63f2aa
companion: cover DecodeInterior error branches
Apr 25, 2026
ae98cdc
companion: cover DecodeLeaf + EncodeTrailer error branches
Apr 25, 2026
cd70e50
companion: cover packLeaves keyword validation guards
Apr 25, 2026
471f312
companion: cover Find leaf-fetch + leaf-decode error paths
Apr 25, 2026
4d2d411
dhtindex: cover DecodePPMI field-length validation arms
Apr 25, 2026
68b7259
cli: cover defaultIdentityPath helper
Apr 25, 2026
4fbc7fd
swarmsearch: cover onSyncRecords sink-routing happy path
Apr 25, 2026
3510bb5
dhtindex: cover NewAnacrolixPutter key-size guard
Apr 25, 2026
828d519
dhtindex: cover CrawlOnce malformed-metainfo classification
Apr 25, 2026
5548a16
dhtindex: cover CrawlOnce mid-loop context-cancel arm
Apr 25, 2026
587f99a
dhtindex: cover SampleInfohashes error + nodes6 arms
Apr 25, 2026
b7e1ad2
companion: cover btree encoder validation guards
Apr 25, 2026
df991fb
companion: fix makeInteriorPage missing Version field
Apr 25, 2026
a61fe13
companion: cover Decode{Interior,Leaf} short-page + bad-varint arms
Apr 25, 2026
8e0b4b7
companion: cover DecodeTrailer unsupported-version arm
Apr 25, 2026
a97fcdc
swarmsearch: cover SyncSession Finish/ApplyEnd/SetBudgets gaps
Apr 25, 2026
ab49ce5
trust: cover LoadOrCreate non-NotExist open-error arm
Apr 25, 2026
659063e
reputation: cover scoreOf returned-clamp arm
Apr 25, 2026
31701bc
daemon: cover Bootstrap.PendingCount admitted-skip arms
Apr 25, 2026
3d8c2e5
daemon: cover NewBootstrap nil-guard + admitted IngestEndorsement
Apr 25, 2026
daf8e5b
daemon: cover NewBootstrap zero-options default-fill arms
Apr 25, 2026
84d5614
indexer: cover Search hit-projection multi-tracker arm
Apr 25, 2026
5fd6bdc
engine: cover AddTrustedPeerEngine success arm
Apr 25, 2026
2be0e49
signing: cover VerifyFile io.ReadAll error arm
Apr 25, 2026
35ad49d
signing: cover SignFile WriteFile-fail arm
Apr 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,24 @@ CI quirks (`.github/workflows/test.yml`) that are easy to miss:
`*_branches_test.go`. When fixing a bug or adding a branch, add a
matching test — the iterate loop treats coverage growth as the default
improvement signal.
- **Hermetic test configs.** Tests that build a `config.Default()`
for `engine.New` / `daemon.New` MUST clear the user-level XDG paths
(`IdentityPath`, `ReputationPath`, `SeedListPath`, `BloomPath`,
`TrustPath`) — leaving them at the defaults points the test at
`~/.local/share/swartznet/*` and parallel tests then race on
shared on-disk state. The pattern is to set each to `""` (the
engine treats empty paths as "skip this subsystem"). Look at
`internal/engine/create_test.go`'s `newTestEngine` for the
canonical setup. The `TrustPath` / `BloomPath` are sometimes set
to a path inside `t.TempDir()` instead of `""` when the test
needs that subsystem live.

## Repository layout

- `cmd/swartznet` — CLI. `main.go` dispatches to `cmd_*.go` per
subcommand (`add`, `search`, `create`, `index`, `files`, `flag`,
`status`, `trust`, `confirm`).
`status`, `trust`, `confirm`, `aggregate {inspect,find,build}`,
`crawl-probe`).
- `cmd/swartznet-gui` — Fyne-based GUI; thin shim over `internal/gui`.
- `cmd/dht-smoke` — standalone DHT probe for ops.
- `internal/daemon` — **the wiring point.** `daemon.New(ctx, Options)`
Expand Down
123 changes: 123 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,131 @@ one second client implementing `sn_search` (the BEP-1
requirement to take a draft to Final). Both require
engagement from actual users of the v0.x prereleases.

### Added — "Aggregate" distributed-layer redesign

A multi-commit series inverts Layer-D from per-keyword BEP-44
items into per-publisher pointers, moves the real keyword index
into a piece-aligned B-tree inside a regular BitTorrent
"companion index torrent", and reconciles updates between peers
via a new Rateless IBLT set-sync session layered on `sn_search`.
Design and rationale in
[`docs/research/PROPOSAL.md`](docs/research/PROPOSAL.md);
byte-level spec in [`docs/research/SPEC.md`](docs/research/SPEC.md);
phase-by-phase tracking in
[`docs/research/ROADMAP.md`](docs/research/ROADMAP.md).

Shipped components:

- `internal/companion/btree.go` — B-tree page layout
(magic `SNAGG\0`, interior/leaf/trailer kinds) with
deterministic build, BFS piece assignment, ed25519-signed
trailer binding the tree to a publisher.
- `internal/companion/read_btree.go` — prefix-query walker
with trailer-sig verification and per-record sig re-check
on every returned hit.
- `internal/companion/pow.go` — hashcash mint +
`SignAndMineRecord` convenience (D=20 default).
- `internal/dhtindex/ppmi.go` — Publisher Pointer Mutable
Item. One BEP-44 item per publisher at the fixed
double-hashed salt `SHA256("snet.index")`. Collapses DHT
cost from O(publishers × keywords) to O(publishers).
- `internal/dhtindex/lookup.go` — `Lookup.Query` resolves
PPMIs first, falls back to legacy per-keyword for
publishers that haven't migrated. Dual-read migration.
- `internal/swarmsearch/riblt.go` — rateless IBLT encoder/
decoder (SIGCOMM 2024) with graduated-degree cycle.
Converges in ~3d symbols for symmetric difference d.
- `internal/swarmsearch/sync_{wire,session}.go` — msg_types
4-8 wire format and in-process session state machine.
- `internal/swarmsearch/handler.go` — LTEP dispatch for
sync msg_types behind the `BitSetReconciliation` (services
bit 9) capability gate. Peers without the bit receive
`reject code 2`.
- `internal/daemon/bootstrap.go` + `bootstrap_https.go` —
three-channel cold-start (anchors + BEP-51 candidates +
`peer_announce` endorsements) plus last-ditch HTTPS anchor
fallback.

Test coverage: ~130 new test functions across the four
packages, including the capstone `TestAggregateEndToEnd` that
exercises publisher → PPMI → subscriber → prefix-query in one
pass.

Status of the BEP-51 crawler track: the primitives now land
incrementally — `dhtindex.SampleInfohashes` (raw BEP-51 query),
`dhtindex.PublisherFromMetainfo` (signature classifier), and
`dhtindex.CrawlOnce` (one-tick glue: sample + fetch + classify
+ sink). A `swartznet crawl-probe --addr <host:port>` ops
command exercises the primitive against a live peer.

The remaining gap is a *production* MetainfoFetcher: BEP-9
ut_metadata only transports the info dict, while
`snet.pubkey` / `snet.sig` are top-level metainfo fields, so
a BEP-9-only fetcher can't promote crawled infohashes to the
"observed publishers" set without an additional signature-
exchange channel. Closing the gap needs either a new LTEP
extension (e.g. `ut_signature`) or a tracker / HTTP mirror
convention. Documented in
[`docs/research/MILESTONE-v0.5.0.md`](docs/research/MILESTONE-v0.5.0.md).
LocalRecord sync was wired up in earlier commits so nodes do
share records over the responder path; the engine attaches a
RecordCache as both source and sink in `engine.New`.

### Added — `swartznet crawl-probe` ops command

One-shot CLI that issues a single BEP-51 `sample_infohashes`
query against a DHT address and prints the response (samples,
interval, num, closest nodes). Pure ops tooling — no running
daemon needed. Useful for validating that a peer supports
BEP-51 and for hand-inspecting the samples it volunteers
during Channel-B crawler development. Text + JSON output.

### Added — Engine periodic RecordCache prune

`engine.DefaultRecordCacheMaxAge` (30 days) +
`DefaultRecordCachePruneInterval` (1 hour). On startup
`engine.New` launches a goroutine that calls
`RecordCache.PruneOlderThan` on a ticker so the Aggregate
cache's TTL is enforced automatically without operator
intervention. Regtest mode swaps in 200 ms / 500 ms so
scenario tests can observe the prune cycle.

### Fixed

- **`upgradeMagnetSession` race in `Engine.AddTorrentFile`**. The
metainfo-arrival upgrade goroutine was spawned for every
`registerLocked` caller, including `AddTorrentFile` and
`AddTorrentMetaInfo`. For those paths, the goroutine raced
`writeTorrentCopy` for the same `<hex>.torrent.tmp` target
file: when the goroutine won the rename, the saved bytes
contained a re-marshalled metainfo (anacrolix's
`Torrent.Metainfo()` synthesises CreationDate / CreatedBy
fields) that no longer byte-matched the original. On the
next `RestoreSession`, `metainfo.Load` rejected the file
with "error after decoding metainfo: expected EOF". Symptom:
intermittent flake under heavy parallel race testing where
`eng2.TorrentSnapshots()` returned 1 torrent instead of 2.
Fix moves the goroutine spawn out of `registerLocked` and
into `AddMagnet` / `AddInfoHash` only — the two paths that
genuinely benefit from a post-fetch metainfo upgrade. Same
gate added in `restoreEntry` so file/metainfo restores skip
the upgrade. Regression-gated by
`TestAddTorrentFileDoesNotRemarshalCopy` (deterministic:
fails reproducibly with original buggy code).

- **Anchor-source admit no-op**. `daemon.Bootstrap.admit`
contained a documented placeholder that called
`tracker.RecordReturned(pub, 0)`, which `RecordReturned`
silently ignores (`if n <= 0 { return }`). Anchor pubkeys
therefore never landed on the tracker, breaking the
intent of `opts.AnchorReputation` and leaving the lookup's
heavy-tail rule unable to identify trusted publishers.
Replaced with `tracker.MarkSeeded(pub, label)` for
`source == "anchor"` only — gives anchors the high
starting score the seeded-bonus branch of `scoreOf` is
designed to express. Candidate sources stay un-seeded
(default 0.5; earn or lose reputation organically).

- **Layer-D BEP-44 put/get round-trip**. anacrolix/torrent's
`NewAnacrolixDhtServer` builds a `dht.ServerConfig` from
scratch and does NOT copy `dht.NewDefaultServerConfig`'s
Expand Down
160 changes: 160 additions & 0 deletions cmd/swartznet/cmd_aggregate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package main

import (
"encoding/hex"
"flag"
"fmt"
"io"
"os"

"github.com/swartznet/swartznet/internal/companion"
)

// cmdAggregate dispatches the `swartznet aggregate <subcommand>`
// tree. These subcommands are ops tooling for the v0.5.0
// "Aggregate" redesign — they exercise the companion-index
// B-tree path without needing a running daemon. Useful for
// operators validating a locally-built index before publishing
// and for subscribers inspecting index torrents they've
// downloaded.
func cmdAggregate(args []string, stdout, stderr io.Writer) int {
if len(args) == 0 {
fmt.Fprintln(stderr, "swartznet aggregate: missing subcommand")
printAggregateUsage(stderr)
return exitUsage
}
sub, rest := args[0], args[1:]
switch sub {
case "inspect":
return cmdAggregateInspect(rest, stdout, stderr)
case "find":
return cmdAggregateFind(rest, stdout, stderr)
case "build":
return cmdAggregateBuild(rest, stdout, stderr)
case "help", "-h", "--help":
printAggregateUsage(stdout)
return exitOK
default:
fmt.Fprintf(stderr, "swartznet aggregate: unknown subcommand %q\n\n", sub)
printAggregateUsage(stderr)
return exitUsage
}
}

func printAggregateUsage(w io.Writer) {
fmt.Fprintln(w, `swartznet aggregate — Aggregate (PPMI + B-tree) ops tooling

Usage:
swartznet aggregate <subcommand> [args]

Subcommands:
build --out=FILE [flags] Sign + pack JSONL records into a signed B-tree index.
inspect <index-file> Print trailer metadata for an Aggregate index.
find <index-file> <prefix> List records matching a keyword prefix.
help Print this message.

The <index-file> path points at the single-file payload inside an
Aggregate companion torrent (i.e. the bytes BuildBTree produces).
Operators get this file by either building it locally via engine
APIs or extracting it from a downloaded companion torrent via
the engine's "files" listing.`)
}

// cmdAggregateInspect prints the trailer + high-level stats for
// an Aggregate index file. Fails if the file isn't a well-formed
// B-tree (bad magic, wrong version, bad trailer signature, etc.).
func cmdAggregateInspect(args []string, stdout, stderr io.Writer) int {
fs := flag.NewFlagSet("aggregate inspect", flag.ContinueOnError)
fs.SetOutput(stderr)
var pieceSize int
fs.IntVar(&pieceSize, "piece-size", companion.MinPieceSize,
"piece size in bytes; must match the torrent's metainfo")
if err := fs.Parse(args); err != nil {
return exitUsage
}
if fs.NArg() != 1 {
fmt.Fprintln(stderr, "usage: swartznet aggregate inspect <index-file>")
return exitUsage
}
path := fs.Arg(0)

data, err := os.ReadFile(path)
if err != nil {
fmt.Fprintf(stderr, "swartznet: read %s: %v\n", path, err)
return exitRuntime
}
src := &companion.BytesPageSource{Data: data, PieceSize: pieceSize}
reader, err := companion.OpenBTree(src)
if err != nil {
fmt.Fprintf(stderr, "swartznet: open b-tree: %v\n", err)
return exitRuntime
}
tr := reader.Trailer()

fmt.Fprintln(stdout, "Aggregate index inspection")
fmt.Fprintf(stdout, " file: %s\n", path)
fmt.Fprintf(stdout, " file size: %d bytes\n", len(data))
fmt.Fprintf(stdout, " piece size: %d bytes\n", pieceSize)
fmt.Fprintf(stdout, " pages: %d\n", tr.NumPages)
fmt.Fprintf(stdout, " records: %d\n", tr.NumRecords)
fmt.Fprintf(stdout, " publisher pk: %s\n", hex.EncodeToString(tr.PubKey[:]))
fmt.Fprintf(stdout, " sequence: %d\n", tr.Seq)
fmt.Fprintf(stdout, " created: %d (unix)\n", tr.CreatedTs)
fmt.Fprintf(stdout, " min PoW bits: %d\n", tr.MinPoWBits)
fmt.Fprintf(stdout, " fingerprint: %s\n", hex.EncodeToString(tr.TreeFingerprint[:]))
return exitOK
}

// cmdAggregateFind runs a prefix-query and prints the matching
// records' infohash + keyword. Mirrors what a subscriber would
// get back from BTreeReader.Find.
func cmdAggregateFind(args []string, stdout, stderr io.Writer) int {
fs := flag.NewFlagSet("aggregate find", flag.ContinueOnError)
fs.SetOutput(stderr)
var pieceSize int
var verify bool
fs.IntVar(&pieceSize, "piece-size", companion.MinPieceSize,
"piece size in bytes; must match the torrent's metainfo")
fs.BoolVar(&verify, "verify", false,
"also run VerifyFingerprint (scans every leaf; slower)")
if err := fs.Parse(args); err != nil {
return exitUsage
}
if fs.NArg() != 2 {
fmt.Fprintln(stderr, "usage: swartznet aggregate find [--piece-size=N] [--verify] <index-file> <prefix>")
return exitUsage
}
path, prefix := fs.Arg(0), fs.Arg(1)

data, err := os.ReadFile(path)
if err != nil {
fmt.Fprintf(stderr, "swartznet: read %s: %v\n", path, err)
return exitRuntime
}
src := &companion.BytesPageSource{Data: data, PieceSize: pieceSize}
reader, err := companion.OpenBTree(src)
if err != nil {
fmt.Fprintf(stderr, "swartznet: open b-tree: %v\n", err)
return exitRuntime
}
if verify {
if err := reader.VerifyFingerprint(); err != nil {
fmt.Fprintf(stderr, "swartznet: fingerprint verification failed: %v\n", err)
return exitRuntime
}
}
hits, err := reader.Find(prefix)
if err != nil {
fmt.Fprintf(stderr, "swartznet: find %q: %v\n", prefix, err)
return exitRuntime
}

fmt.Fprintf(stdout, "Matches for prefix %q: %d records\n", prefix, len(hits))
for _, h := range hits {
fmt.Fprintf(stdout, " %s %-40s t=%d\n",
hex.EncodeToString(h.Ih[:]),
h.Kw,
h.T)
}
return exitOK
}
Loading
Loading