-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
154 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
// Copyright Chrono Technologies LLC | ||
// SPDX-License-Identifier: MIT | ||
|
||
package arc | ||
|
||
import "crypto/sha256" | ||
|
||
const ( | ||
// Length of the blobID in bytes. | ||
blobIDLen = 32 | ||
) | ||
|
||
// blobID is a 32-byte fixed-length byte array representing the SHA-256 hash of | ||
// a blob value. It is an array and not a slice for map key compatibility. | ||
type blobID [blobIDLen]byte | ||
|
||
// Slice returns the given blobID as a byte slice. | ||
func (id blobID) Slice() []byte { | ||
return id[:] | ||
} | ||
|
||
// blob represents the blob value and its reference count. | ||
type blob struct { | ||
value []byte | ||
refCount int | ||
} | ||
|
||
// blobStore maps blobIDs to their corresponding blobs. It is used to store | ||
// values that exceed the 32-byte value length threshold. | ||
type blobStore map[blobID]*blob | ||
|
||
// get returns the blob that matches the blobID. | ||
func (bs blobStore) get(id blobID) []byte { | ||
b, found := bs[id] | ||
|
||
if !found { | ||
return nil | ||
} | ||
|
||
// Create a copy of the value since returning a pointer to the underlying | ||
// value can have serious implications, such as breaking data integrity. | ||
ret := make([]byte, len(b.value)) | ||
copy(ret, b.value) | ||
|
||
return ret | ||
} | ||
|
||
// put either creates a new blob and inserts it to the blobStore or increments | ||
// the refCount of an existing blob. It returns a blobID on success. | ||
func (bs blobStore) put(value []byte) blobID { | ||
k := blobID(sha256.Sum256(value)) | ||
|
||
if b, found := bs[k]; found { | ||
b.refCount++ | ||
} else { | ||
bs[k] = &blob{value: value, refCount: 1} | ||
} | ||
|
||
return k | ||
} | ||
|
||
// release decrements the refCount of a blob if it exists for the given blobID. | ||
// When the refCount reaches zero, the blob is removed from the blobStore. | ||
func (bs blobStore) release(id blobID) { | ||
if b, found := bs[id]; found { | ||
if b.refCount > 0 { | ||
b.refCount-- | ||
} | ||
|
||
if b.refCount == 0 { | ||
delete(bs, id) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
// Copyright Chrono Technologies LLC | ||
// SPDX-License-Identifier: MIT | ||
|
||
package arc | ||
|
||
import ( | ||
"bytes" | ||
"crypto/sha256" | ||
"testing" | ||
) | ||
|
||
func TestBlobStorePut(t *testing.T) { | ||
store := blobStore{} | ||
|
||
tests := []struct { | ||
value []byte | ||
expectedBlobID blobID | ||
expectedRefCount int | ||
}{ | ||
{[]byte("apple"), sha256.Sum256([]byte("apple")), 1}, | ||
{[]byte("apple"), sha256.Sum256([]byte("apple")), 2}, | ||
{[]byte("apple"), sha256.Sum256([]byte("apple")), 3}, | ||
} | ||
|
||
for _, test := range tests { | ||
blobID := store.put(test.value) | ||
|
||
if !bytes.Equal(blobID.Slice(), test.expectedBlobID.Slice()) { | ||
t.Errorf("unexpected blobID: got:%q, want:%q", blobID, test.expectedBlobID) | ||
} | ||
|
||
value := store.get(blobID) | ||
|
||
if !bytes.Equal(value, test.value) { | ||
t.Errorf("unexpected blob: got:%q, want:%q", value, test.value) | ||
} | ||
|
||
if got := store[blobID].refCount; got != test.expectedRefCount { | ||
t.Errorf("unexpected refCount: got:%d, want:%d", got, test.expectedRefCount) | ||
} | ||
} | ||
} | ||
|
||
func TestBlobStoreRelease(t *testing.T) { | ||
store := blobStore{} | ||
value := []byte("pineapple") | ||
refCount := 20 | ||
|
||
var blobID blobID | ||
|
||
for i := 0; i < refCount; i++ { | ||
blobID = store.put(value) | ||
} | ||
|
||
for i := refCount; i > 0; i-- { | ||
store.release(blobID) | ||
|
||
expectedRefCount := i - 1 | ||
|
||
if expectedRefCount == 0 { | ||
if _, found := store[blobID]; found { | ||
t.Error("expected blob to be removed") | ||
} | ||
} else { | ||
if store[blobID].refCount != expectedRefCount { | ||
t.Errorf("unexpected refCount: got:%d, want:%d", store[blobID].refCount, expectedRefCount) | ||
} | ||
} | ||
} | ||
|
||
// Test that the store does not panic with an unknown key. | ||
store.release(sha256.Sum256([]byte("bogus"))) | ||
|
||
if len(store) != 0 { | ||
t.Error("store should be empty") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
// Copyright Chrono Technologies LLC | ||
// SPDX-License-Identifier: MIT | ||
|
||
package arc | ||
|
||
import "fmt" | ||
|