aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--middleware/canonical.go57
-rw-r--r--middleware/canonical_test.go72
2 files changed, 129 insertions, 0 deletions
diff --git a/middleware/canonical.go b/middleware/canonical.go
new file mode 100644
index 000000000..5d51231c2
--- /dev/null
+++ b/middleware/canonical.go
@@ -0,0 +1,57 @@
+package middleware
+
+import (
+ "bytes"
+
+ "github.com/miekg/dns"
+)
+
+// Less returns <0 when a is less than b, 0 when they are equal and
+// >0 when a is larger than b.
+// The function order names in DNSSEC canonical order.
+//
+// See http://bert-hubert.blogspot.co.uk/2015/10/how-to-do-fast-canonical-ordering-of.html
+// for a blog article on how we do this. And https://tools.ietf.org/html/rfc4034#section-6.1 .
+func Less(a, b string) int {
+ i := 1
+ aj := len(a)
+ bj := len(b)
+ for {
+ ai, _ := dns.PrevLabel(a, i)
+ bi, _ := dns.PrevLabel(b, i)
+ // sadly this []byte will allocate...
+ ab := []byte(a[ai:aj])
+ toLowerAndDDD(ab)
+ bb := []byte(b[bi:bj])
+ toLowerAndDDD(bb)
+
+ res := bytes.Compare(ab, bb)
+ if res != 0 {
+ return res
+ }
+
+ i++
+ aj, bj = ai, bi
+ }
+ return 0
+}
+
+func toLowerAndDDD(b []byte) {
+ lb := len(b)
+ for i := 0; i < lb; i++ {
+ if b[i] >= 'A' && b[i] <= 'Z' {
+ b[i] += 32
+ continue
+ }
+ if i+3 < lb && b[i] == '\\' && isDigit(b[i+1]) && isDigit(b[i+2]) && isDigit(b[i+3]) {
+ b[i] = dddToByte(b[i:])
+ for j := i + 1; j < lb-3; j++ {
+ b[j] = b[j+3]
+ }
+ lb -= 3
+ }
+ }
+}
+
+func isDigit(b byte) bool { return b >= '0' && b <= '9' }
+func dddToByte(s []byte) byte { return byte((s[1]-'0')*100 + (s[2]-'0')*10 + (s[3] - '0')) }
diff --git a/middleware/canonical_test.go b/middleware/canonical_test.go
new file mode 100644
index 000000000..390878a67
--- /dev/null
+++ b/middleware/canonical_test.go
@@ -0,0 +1,72 @@
+package middleware
+
+import (
+ "sort"
+ "testing"
+)
+
+type set []string
+
+func (p set) Len() int { return len(p) }
+func (p set) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
+func (p set) Less(i, j int) bool { d := Less(p[i], p[j]); return d <= 0 }
+
+func TestLess(t *testing.T) {
+ tests := []struct {
+ in []string
+ out []string
+ }{
+ {
+ []string{"aaa.powerdns.de", "bbb.powerdns.net.", "xxx.powerdns.com."},
+ []string{"xxx.powerdns.com.", "aaa.powerdns.de", "bbb.powerdns.net."},
+ },
+ {
+ []string{"aaa.POWERDNS.de", "bbb.PoweRdnS.net.", "xxx.powerdns.com."},
+ []string{"xxx.powerdns.com.", "aaa.POWERDNS.de", "bbb.PoweRdnS.net."},
+ },
+ {
+ []string{"aaa.aaaa.aa.", "aa.aaa.a.", "bbb.bbbb.bb."},
+ []string{"aa.aaa.a.", "aaa.aaaa.aa.", "bbb.bbbb.bb."},
+ },
+ {
+ []string{"aaaaa.", "aaa.", "bbb."},
+ []string{"aaa.", "aaaaa.", "bbb."},
+ },
+ {
+ []string{"a.a.a.a.", "a.a.", "a.a.a."},
+ []string{"a.a.", "a.a.a.", "a.a.a.a."},
+ },
+ {
+ []string{"example.", "z.example.", "a.example."},
+ []string{"example.", "a.example.", "z.example."},
+ },
+ {
+ []string{"a.example.", "Z.a.example.", "z.example.", "yljkjljk.a.example.", "\\001.z.example.", "example.", "*.z.example.", "\\200.z.example.", "zABC.a.EXAMPLE."},
+ []string{"example.", "a.example.", "yljkjljk.a.example.", "Z.a.example.", "zABC.a.EXAMPLE.", "z.example.", "\\001.z.example.", "*.z.example.", "\\200.z.example."},
+ },
+ {
+ // RFC3034 example.
+ []string{"a.example.", "Z.a.example.", "z.example.", "yljkjljk.a.example.", "example.", "*.z.example.", "zABC.a.EXAMPLE."},
+ []string{"example.", "a.example.", "yljkjljk.a.example.", "Z.a.example.", "zABC.a.EXAMPLE.", "z.example.", "*.z.example."},
+ },
+ }
+
+Tests:
+ for j, test := range tests {
+ sort.Sort(set(test.in))
+ for i := 0; i < len(test.in); i++ {
+ if test.in[i] != test.out[i] {
+ t.Errorf("test %d: expected %s, got %s\n", j, test.out[i], test.in[i])
+ n := ""
+ for k, in := range test.in {
+ if k+1 == len(test.in) {
+ n = "\n"
+ }
+ t.Logf("%s <-> %s\n%s", in, test.out[k], n)
+ }
+ continue Tests
+ }
+
+ }
+ }
+}