package host import ( "context" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostclaim" ) type claimRecoveryJournal struct { Prior hostclaim.Observed Generation string Sessions []uint64 } func claimRecoveryJournalID(o hostclaim.Observed) string { h := o.Claim.Holder sum := sha256.Sum256([]byte(fmt.Sprintf("%s\x00%s\x00%d\x00%d\x00%s", o.Host, h.Incarnation, h.Stamp, h.Session, o.Claim.Op.ID))) return hex.EncodeToString(sum[:]) } func (j claimRecoveryJournal) successor(o hostclaim.Observed, box string) bool { if j.Prior.Claim == nil || j.Prior.Claim.Holder == nil || o.Claim == nil || o.Claim.Holder == nil { return false } p, c := j.Prior.Claim, o.Claim a, b := p.Holder, c.Holder if j.Prior.Host != o.Host || a.Incarnation != b.Incarnation || p.Op.ID != c.Op.ID || p.Op.Kind != c.Op.Kind || p.Op.Phase != c.Op.Phase || p.Op.Exec != c.Op.Exec || c.Op.Exec != box || b.WriterBox != box || b.Session <= a.Session || b.Stamp != b.Session || b.Generation != j.Generation || len(b.Admissions) == 0 { return false } allowed := false for _, session := range j.Sessions { if session == b.Session { allowed = true break } } if !allowed { return false } last := b.Admissions[len(b.Admissions)-1] return last.Session == b.Session && last.Box == box && last.Generation == j.Generation && last.Mode == "cold" && last.Predecessor == recoveryPredecessor(j.Prior).Generation } func (h claimCrossBoxHandover) recoveryJournal(ctx context.Context, o hostclaim.Observed) (string, string, *claimRecoveryJournal, error) { if h.Registry == nil { return "", "", nil, hostbox.ErrTypedInventory } bodies, err := h.Registry.PendingRecoveryHandovers(ctx) if err != nil { return "", "", nil, err } var found *claimRecoveryJournal var id, body string for key, raw := range bodies { var j claimRecoveryJournal if json.Unmarshal([]byte(raw), &j) != nil || j.Prior.Claim == nil || j.Prior.Claim.Holder == nil || j.Generation == "" { return "", "", nil, hostbox.ErrChanged } if j.Prior.Host != o.Host { continue } if found != nil || key != claimRecoveryJournalID(j.Prior) || (!hostclaim.SameWriterIdentity(j.Prior, o) && !j.successor(o, h.Box)) { return "", "", nil, hostbox.ErrChanged } found, id, body = &j, key, raw } return id, body, found, nil } func (h claimCrossBoxHandover) prepareRecovery(ctx context.Context, o hostclaim.Observed, in hostclaim.Admission) (hostclaim.Admission, error) { if !claimInitialSourceRecovery(o, h.Box) && !(claimRecoveryObservation(o) && claimRemotePredecessor(o, h.Box)) { return in, nil } if err := h.recoveryEvidence(ctx, o); err != nil { return in, err } id, body, j, err := h.recoveryJournal(ctx, o) if err != nil { return in, err } if j != nil { if !hostclaim.SameWriterIdentity(j.Prior, o) { return in, hostbox.ErrChanged } in.NewGeneration = j.Generation session := o.Claim.Gen + 1 for _, existing := range j.Sessions { if existing == session { return in, nil } } j.Sessions = append(j.Sessions, session) raw, err := json.Marshal(j) if err != nil { return in, err } return in, h.Registry.ReviseRecoveryHandover(ctx, id, body, string(raw)) } if in.Mode != "cold" || in.KeepStamp || in.NewGeneration == "" { return in, hostbox.ErrChanged } raw, err := json.Marshal(claimRecoveryJournal{Prior: o, Generation: in.NewGeneration, Sessions: []uint64{o.Claim.Gen + 1}}) if err != nil { return in, err } return in, h.Registry.BeginRecoveryHandover(ctx, claimRecoveryJournalID(o), string(raw)) } func (h claimCrossBoxHandover) pendingRecovery(ctx context.Context, o hostclaim.Observed) (hostclaim.Observed, bool, error) { if !claimRecoveryObservation(o) || o.Claim.Holder == nil || h.Registry == nil { return o, false, nil } id, body, j, err := h.recoveryJournal(ctx, o) if err != nil || j == nil { return o, false, err } if hostclaim.SameWriterIdentity(j.Prior, o) { return o, false, nil } if h.Launches == nil { return o, false, hostbox.ErrTypedInventory } actor, err := h.Identity.Verify(ctx) if err != nil { return o, false, err } next := o.Claim.Holder launches, err := h.Launches.SessionLaunches(ctx, o.Host, next.Incarnation, next.Stamp, next.Session, h.Box, actor.Enrollment) if err != nil { return o, false, err } if len(launches) > 0 { return o, false, h.Registry.CompleteRecoveryHandover(ctx, id, body) } return j.Prior, true, nil } func (h claimCrossBoxHandover) completeRecovery(ctx context.Context, o hostclaim.Observed) error { if !claimRecoveryObservation(o) || o.Claim.Holder == nil || h.Registry == nil { return nil } id, body, j, err := h.recoveryJournal(ctx, o) if err != nil || j == nil { return err } if !j.successor(o, h.Box) { return hostbox.ErrChanged } return h.Registry.CompleteRecoveryHandover(ctx, id, body) }