Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions inmem/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type MemStore struct {
blobRefs map[claimKey]registry.BlobClaim
intents map[string]registry.UploadIntent // keyed by string(digest)
locations map[locKey]registry.BlobLocation // keyed by (space, digest)
encParams map[locKey]registry.BlobEncryptionParams // keyed by (space, digest)
inclusions map[locKey]registry.BlobInclusion // keyed by (space, digest)
parks map[string]registry.BlobPark // keyed by string(digest)
sessions map[string]registry.MultipartSession // keyed by uploadID
Expand All @@ -67,8 +68,9 @@ type MemStore struct {
revCursor *registry.RevocationCursor // the single revocation_cursor row
}

// claimKey / locKey are the composite map keys for the blob_refs and
// blob_locations tables (digest bytes carried as a string for comparability).
// claimKey / locKey are the composite map keys for the blob_refs and the
// (space, digest)-keyed tables — blob_locations, blob_encryption_params and
// shard_inclusions (digest bytes carried as a string for comparability).
type claimKey struct {
digest, bucket, objectKey, versionID string
}
Expand All @@ -87,6 +89,7 @@ func NewMemStore() *MemStore {
blobRefs: map[claimKey]registry.BlobClaim{},
intents: map[string]registry.UploadIntent{},
locations: map[locKey]registry.BlobLocation{},
encParams: map[locKey]registry.BlobEncryptionParams{},
inclusions: map[locKey]registry.BlobInclusion{},
parks: map[string]registry.BlobPark{},
sessions: map[string]registry.MultipartSession{},
Expand Down
100 changes: 67 additions & 33 deletions inmem/stores.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,29 @@ import (
)

// In-memory implementations of the architecture's relational stores
// (registry.BlobRefStore / IntentStore / LocationStore / MultipartStore /
// GCStore), mirroring the Postgres tables so the in-process suite and
// standalone mode exercise the same write/read/delete code paths.
// (registry.BlobRefStore / IntentStore / LocationStore / EncryptionParamsStore /
// MultipartStore / GCStore), mirroring the Postgres tables so the in-process
// suite and standalone mode exercise the same write/read/delete code paths.

// Compile-time assertions: *MemStore satisfies every store interface.
var (
_ registry.BlobRefStore = (*MemStore)(nil)
_ registry.IntentStore = (*MemStore)(nil)
_ registry.LocationStore = (*MemStore)(nil)
_ registry.EncryptionParamsStore = (*MemStore)(nil)
_ registry.InclusionStore = (*MemStore)(nil)
_ registry.MultipartStore = (*MemStore)(nil)
_ registry.GCStore = (*MemStore)(nil)
_ registry.RevocationCursorStore = (*MemStore)(nil)
)

func cloneBytes(b []byte) []byte {
if b == nil {
return nil
}
return append([]byte(nil), b...)
}

// BlobRefStore ===============================================================
func (m *MemStore) AddBlobClaim(_ context.Context, c registry.BlobClaim) error {
m.mu.Lock()
defer m.mu.Unlock()
k := claimKey{string(c.Digest), c.Bucket, c.ObjectKey, c.VersionID}
cp := c
cp.Digest = cloneBytes(c.Digest)
cp.Digest = bytes.Clone(c.Digest)
m.blobRefs[k] = cp
return nil
}
Expand Down Expand Up @@ -70,7 +64,7 @@ func (m *MemStore) PutIntent(_ context.Context, in registry.UploadIntent) error
m.mu.Lock()
defer m.mu.Unlock()
cp := in
cp.Digest = cloneBytes(in.Digest)
cp.Digest = bytes.Clone(in.Digest)
m.intents[string(in.Digest)] = cp
return nil
}
Expand All @@ -95,7 +89,7 @@ func (m *MemStore) GetIntent(_ context.Context, digest []byte) (*registry.Upload
return nil, registry.ErrNotFound
}
cp := in
cp.Digest = cloneBytes(in.Digest)
cp.Digest = bytes.Clone(in.Digest)
return &cp, nil
}

Expand All @@ -108,7 +102,7 @@ func (m *MemStore) ListIntentsByState(_ context.Context, state string) ([]regist
continue
}
cp := in
cp.Digest = cloneBytes(in.Digest)
cp.Digest = bytes.Clone(in.Digest)
out = append(out, cp)
}
return out, nil
Expand All @@ -126,9 +120,7 @@ func (m *MemStore) DeleteIntent(_ context.Context, digest []byte) error {
func (m *MemStore) PutLocation(_ context.Context, loc registry.BlobLocation) error {
m.mu.Lock()
defer m.mu.Unlock()
cp := loc
cp.Digest = cloneBytes(loc.Digest)
m.locations[locKey{loc.Space, string(loc.Digest)}] = cp
m.locations[locKey{loc.Space, string(loc.Digest)}] = cloneLocation(loc)
return nil
}

Expand All @@ -139,8 +131,7 @@ func (m *MemStore) GetLocation(_ context.Context, space did.DID, digest []byte)
if !ok {
return nil, registry.ErrNotFound
}
cp := loc
cp.Digest = cloneBytes(loc.Digest)
cp := cloneLocation(loc)
return &cp, nil
}

