package erasure import ( "crypto/sha256" "encoding/hex" "fmt" "net/url" "strings" ) // TorrentShardEntry is one shard in a published torrent manifest. type TorrentShardEntry struct { Index int `json:"index"` ShardHash string `json:"shard_hash"` URL string `json:"url"` } // TorrentManifest is the C2 super-seeder torrent manifest for a deploy-plan token. type TorrentManifest struct { Token string `json:"token"` Scheme string `json:"scheme"` DataShards int `json:"data_shards"` ParityShards int `json:"parity_shards"` PayloadSHA256 string `json:"sha256"` PayloadSize int `json:"payload_size"` SwarmMagnet string `json:"swarm_magnet"` ManifestURL string `json:"manifest_url"` ShardManifestURLs []string `json:"shard_manifest_urls"` Shards []TorrentShardEntry `json:"shards"` } // ShardContentHash returns the content-address hex SHA256 of one encoded shard. func ShardContentHash(shard []byte) string { sum := sha256.Sum256(shard) return hex.EncodeToString(sum[:]) } // ShardContentHashes returns content-address hashes for each shard slice. func ShardContentHashes(shards [][]byte) []string { out := make([]string, len(shards)) for i, sh := range shards { out[i] = ShardContentHash(sh) } return out } // BuildTorrentManifest publishes canonical shard URLs and a swarm magnet for BGP hints. func BuildTorrentManifest(serverURL, token, payloadSHA string, payloadSize int, p Params, shardHashes []string) (*TorrentManifest, error) { p, err := p.Normalize() if err != nil { return nil, err } total := p.TotalShards() if len(shardHashes) != total { return nil, fmt.Errorf("erasure torrent: shard hash count mismatch") } base := strings.TrimRight(strings.TrimSpace(serverURL), "/") if base == "" { base = "http://127.0.0.1:8989" } manifestURL := fmt.Sprintf("%s/api/v1/public/erasure-torrent/%s/manifest", base, token) shards := make([]TorrentShardEntry, total) urls := make([]string, total) for i := 0; i < total; i++ { shardURL := fmt.Sprintf("%s/api/v1/public/erasure-shard/%s/%d", base, token, i) shards[i] = TorrentShardEntry{Index: i, ShardHash: shardHashes[i], URL: shardURL} urls[i] = shardURL } return &TorrentManifest{ Token: token, Scheme: SchemeReedSolomonV1, DataShards: p.DataShards, ParityShards: p.ParityShards, PayloadSHA256: strings.TrimSpace(strings.ToLower(payloadSHA)), PayloadSize: payloadSize, SwarmMagnet: SwarmMagnetLink(token, payloadSHA), ManifestURL: manifestURL, ShardManifestURLs: urls, Shards: shards, }, nil } // SwarmMagnetLink builds a magnet URI for fleet torrent swarm discovery. func SwarmMagnetLink(token, payloadSHA string) string { token = strings.TrimSpace(token) payloadSHA = strings.TrimSpace(strings.ToLower(payloadSHA)) if token == "" { return "" } q := url.Values{} if payloadSHA != "" { q.Set("xt", "urn:sha256:"+payloadSHA) } label := token if len(label) > 8 { label = label[:8] } q.Set("dn", "aetherforge-erasure-"+label) q.Set("tr", "urn:aetherforge:erasure:"+token) return "magnet:?" + q.Encode() }