package delivery import ( "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "unicode/utf8" ) // ManifestDigest is the hex sha256 of a manifest's bytes exactly as read at the commit: the key of a manifests value // and the digest a Module and a candidate's module carry (novox/hq ADR 0429 clause 16). func ManifestDigest(raw []byte) string { sum := sha256.Sum256(raw) return hex.EncodeToString(sum[:]) } // ManifestsKey is the key, in ManifestsBucket, of a manifest: its digest, 64 lowercase hex digits. The indexer // writes it once, by a create that fails when the key exists. func ManifestsKey(digest string) (string, error) { if !isHex64(digest) { return "", fmt.Errorf("delivery: %q is not a manifest digest, 64 lowercase hex digits", digest) } return digest, nil } // ParseManifestsKey is the inverse of ManifestsKey. func ParseManifestsKey(key string) (digest string, err error) { if digest, err = ManifestsKey(key); err != nil { return "", fmt.Errorf("delivery: %q is not a manifests key: %w", key, err) } return digest, nil } // Manifest is the value of a manifests key: the module and its manifest, the raw text of its module.json as read at the // commit, byte for byte (ManifestDigest of JSON is the key). It is the one value of delivery that is larger than about // 500 bytes: the largest manifest of the catalogue is about 21 KB. It is a string, not nested JSON, because nesting // would re-write the manifest's spacing and so change its digest. type Manifest struct { V int `json:"v"` Module string `json:"module"` JSON string `json:"manifest"` } func (m Manifest) check() error { if err := checkName("module", m.Module); err != nil { return err } if m.JSON == "" { return fmt.Errorf("delivery: the manifest of %s is empty", m.Module) } if !utf8.ValidString(m.JSON) { return fmt.Errorf("delivery: the manifest of %s is not UTF-8 text, which a value cannot hold byte for byte", m.Module) } if !json.Valid([]byte(m.JSON)) { return fmt.Errorf("delivery: the manifest of %s is not JSON", m.Module) } return nil } // EncodeManifest is m as JSON at Version; it refuses a module that is not a name, and a manifest that is empty, not // UTF-8 or not JSON. func EncodeManifest(m Manifest) ([]byte, error) { if err := m.check(); err != nil { return nil, err } m.V = Version return json.Marshal(m) } // DecodeManifest reads a manifests value; it refuses an unknown version (ErrUnknownVersion) and what EncodeManifest // refuses. func DecodeManifest(data []byte) (Manifest, error) { var m Manifest if err := decode(data, &m); err != nil { return Manifest{}, err } if err := m.check(); err != nil { return Manifest{}, err } return m, nil } // SameManifest says whether two manifests are the same value, so a writer writes only when they differ. func SameManifest(a, b Manifest) bool { return a == b }