diff --git a/nsecx.go b/nsecx.go index f8826817b3..8bed0906a8 100644 --- a/nsecx.go +++ b/nsecx.go @@ -93,3 +93,62 @@ func (rr *NSEC3) Match(name string) bool { } return false } + +// compares domains according to the canonical ordering specified in RFC4034 +// returns an integer value similar to strcmp +// (0 for equal values, -1 if s1 < s2, 1 if s1 > s2) +func DomainCompare(s1, s2 string) int { + s1 = strings.ToLower(s1) + s2 = strings.ToLower(s2) + + if s1 == s2 { + return 0 + } + + s1_labels := SplitDomainName(s1) + s2_labels := SplitDomainName(s2) + + if s1_labels == nil { // s1 is root + return -1 + } + if s2_labels == nil { // s2 is root + return 1 + } + + reverse_arr(s1_labels) + reverse_arr(s2_labels) + + s1_label_len := len(s1_labels) + s2_label_len := len(s2_labels) + + var min_label_len int + var default_ret int // based on label count, if the common prefix matches + + if s1_label_len < s2_label_len { + min_label_len = s1_label_len + default_ret = -1 + } else { + min_label_len = s2_label_len + default_ret = 1 + } + + for i := 0; i < min_label_len; i++ { + cmp := strings.Compare(s1_labels[i], s2_labels[i]) + if cmp != 0 { + return cmp + } + } + + return default_ret +} + +// Match returns true if the given name is covered by the NSEC record +func (rr *NSEC) Cover(name string) bool { + return DomainCompare(rr.Hdr.Name, name) <= 0 && DomainCompare(name, rr.NextDomain) == -1 +} + +func reverse_arr(arr []string) { + for i, j := 0, len(arr) - 1; i < j; i, j = i+1, j-1 { + arr[i], arr[j] = arr[j], arr[i] + } +} diff --git a/nsecx_test.go b/nsecx_test.go index ee92653343..b87f7b2aae 100644 --- a/nsecx_test.go +++ b/nsecx_test.go @@ -168,3 +168,51 @@ func BenchmarkHashName(b *testing.B) { }) } } + +func TestDomainCompare(t *testing.T) { + domains := []string{ // from RFC 4034 + "example.", + "a.example.", + "yljkjljk.a.example.", + "Z.a.example.", + "zABC.a.EXAMPLE.", + "z.example.", + "\x01.z.example.", + "*.z.example.", + "\xc8.z.example.", + } + + len_domains := len(domains) + + for i, domain := range domains { + if i != 0 { + prev_domain := domains[i-1] + if !(DomainCompare(prev_domain, domain) == -1 && DomainCompare(domain, prev_domain) == 1) { + t.Fatalf("prev comparison failure between %s and %s", prev_domain, domain) + } + } + + if DomainCompare(domain, domain) != 0 { + t.Fatalf("self comparison failure for %s", domain) + } + + if i != len_domains-1 { + next_domain := domains[i+1] + if !(DomainCompare(domain, next_domain) == -1 && DomainCompare(next_domain, domain) == 1) { + t.Fatalf("next comparison failure between %s and %s, %d and %d", domain, next_domain, DomainCompare(domain, next_domain), DomainCompare(next_domain, domain)) + } + } + } +} + +func TestNsecCover(t *testing.T) { + nsec := testRR("aaa.ee. 3600 IN NSEC aac.ee. NS RRSIG NSEC").(*NSEC) + + if !nsec.Cover("aaaa.ee.") { + t.Fatal("nsec cover positive example failure") + } + + if nsec.Cover("aad.ee.") { + t.Fatal("nsec cover negative example failure") + } +}