package host import ( "context" "reflect" "strconv" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostguard" ) func (h claimCrossBoxHandover) installRecoveryRemote(ctx context.Context, prior, admitted hostclaim.Observed) error { if h.RecoveryRebind == nil || h.Inventory == nil || h.Guard.FenceView == nil { return hostbox.ErrUnsupported } actor, err := h.Identity.Verify(ctx) if err != nil { return err } stamp, err := strconv.ParseUint(prior.Claim.Op.Journal[hostclaim.JournalSourceStamp], 10, 64) if err != nil || stamp == 0 { return hostbox.ErrChanged } next := admitted.Claim.Holder captured := hostguard.Captured{Incarnation: next.Incarnation, Stamp: stamp} successor := hostguard.Captured{Incarnation: next.Incarnation, Stamp: next.Stamp} for attempt := 0; attempt < 3; attempt++ { source, revision, err := h.Inventory.Snapshot(ctx, prior.Host, captured) if err != nil { return err } var q recoveryRebindRequest err = h.Guard.WithLocal(ctx, prior.Host, func() error { fresh, err := hostclaim.Read(ctx, h.Evidence, prior.Host) if err != nil { return err } if fresh.ETag != admitted.ETag || !hostclaim.SameWriterIdentity(fresh, admitted) { return hostbox.ErrChanged } view, err := h.Guard.FenceView(ctx, prior.Host, captured) if err != nil { return err } if view.Installation.Stamp != stamp && view.Installation.Stamp != next.Stamp || view.Installation.Stamp == stamp && view.Installation.InventoryRevision != revision { return hostbox.ErrChanged } _, etag, err := rootClaimRecord(ctx, h.Evidence, fresh.Claim, h.Guard.DataDir) if err != nil { return err } q = recoveryRebindRequest{V: 1, Prior: captured, Revision: revision, RecordKey: next.Key, RecordETag: etag, Next: hostbox.ColdSuccessor{Host: admitted.Host, Incarnation: next.Incarnation, Stamp: next.Stamp, Session: next.Session, Generation: next.Generation, Box: actor.Box, Enrollment: actor.Enrollment, DirectoryRevision: actor.Binding, ETag: admitted.ETag, Op: admitted.Claim.Op.ID, Phase: admitted.Claim.Op.Phase}} return nil }) if err != nil { return err } if err = h.RecoveryRebind(ctx, q); err != nil { return err } if err = h.Guard.Activate(ctx, admitted.Host, h.Box, nil); err != nil { return err } current, rev, err := h.Inventory.Snapshot(ctx, prior.Host, successor) if err != nil { return err } err = h.Guard.With(ctx, prior.Host, successor, func(c *hostclaim.Claim) error { if !hostclaim.SameWriterIdentity(admitted, hostclaim.Observed{Host: admitted.Host, Claim: c}) { return hostbox.ErrChanged } return nil }, func() error { fresh, err := hostclaim.Read(ctx, h.Evidence, prior.Host) if err != nil { return err } if fresh.ETag != admitted.ETag || !hostclaim.SameWriterIdentity(fresh, admitted) { return hostbox.ErrChanged } view, err := h.Guard.FenceView(ctx, prior.Host, successor) if err != nil { return err } if view.Installation.Stamp != next.Stamp || view.Installation.Incarnation != next.Incarnation || view.Installation.InventoryRevision != rev || rev != 1 || !reflect.DeepEqual(current, recoveryInventory(source, q.Next)) { return hostbox.ErrChanged } _, etag, err := rootClaimRecord(ctx, h.Evidence, fresh.Claim, h.Guard.DataDir) if err != nil { return err } if etag != q.RecordETag { return hostbox.ErrChanged } source.InstallationStamp = next.Stamp owner := "" for _, e := range current.Entries { if e.Kind == "route" { owner = e.Ownership } } if owner == "" { return hostbox.ErrTypedInventory } return h.rebindRecoveryRows(ctx, &source, stamp, owner) }) if err == hostbox.ErrChanged { continue } if err != nil { return err } return nil } return hostbox.ErrChanged }