package host import ( "context" "os" "sync" "time" "github.com/carlosframework/platform/internal/lease" "github.com/carlosframework/platform/internal/moves" ) // guardedMirror is the one path every staging write takes (moves spec ยง7.3, // model 14 and 18): each mutation of the target bucket calls Guarded.Before // and is signed at the instant the guard checked the staging lease, through // a lease.StagingWriter whose At methods sign once and never retry. A // caller that wants another attempt calls again and gets a new check and a // new signature. // // It is a lease.MirrorStore, a VersionedCopier and a VersionedPutter, so // lease.MirrorPrefixKeep runs over it in its versioned mode. Writes to any // other bucket are the source's own receipts and ledger in its own bucket: // they are not staging, need no guard, and go through own, the ordinary // mirror client. Reads go through the staging writer's ordinary reads. // Nothing here ever calls a lease.VersionedStore against the target bucket. type guardedMirror struct { w lease.StagingWriter g moves.Guarded dst string // the target bucket: the only bucket the guard covers own lease.MirrorStore // the source's own bucket: receipts and the ledger // lastSign is the latest signing time the guard handed out through // this mirror: the cut's seal bound (ruling G7) is computed from it. mu sync.Mutex lastSign time.Time } // before is Guarded.Before, recording the signing time it hands out. func (m *guardedMirror) before(fn func(signAt time.Time) error) error { return m.g.Before(func(signAt time.Time) error { m.mu.Lock() if signAt.After(m.lastSign) { m.lastSign = signAt } m.mu.Unlock() return fn(signAt) }) } // lastSigned is the latest signing time handed out, or zero when none was: // a request signed then can land until lastSigned + moves.SignatureLife. func (m *guardedMirror) lastSigned() time.Time { m.mu.Lock() defer m.mu.Unlock() return m.lastSign } var ( _ lease.MirrorStore = (*guardedMirror)(nil) _ lease.VersionedCopier = (*guardedMirror)(nil) _ lease.VersionedPutter = (*guardedMirror)(nil) ) func (m *guardedMirror) List(ctx context.Context, bucket, prefix string) (map[string]lease.MirrorObject, error) { return m.w.List(ctx, bucket, prefix) } func (m *guardedMirror) Get(ctx context.Context, bucket, key string) ([]byte, error) { if bucket != m.dst { return m.own.Get(ctx, bucket, key) } return m.w.Get(ctx, bucket, key) } func (m *guardedMirror) CopyVersioned(ctx context.Context, srcBucket, srcKey, srcETag, dstBucket, dstKey string) (etag, versionID string, err error) { err = m.before(func(signAt time.Time) error { var cerr error etag, versionID, cerr = m.w.CopyVersionedAt(ctx, signAt, srcBucket, srcKey, srcETag, dstBucket, dstKey) return cerr }) return etag, versionID, err } // Copy is CopyVersioned without the version. MirrorPrefixKeep never calls // it on a VersionedCopier; it exists so the type is a MirrorStore. func (m *guardedMirror) Copy(ctx context.Context, srcBucket, srcKey, srcETag, dstBucket, dstKey string) (string, error) { etag, _, err := m.CopyVersioned(ctx, srcBucket, srcKey, srcETag, dstBucket, dstKey) return etag, err } func (m *guardedMirror) PutVersioned(ctx context.Context, bucket, key string, body []byte) (versionID string, err error) { err = m.before(func(signAt time.Time) error { var perr error versionID, perr = m.w.PutVersionedAt(ctx, signAt, bucket, key, body) return perr }) return versionID, err } // PutFileVersioned stages the file at path (size bytes, sha256Hex) as key, // read from disk inside the guarded call: a retry opens it again under a // new check. func (m *guardedMirror) PutFileVersioned(ctx context.Context, bucket, key, path string, size int64, sha256Hex string) (versionID string, err error) { err = m.before(func(signAt time.Time) error { f, oerr := os.Open(path) if oerr != nil { return oerr } defer f.Close() var perr error versionID, perr = m.w.PutReaderVersionedAt(ctx, signAt, bucket, key, f, size, sha256Hex) return perr }) return versionID, err } // PutIfAbsentVersioned is move.json's create-only write; an existing object // is lease.ErrExists. func (m *guardedMirror) PutIfAbsentVersioned(ctx context.Context, bucket, key string, body []byte) (versionID string, err error) { err = m.before(func(signAt time.Time) error { var perr error versionID, perr = m.w.PutIfAbsentVersionedAt(ctx, signAt, bucket, key, body) return perr }) return versionID, err } func (m *guardedMirror) Put(ctx context.Context, bucket, key string, body []byte) error { if bucket != m.dst { return m.own.Put(ctx, bucket, key, body) } _, err := m.PutVersioned(ctx, bucket, key, body) return err } func (m *guardedMirror) Delete(ctx context.Context, bucket, key string) error { if bucket != m.dst { return m.own.Delete(ctx, bucket, key) } return m.before(func(signAt time.Time) error { return m.w.DeleteAt(ctx, signAt, bucket, key) }) }