Expand All @@ -151,16 +142,43 @@ func (m *MemStore) DeleteLocation(_ context.Context, space did.DID, digest []byt
return nil
}

// EncryptionParamsStore ======================================================

func (m *MemStore) PutEncryptionParams(_ context.Context, params registry.BlobEncryptionParams) error {
m.mu.Lock()
defer m.mu.Unlock()
m.encParams[locKey{params.Space, string(params.Digest)}] = cloneEncryptionParams(params)
return nil
}

func (m *MemStore) GetEncryptionParams(_ context.Context, space did.DID, digest []byte) (*registry.BlobEncryptionParams, error) {
m.mu.Lock()
defer m.mu.Unlock()
params, ok := m.encParams[locKey{space, string(digest)}]
if !ok {
return nil, registry.ErrNotFound
}
cp := cloneEncryptionParams(params)
return &cp, nil
}

func (m *MemStore) DeleteEncryptionParams(_ context.Context, space did.DID, digest []byte) error {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.encParams, locKey{space, string(digest)})
return nil
}

// ParkStore ==================================================================

func (m *MemStore) PutPark(_ context.Context, p registry.BlobPark) error {
m.mu.Lock()
defer m.mu.Unlock()
cp := p
cp.Digest = cloneBytes(p.Digest)
cp.AddTask = cloneBytes(p.AddTask)
cp.AcceptTask = cloneBytes(p.AcceptTask)
cp.PutInvocation = cloneBytes(p.PutInvocation)
cp.Digest = bytes.Clone(p.Digest)
cp.AddTask = bytes.Clone(p.AddTask)
cp.AcceptTask = bytes.Clone(p.AcceptTask)
cp.PutInvocation = bytes.Clone(p.PutInvocation)
m.parks[string(p.Digest)] = cp
return nil
}
Expand All @@ -173,10 +191,10 @@ func (m *MemStore) GetPark(_ context.Context, digest []byte) (*registry.BlobPark
return nil, registry.ErrNotFound
}
cp := park
cp.Digest = cloneBytes(park.Digest)
cp.AddTask = cloneBytes(park.AddTask)
cp.AcceptTask = cloneBytes(park.AcceptTask)
cp.PutInvocation = cloneBytes(park.PutInvocation)
cp.Digest = bytes.Clone(park.Digest)
cp.AddTask = bytes.Clone(park.AddTask)
cp.AcceptTask = bytes.Clone(park.AcceptTask)
cp.PutInvocation = bytes.Clone(park.PutInvocation)
return &cp, nil
}

Expand All @@ -194,8 +212,8 @@ func (m *MemStore) PutInclusions(_ context.Context, incs []registry.BlobInclusio
defer m.mu.Unlock()
for _, inc := range incs {
cp := inc
cp.Digest = cloneBytes(inc.Digest)
cp.ShardDigest = cloneBytes(inc.ShardDigest)
cp.Digest = bytes.Clone(inc.Digest)
cp.ShardDigest = bytes.Clone(inc.ShardDigest)
m.inclusions[locKey{inc.Space, string(inc.Digest)}] = cp
}
return nil
Expand All @@ -209,8 +227,8 @@ func (m *MemStore) GetInclusion(_ context.Context, space did.DID, digest []byte)
return nil, registry.ErrNotFound
}
cp := inc
cp.Digest = cloneBytes(inc.Digest)
cp.ShardDigest = cloneBytes(inc.ShardDigest)
cp.Digest = bytes.Clone(inc.Digest)
cp.ShardDigest = bytes.Clone(inc.ShardDigest)
return &cp, nil
}

Expand Down Expand Up @@ -406,12 +424,28 @@ func cloneSession(s registry.MultipartSession) registry.MultipartSession {
return s
}

// cloneLocation deep-copies a BlobLocation's digest so the stored copy and any
// returned copy never alias the caller's slice.
func cloneLocation(loc registry.BlobLocation) registry.BlobLocation {
loc.Digest = bytes.Clone(loc.Digest)
return loc
}

// cloneEncryptionParams deep-copies a BlobEncryptionParams' byte-slice fields
// so the stored copy and any returned copy never alias the caller's slices.
func cloneEncryptionParams(p registry.BlobEncryptionParams) registry.BlobEncryptionParams {
p.Digest = bytes.Clone(p.Digest)
p.BaseNonce = bytes.Clone(p.BaseNonce)
p.AAD = bytes.Clone(p.AAD)
return p
}

