diff --git a/lns_package_test.go b/lns_package_test.go index 87939e9..beb5623 100644 --- a/lns_package_test.go +++ b/lns_package_test.go @@ -730,6 +730,11 @@ func TestPackageResourceAliases(t *testing.T) { t.Fatal("resource aliases should expose record type helpers from pkg/dns") } + nsec := resource.ToNSEC("Example") + if nsec.Name != "Example." || nsec.NextDomain != "example\x00." || nsec.TTL != DEFAULT_TTL { + t.Fatalf("Resource.ToNSEC returned %+v", nsec) + } + encoded, err := resource.Encode() if err != nil { t.Fatalf("Resource.Encode returned error: %v", err) diff --git a/pkg/dns/resource.go b/pkg/dns/resource.go index 70b0b86..d684e79 100644 --- a/pkg/dns/resource.go +++ b/pkg/dns/resource.go @@ -247,6 +247,30 @@ func (r *Resource) GetHasDS() bool { return r.HasDS() } +// ToNSEC constructs the reference NSEC helper output for the resource. +// +// The bitmap selection matches the JS reference helper: +// - NS-capable resources use TYPE_MAP_NS +// - TXT-only resources use TYPE_MAP_TXT +// - everything else falls back to TYPE_MAP_EMPTY +func (r *Resource) ToNSEC(name string) NSECRecord { + typeMap := TYPE_MAP_EMPTY + if r != nil { + if r.HasNS() { + typeMap = TYPE_MAP_NS + } else if r.HasType(HSTypeTXT) { + typeMap = TYPE_MAP_TXT + } + } + + return Create(name, NextName(name), typeMap) +} + +// GetToNSEC is an alias for ToNSEC. +func (r *Resource) GetToNSEC(name string) NSECRecord { + return r.ToNSEC(name) +} + // Encode serializes the resource payload in the reference binary format. func (r *Resource) Encode() ([]byte, error) { if r == nil { diff --git a/pkg/dns/resource_test.go b/pkg/dns/resource_test.go index 39d889f..f2db18e 100644 --- a/pkg/dns/resource_test.go +++ b/pkg/dns/resource_test.go @@ -242,6 +242,67 @@ func TestResourceTypeHelpers(t *testing.T) { } } +func TestResourceToNSEC(t *testing.T) { + cases := []struct { + name string + record []ResourceRecord + want []byte + }{ + { + name: "empty", + want: TYPE_MAP_EMPTY, + }, + { + name: "txt", + record: []ResourceRecord{ + TXTRecord{Entries: []string{"txt"}}, + }, + want: TYPE_MAP_TXT, + }, + { + name: "ns", + record: []ResourceRecord{ + TXTRecord{Entries: []string{"txt"}}, + NSRecord{NS: "ns1.example."}, + }, + want: TYPE_MAP_NS, + }, + } + + for _, tc := range cases { + resource := NewResource() + resource.Records = append(resource.Records, tc.record...) + + rr := resource.ToNSEC("Example") + if rr.Name != "Example." { + t.Fatalf("%s: ToNSEC name = %q, want %q", tc.name, rr.Name, "Example.") + } + + if rr.NextDomain != "example\x00." { + t.Fatalf("%s: ToNSEC next domain = %q, want %q", tc.name, rr.NextDomain, "example\x00.") + } + + if rr.TTL != DEFAULT_TTL { + t.Fatalf("%s: ToNSEC TTL = %d, want %d", tc.name, rr.TTL, DEFAULT_TTL) + } + + if !bytes.Equal(rr.TypeBitmap, tc.want) { + t.Fatalf("%s: ToNSEC bitmap = %x, want %x", tc.name, rr.TypeBitmap, tc.want) + } + + alias := resource.GetToNSEC("Example") + if alias.Name != rr.Name || alias.NextDomain != rr.NextDomain || alias.TTL != rr.TTL { + t.Fatalf("%s: GetToNSEC should alias ToNSEC, got %+v want %+v", tc.name, alias, rr) + } + } + + var nilResource *Resource + rr := nilResource.ToNSEC("Example") + if !bytes.Equal(rr.TypeBitmap, TYPE_MAP_EMPTY) { + t.Fatalf("nil resource ToNSEC bitmap = %x, want %x", rr.TypeBitmap, TYPE_MAP_EMPTY) + } +} + func TestResourceDecodeStopsAtUnknownType(t *testing.T) { resource := NewResource() resource.Records = []ResourceRecord{TXTRecord{Entries: []string{"known"}}}