package link import ( "bytes" "context" "strings" "testing" "time" "git.novox.be/novox/mesh-sdk/go/declaration" "github.com/nats-io/nats.go" ) // 1695/AC7: (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 declaration-files, 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) store, err := js.ObjectStore(declaration.FileBucket) if err != nil { if store, err = js.CreateObjectStore(&nats.ObjectStoreConfig{Bucket: declaration.FileBucket}); 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} 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) } 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") } }