func clonePart(p registry.MultipartPart) registry.MultipartPart {
p.ETagMD5 = cloneBytes(p.ETagMD5)
p.ETagMD5 = bytes.Clone(p.ETagMD5)
if p.BlobDigests != nil {
ds := make([][]byte, len(p.BlobDigests))
for i, d := range p.BlobDigests {
ds[i] = cloneBytes(d)
ds[i] = bytes.Clone(d)
}
p.BlobDigests = ds
}
Expand Down
126 changes: 126 additions & 0 deletions inmem/stores_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package inmem

import (
"context"
"errors"
"reflect"
"sync"
"sync/atomic"
"testing"
Expand Down Expand Up @@ -254,6 +256,130 @@ func TestLocations_RoundTrip(t *testing.T) {
}
}

// feeParams returns a complete parameter set for space/digest, the shape a FEE
// envelope's row takes.
func feeParams(space did.DID, digest []byte) registry.BlobEncryptionParams {
return registry.BlobEncryptionParams{
Space: space,
Digest: digest,
HeaderLen: 212,
BaseNonce: []byte("nonce07"),
ChunkSize: 65536,
AAD: []byte("cose-enc-structure"),
}
}

func TestEncryptionParams_RoundTrip(t *testing.T) {
ctx := context.Background()
m := NewMemStore()
space := testutil.RandomDID(t)
digest := []byte("enc-digest")
want := feeParams(space, digest)

if err := m.PutEncryptionParams(ctx, want); err != nil {
t.Fatalf("PutEncryptionParams: %v", err)
}
got, err := m.GetEncryptionParams(ctx, space, digest)
if err != nil {
t.Fatalf("GetEncryptionParams: %v", err)
}
if !reflect.DeepEqual(*got, want) {
t.Fatalf("GetEncryptionParams = %+v, want %+v", *got, want)
}
}

// A blob with no row is a plaintext blob, which is how the read path learns not
// to decrypt.
func TestEncryptionParams_MissingIsNotFound(t *testing.T) {
ctx := context.Background()
m := NewMemStore()

_, err := m.GetEncryptionParams(ctx, testutil.RandomDID(t), []byte("absent"))
if !errors.Is(err, registry.ErrNotFound) {
t.Fatalf("GetEncryptionParams err = %v, want ErrNotFound", err)
}
}

func TestEncryptionParams_DeleteRemovesRow(t *testing.T) {
ctx := context.Background()
m := NewMemStore()
space := testutil.RandomDID(t)
digest := []byte("enc-digest")
if err := m.PutEncryptionParams(ctx, feeParams(space, digest)); err != nil {
t.Fatalf("PutEncryptionParams: %v", err)
}

if err := m.DeleteEncryptionParams(ctx, space, digest); err != nil {
t.Fatalf("DeleteEncryptionParams: %v", err)
}
if _, err := m.GetEncryptionParams(ctx, space, digest); !errors.Is(err, registry.ErrNotFound) {
t.Fatalf("GetEncryptionParams after delete err = %v, want ErrNotFound", err)
}
}

func TestEncryptionParams_DeleteIsIdempotent(t *testing.T) {
ctx := context.Background()
m := NewMemStore()

if err := m.DeleteEncryptionParams(ctx, testutil.RandomDID(t), []byte("absent")); err != nil {
t.Fatalf("DeleteEncryptionParams(absent): %v", err)
}
}

// The store must not alias the caller's byte slices, nor let a caller reach
// back into it through a returned copy.
func TestEncryptionParams_NoSliceAliasing(t *testing.T) {
ctx := context.Background()
m := NewMemStore()
space := testutil.RandomDID(t)
digest := []byte("enc-digest")
params := feeParams(space, digest)

if err := m.PutEncryptionParams(ctx, params); err != nil {
t.Fatalf("PutEncryptionParams: %v", err)
}
params.BaseNonce[0] = 'X'
params.AAD[0] = 'X'

got, err := m.GetEncryptionParams(ctx, space, digest)
if err != nil {
t.Fatalf("GetEncryptionParams: %v", err)
}
got.BaseNonce[0] = 'Y'

again, err := m.GetEncryptionParams(ctx, space, digest)
if err != nil {
t.Fatalf("GetEncryptionParams (again): %v", err)
}
if !reflect.DeepEqual(*again, feeParams(space, digest)) {
t.Fatalf("stored params were aliased: %+v", *again)
}
}

// The two tables have independent lifecycles and no cascade between them:
// deleting a location leaves the encryption parameters in place, which is why a
// caller removing a blob must delete both.
func TestEncryptionParams_IndependentOfLocation(t *testing.T) {
ctx := context.Background()
m := NewMemStore()
space := testutil.RandomDID(t)
digest := []byte("enc-digest")
if err := m.PutLocation(ctx, registry.BlobLocation{Space: space, Digest: digest, Provider: "did:piri:1", URL: "http://piri/enc", Size: 4096}); err != nil {
t.Fatalf("PutLocation: %v", err)
}
if err := m.PutEncryptionParams(ctx, feeParams(space, digest)); err != nil {
t.Fatalf("PutEncryptionParams: %v", err)
}

if err := m.DeleteLocation(ctx, space, digest); err != nil {
t.Fatalf("DeleteLocation: %v", err)
}

if _, err := m.GetEncryptionParams(ctx, space, digest); err != nil {
t.Fatalf("DeleteLocation shredded the encryption params: %v", err)
}
}

func TestRevocationCursor_UpsertRoundTrip(t *testing.T) {
ctx := context.Background()
m := NewMemStore()
Expand Down
Loading
Loading