package delivery import ( "encoding/json" "fmt" "reflect" "strings" ) // The controller's grants (novox/hq ADR 0528 clause 2, ADR 0429 ยง6 rule 6): when the controller has issued a module's // bus credential for a fingerprint, it writes `.` in its bucket grants. The assigner reads // it: a node's want moves to a fingerprint only once its grant exists. Only the controller writes the bucket. // // The names say Controller, since the catalogue's GrantKey (catalogue/streams.go) is a stream's grant pattern, unrelated. const ( // ControllerGrantsBucket is the controller's bucket of issued credentials by fingerprint. ControllerGrantsBucket = "mesh-controller_grants" // ControllerGrantsHistory is the revisions the bucket keeps per key. ControllerGrantsHistory = 5 ) // ControllerGrantKey is the key, in ControllerGrantsBucket, of a module's credential issued for a fingerprint: // `.`, the hex being the fingerprint's key part (FingerprintKeyPart). func ControllerGrantKey(module, fingerprint string) (string, error) { return BuilderBuildKey(module, fingerprint) } // ParseControllerGrantKey is the inverse of ControllerGrantKey; it refuses a want or held key, naming it. func ParseControllerGrantKey(key string) (module, fingerprint string, err error) { if module, fingerprint, err = ParseBuilderBuildKey(key); err != nil { return "", "", fmt.Errorf("delivery: %q is not a grant key, .: %w", key, err) } return module, fingerprint, nil } // ControllerGrant is the value of a grant key: the bus accounts (their public keys) the module's processes were issued // for that fingerprint, none for a module whose grants the tool runner's account carries, so a reissue for another // account is a new revision. The entry's own time says when it was issued. type ControllerGrant struct { V int `json:"v"` Accounts []string `json:"accounts,omitempty"` } func (g ControllerGrant) check() error { for _, a := range g.Accounts { if strings.TrimSpace(a) == "" || strings.ContainsAny(a, " \t\n") { return fmt.Errorf("delivery: a grant names an empty or spaced account %q", a) } } return nil } // EncodeControllerGrant is g as JSON at Version; it refuses an empty or spaced account. func EncodeControllerGrant(g ControllerGrant) ([]byte, error) { if err := g.check(); err != nil { return nil, err } g.V = Version return json.Marshal(g) } // DecodeControllerGrant reads a grant; it refuses an unknown version (ErrUnknownVersion) and an empty or spaced account. func DecodeControllerGrant(data []byte) (ControllerGrant, error) { var g ControllerGrant if err := decode(data, &g); err != nil { return ControllerGrant{}, err } if err := g.check(); err != nil { return ControllerGrant{}, err } return g, nil } // SameControllerGrant says whether two grants are the same value. func SameControllerGrant(a, b ControllerGrant) bool { return reflect.DeepEqual(a, b) } // NamesNode says whether the grant names module's accounts on node: its own `.` or a process's // `..`. A grant that names no account (a module that does not speak on the bus) names every // node. The assigner wants a node only once the grant names its account (issue 1945), and a put waits for it (ADR // 0546 clause 4). func (g ControllerGrant) NamesNode(node, module string) bool { if len(g.Accounts) == 0 { return true } own := node + "." + module for _, a := range g.Accounts { if a == own || strings.HasPrefix(a, own+".") { return true } } return false }