118 lines
4.1 KiB
Go
118 lines
4.1 KiB
Go
package link
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/declaration"
|
|
"github.com/nats-io/nats.go"
|
|
)
|
|
|
|
// 1695/AC7, AC10 (each node reads only the bucket of its own name): (untagged on purpose: the merge check of the trunk runs the tagless suite against its throwaway bus, and the
|
|
// test skips without MESH_TEST_NATS) against a real bus with the bucket of its own node, over the bus's default maximum message size, a
|
|
// node-engine's link reads three objects that together are over 3 MiB by name, verifies each with File.Verify, and
|
|
// refuses an object that is absent, one larger than the declaration says, and one that differs by a byte.
|
|
//
|
|
// MESH_TEST_NATS=nats://127.0.0.1:4222 go test ./internal/link/ -run TestFilesAreFetchedByDigestFromTheBucket
|
|
//
|
|
// The bucket is made here: in the mesh the controller makes and fills it, and the node-engine only reads.
|
|
func TestFilesAreFetchedByDigestFromTheBucket(t *testing.T) {
|
|
conn, js := aBus(t)
|
|
const node = "filesnode"
|
|
bucket, err := declaration.FileBucketFor(node)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
store, err := js.ObjectStore(bucket)
|
|
if err != nil {
|
|
if store, err = js.CreateObjectStore(&nats.ObjectStoreConfig{Bucket: bucket}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
// Another node's bucket holds an object this node's engine must never be able to read, whatever its name.
|
|
otherBucket, _ := declaration.FileBucketFor("othernode")
|
|
other, err := js.ObjectStore(otherBucket)
|
|
if err != nil {
|
|
if other, err = js.CreateObjectStore(&nats.ObjectStoreConfig{Bucket: otherBucket}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
foreign := strings.Repeat("2", 64)
|
|
if _, err := other.PutBytes(foreign, []byte("another node's file")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var files []*declaration.File
|
|
bodies := map[string][]byte{}
|
|
total := 0
|
|
for i, b := range []byte{'a', 'b', 'c'} {
|
|
body := append(bytes.Repeat([]byte{b}, 1100*1024), byte(i))
|
|
total += len(body)
|
|
f := &declaration.File{ID: "m.f" + string(b), Type: declaration.TypeFile, Path: "/x/" + string(b),
|
|
Digest: declaration.DigestOf(body), Size: int64(len(body))}
|
|
name, err := declaration.ObjectName(f.Digest)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := store.PutBytes(name, body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
files, bodies[f.ID] = append(files, f), body
|
|
}
|
|
if total <= 3<<20 {
|
|
t.Fatalf("the objects add up to %d bytes, not over 3 MiB", total)
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
bus := OverNATS{Conn: conn, JS: js, Node: node}
|
|
fetch := FilesFrom(WithFiles(ctx, bus))
|
|
if fetch == nil {
|
|
t.Fatal("a link over the bus carries no fetch")
|
|
}
|
|
for _, f := range files {
|
|
name, _ := declaration.ObjectName(f.Digest)
|
|
got, err := fetch(ctx, name, f.Size)
|
|
if err != nil {
|
|
t.Fatalf("fetching %s: %v", f.ID, err)
|
|
}
|
|
if err := f.Verify(got); err != nil {
|
|
t.Errorf("%s fetched and does not verify: %v", f.ID, err)
|
|
}
|
|
if !bytes.Equal(got, bodies[f.ID]) {
|
|
t.Errorf("%s fetched with other content", f.ID)
|
|
}
|
|
}
|
|
|
|
name, _ := declaration.ObjectName(files[0].Digest)
|
|
if _, err := fetch(ctx, name, files[0].Size-1); err == nil || !strings.Contains(err.Error(), "larger") {
|
|
t.Errorf("an object larger than the declaration says was read: %v", err)
|
|
}
|
|
if _, err := fetch(ctx, foreign, 100); err == nil {
|
|
t.Error("1695/AC10: an object of another node's bucket was read")
|
|
}
|
|
if _, err := (OverNATS{Conn: conn, JS: js, Node: "bad.node"}).FileObject(ctx, name, 10); err == nil {
|
|
t.Error("1695/AC10: a node name that is not a bucket token was taken")
|
|
}
|
|
absent := strings.Repeat("0", 64)
|
|
if _, err := fetch(ctx, absent, 10); err == nil {
|
|
t.Error("an absent object was read")
|
|
}
|
|
// A changed byte is stored under the digest of the original: what the controller never does and an attacker might.
|
|
bad := append([]byte(nil), bodies["m.fa"]...)
|
|
bad[5] ^= 1
|
|
swapped := strings.Repeat("1", 64)
|
|
if _, err := store.PutBytes(swapped, bad); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := fetch(ctx, swapped, files[0].Size)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := files[0].Verify(got); err == nil {
|
|
t.Error("an object that differs by a byte verified")
|
|
}
|
|
}
|