package locator_test import ( "bytes" "encoding/hex" "errors" "path/filepath" "slices" "testing" datekeys "g.activething.com/go/DateKeys" "g.activething.com/go/DateKeys/extension" "g.activething.com/go/DateKeys/internal/testkit" "g.activething.com/go/DateKeys/locator" "g.activething.com/go/DateKeys/profile" ) func unhex(t *testing.T, s string) []byte { t.Helper() b, err := hex.DecodeString(s) if err != nil { t.Fatal(err) } return b } // testdata/vectors/locator.json is frozen: the extension of the .dkk reads, // the sealed locator opens with the release of its round, the rest is found // in the host at its offset, the envelope gives the .dkc back, and the rules // of the addresses and of the padding give what the file says (spec v0.12, // §44.1). So do its cases of §64: a locator whose addresses a reader rejects // and uses in turn, the resources of the rest, the data of the extension and // the plaintexts of the locator. func TestLocatorVectors(t *testing.T) { var v testkit.LocatorVectorFile if err := testkit.ReadJSON(filepath.Join("..", "testdata", "vectors", "locator.json"), &v); err != nil { t.Fatal(err) } p := profile.Quicknet() info, err := locator.ParseInfo(extension.Extension{ID: extension.CapsuleID, Version: 1, Data: unhex(t, v.Extension)}) if err != nil || info.Note != v.Note || info.DateKey.Compact() != v.DateKey || info.DateKey.Round != v.Round || !bytes.Equal(info.Sealed, unhex(t, v.Sealed)) { t.Fatalf("the extension: %+v, %v", info, err) } loc, err := locator.Open(p, v.Round, testkit.Release(v.Round), unhex(t, v.Sealed)) if err != nil { t.Fatal(err) } plain, err := loc.Marshal() if err != nil || !bytes.Equal(plain, unhex(t, v.Plaintext)) || len(plain) != locator.Block { t.Fatalf("the plaintext of the locator: %d bytes, %v", len(plain), err) } if hex.EncodeToString(loc.EnvelopeKey[:]) != v.EnvelopeKey || hex.EncodeToString(loc.RestDigest[:]) != v.RestDigest || loc.RestSize != v.RestSize || hex.EncodeToString(loc.CapsuleDigest[:]) != v.CapsuleDigest || hex.EncodeToString(loc.EnvelopeHeader) != v.Header || len(loc.Addresses) != len(v.Addresses) { t.Fatalf("the fields of the locator: %+v", loc) } for i, a := range v.Addresses { if loc.Addresses[i].URI != a.URI || loc.Addresses[i].Offset != a.Offset || loc.Addresses[i].Host() != a.Host { t.Errorf("address %d: %+v", i, loc.Addresses[i]) } } // The rest, from the host at the offset of the first address, opens the envelope. rest, err := loc.RestIn(unhex(t, v.Host), v.HostOffset) if err != nil || !bytes.Equal(rest, unhex(t, v.Rest)) { t.Fatalf("the rest in the host: %v", err) } dkc, err := loc.OpenEnvelope(rest) if err != nil || !bytes.Equal(dkc, unhex(t, v.DKC)) { t.Fatalf("the envelope: %v", err) } for _, c := range v.PaddingCases { if got := locator.PlaintextLength(c.Base); got != c.Total || got%locator.Block != 0 { t.Errorf("padding of %d: %d, want %d", c.Base, got, c.Total) } } for _, c := range v.URICases { if got := locator.CheckURI(c.URI) == nil; got != c.OK { t.Errorf("%q: accepted %v, want %v", c.URI, got, c.OK) } } // The mixed locator reads: a reader rejects the addresses that break the // rules and uses the other, which finds the rest in the host. want := unhex(t, v.DKC) mixed, err := locator.Open(p, v.Round, testkit.Release(v.Round), unhex(t, v.Mixed.Sealed)) if err != nil { t.Fatal(err) } if back, err := locator.Unmarshal(unhex(t, v.Mixed.Plaintext)); err != nil || len(back.Addresses) != len(v.Mixed.Addresses) || len(mixed.Addresses) != len(v.Mixed.Addresses) { t.Fatalf("the mixed locator: %v", err) } var usable []locator.Address for i, a := range v.Mixed.Addresses { if mixed.Addresses[i] != (locator.Address{URI: a.URI, Offset: a.Offset}) { t.Errorf("mixed address %d: %+v", i, mixed.Addresses[i]) } if a.Usable { usable = append(usable, mixed.Addresses[i]) } } if got := mixed.Usable(); len(usable) == 0 || !slices.Equal(got, usable) { t.Fatalf("usable: %v, want %v", got, usable) } if rest, err = mixed.RestIn(unhex(t, v.Host), usable[0].Offset); err != nil { t.Fatal(err) } if dkc, err := mixed.OpenEnvelope(rest); err != nil || !bytes.Equal(dkc, want) { t.Fatalf("the envelope of the mixed locator: %v", err) } // A reader reads RestSize bytes from the offset, whatever follows, and // uses them only when their SHA-256 is resto_digest. for _, c := range v.RestCases { opens := false if r, err := loc.RestIn(unhex(t, c.Resource), c.Offset); err == nil { dkc, err := loc.OpenEnvelope(r) opens = err == nil && bytes.Equal(dkc, want) } if opens != c.Opens { t.Errorf("%s: opens %v, want %v", c.Name, opens, c.Opens) } } // An extension that a reader cannot use carries only // ERR_EXTENSION_DATA_INVALID (spec §54). for _, c := range v.ExtensionCases { _, err := locator.ParseInfo(extension.Extension{ID: extension.CapsuleID, Version: 1, Data: unhex(t, c.Data)}) if (err == nil) != c.OK || err != nil && !errors.Is(err, datekeys.ErrExtensionDataInvalid) { t.Errorf("%s: %v", c.Name, err) } } for _, c := range v.PlaintextCases { if _, err := locator.Unmarshal(unhex(t, c.Plaintext)); (err == nil) != c.OK { t.Errorf("%s: %v", c.Name, err) } } }