package release import ( "encoding/json" "math" "os" "strings" "time" "github.com/carlosframework/platform/internal/rootstate" ) const rootBakeKey = "bake.json" type rootBakeRecord struct { Schema int `json:"schema"` Revision uint64 `json:"revision"` Complete map[string]uint64 `json:"complete"` Observed time.Time `json:"observed"` Clock FirstSeen `json:"clock"` } func validRootBake(r rootBakeRecord) bool { if r.Schema != 1 || r.Revision == 0 || r.Observed.IsZero() || r.Complete == nil || r.Clock.Channels == nil || math.IsNaN(r.Clock.Jitter) || r.Clock.Jitter < 0 || r.Clock.Jitter >= .25 { return false } for key, revision := range r.Complete { parts := strings.Split(key, clockSep) if len(parts) != 2 || !ValidAccount(parts[0]) || !ValidApp(parts[1]) || revision == 0 || revision > r.Revision { return false } } for _, versions := range r.Clock.Channels { if versions == nil || len(versions) > firstSeenKeep { return false } for version, at := range versions { if !ValidVersion(version) || at.IsZero() || at.After(r.Observed) { return false } } } for _, mem := range r.Clock.Production { if mem == nil || mem.OffFirstSeen.After(r.Observed) || mem.OffSetAt.IsZero() != mem.OffFirstSeen.IsZero() { return false } } for _, mem := range r.Clock.Holds { if mem == nil || mem.Window < 0 || mem.Pending < 0 || mem.PendingAt.After(r.Observed) || mem.DeletedAt.After(r.Observed) { return false } } return true } func (h *RootHistory) writeBake(r rootBakeRecord) error { if h == nil || h.Store == nil || !validRootBake(r) { return os.ErrPermission } raw, err := json.Marshal(r) if err != nil { return err } if len(raw) > rootstate.MaxRecord { return os.ErrPermission } return h.Store.Write(rootBakeKey, raw) } func (h *RootHistory) LoadBake(bindings []HistoryBinding, now time.Time) (*FirstSeen, error) { if err := h.reconstructionReady(); err != nil { return nil, err } if h == nil || h.Store == nil || now.IsZero() { return nil, os.ErrPermission } raw, err := h.Store.Read(rootBakeKey) if err != nil { return nil, err } var r rootBakeRecord if len(raw) > rootstate.MaxRecord || strictCanonicalJSON(raw, &r) != nil || !validRootBake(r) || now.Before(r.Observed) { return nil, os.ErrPermission } for _, b := range bindings { if !b.valid() || r.Complete[prodKey(b.Account, b.App)] == 0 { return nil, os.ErrPermission } } if err := h.Store.Write(rootBakeKey, raw); err != nil { return nil, err } clock := r.Clock clock.migrated = true clock.save = func() error { if r.Revision == ^uint64(0) { return os.ErrPermission } r.Revision++ r.Observed, r.Clock = now.UTC(), clock for key := range r.Complete { r.Complete[key] = r.Revision } return h.writeBake(r) } return &clock, nil }