Commit 22ae77b7 authored by Ian Gudger's avatar Ian Gudger Committed by Brad Fitzpatrick

dns/dnsmessage: change Builder to append and update documentation

The new appending behavior is required for an efficient DNS client.

(*Builder).Start was intended to append, but didn't. This was a mistake
(all tests and examples assumed that it did).

In addition, message compression when appending has been fixed.

Updates golang/go#16218

Change-Id: I3f42aa6e653e2990fa90368a2803e588ea15b85a
Reviewed-on: https://go-review.googlesource.com/97716Reviewed-by: 's avatarBrad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent cbe0f930
This diff is collapsed.
...@@ -62,7 +62,7 @@ func TestQuestionPackUnpack(t *testing.T) { ...@@ -62,7 +62,7 @@ func TestQuestionPackUnpack(t *testing.T) {
Type: TypeA, Type: TypeA,
Class: ClassINET, Class: ClassINET,
} }
buf, err := want.pack(make([]byte, 1, 50), map[string]int{}) buf, err := want.pack(make([]byte, 1, 50), map[string]int{}, 1)
if err != nil { if err != nil {
t.Fatal("Packing failed:", err) t.Fatal("Packing failed:", err)
} }
...@@ -129,7 +129,7 @@ func TestNamePackUnpack(t *testing.T) { ...@@ -129,7 +129,7 @@ func TestNamePackUnpack(t *testing.T) {
for _, test := range tests { for _, test := range tests {
in := mustNewName(test.in) in := mustNewName(test.in)
want := mustNewName(test.want) want := mustNewName(test.want)
buf, err := in.pack(make([]byte, 0, 30), map[string]int{}) buf, err := in.pack(make([]byte, 0, 30), map[string]int{}, 0)
if err != test.err { if err != test.err {
t.Errorf("Packing of %q: got err = %v, want err = %v", test.in, err, test.err) t.Errorf("Packing of %q: got err = %v, want err = %v", test.in, err, test.err)
continue continue
...@@ -248,6 +248,40 @@ func TestDNSPackUnpack(t *testing.T) { ...@@ -248,6 +248,40 @@ func TestDNSPackUnpack(t *testing.T) {
} }
} }
func TestDNSAppendPackUnpack(t *testing.T) {
wants := []Message{
{
Questions: []Question{
{
Name: mustNewName("."),
Type: TypeAAAA,
Class: ClassINET,
},
},
Answers: []Resource{},
Authorities: []Resource{},
Additionals: []Resource{},
},
largeTestMsg(),
}
for i, want := range wants {
b := make([]byte, 2, 514)
b, err := want.AppendPack(b)
if err != nil {
t.Fatalf("%d: packing failed: %v", i, err)
}
b = b[2:]
var got Message
err = got.Unpack(b)
if err != nil {
t.Fatalf("%d: unpacking failed: %v", i, err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("%d: got = %+v, want = %+v", i, &got, &want)
}
}
}
func TestSkipAll(t *testing.T) { func TestSkipAll(t *testing.T) {
msg := largeTestMsg() msg := largeTestMsg()
buf, err := msg.Pack() buf, err := msg.Pack()
...@@ -412,7 +446,7 @@ func TestVeryLongTxt(t *testing.T) { ...@@ -412,7 +446,7 @@ func TestVeryLongTxt(t *testing.T) {
}, },
&TXTResource{loremIpsum}, &TXTResource{loremIpsum},
} }
buf, err := want.pack(make([]byte, 0, 8000), map[string]int{}) buf, err := want.pack(make([]byte, 0, 8000), map[string]int{}, 0)
if err != nil { if err != nil {
t.Fatal("Packing failed:", err) t.Fatal("Packing failed:", err)
} }
...@@ -434,6 +468,26 @@ func TestVeryLongTxt(t *testing.T) { ...@@ -434,6 +468,26 @@ func TestVeryLongTxt(t *testing.T) {
} }
} }
func TestStartAppends(t *testing.T) {
buf := make([]byte, 2, 514)
wantBuf := []byte{4, 44}
copy(buf, wantBuf)
b := NewBuilder(buf, Header{})
b.EnableCompression()
buf, err := b.Finish()
if err != nil {
t.Fatal("Building failed:", err)
}
if got, want := len(buf), headerLen+2; got != want {
t.Errorf("Got len(buf} = %d, want = %d", got, want)
}
if string(buf[:2]) != string(wantBuf) {
t.Errorf("Original data not preserved, got = %v, want = %v", buf[:2], wantBuf)
}
}
func TestStartError(t *testing.T) { func TestStartError(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
...@@ -514,8 +568,8 @@ func TestBuilder(t *testing.T) { ...@@ -514,8 +568,8 @@ func TestBuilder(t *testing.T) {
t.Fatal("Packing without builder:", err) t.Fatal("Packing without builder:", err)
} }
var b Builder b := NewBuilder(nil, msg.Header)
b.Start(nil, msg.Header) b.EnableCompression()
if err := b.StartQuestions(); err != nil { if err := b.StartQuestions(); err != nil {
t.Fatal("b.StartQuestions():", err) t.Fatal("b.StartQuestions():", err)
...@@ -653,9 +707,7 @@ func TestResourcePack(t *testing.T) { ...@@ -653,9 +707,7 @@ func TestResourcePack(t *testing.T) {
} }
} }
func BenchmarkParsing(b *testing.B) { func benchmarkParsingSetup() ([]byte, error) {
b.ReportAllocs()
name := mustNewName("foo.bar.example.com.") name := mustNewName("foo.bar.example.com.")
msg := Message{ msg := Message{
Header: Header{Response: true, Authoritative: true}, Header: Header{Response: true, Authoritative: true},
...@@ -700,111 +752,148 @@ func BenchmarkParsing(b *testing.B) { ...@@ -700,111 +752,148 @@ func BenchmarkParsing(b *testing.B) {
buf, err := msg.Pack() buf, err := msg.Pack()
if err != nil { if err != nil {
b.Fatal("msg.Pack():", err) return nil, fmt.Errorf("msg.Pack(): %v", err)
} }
return buf, nil
}
for i := 0; i < b.N; i++ { func benchmarkParsing(tb testing.TB, buf []byte) {
var p Parser var p Parser
if _, err := p.Start(buf); err != nil { if _, err := p.Start(buf); err != nil {
b.Fatal("p.Start(buf):", err) tb.Fatal("p.Start(buf):", err)
}
for {
_, err := p.Question()
if err == ErrSectionDone {
break
}
if err != nil {
tb.Fatal("p.Question():", err)
} }
}
for { for {
_, err := p.Question() h, err := p.AnswerHeader()
if err == ErrSectionDone { if err == ErrSectionDone {
break break
} }
if err != nil { if err != nil {
b.Fatal("p.Question():", err) panic(err)
}
} }
for { switch h.Type {
h, err := p.AnswerHeader() case TypeA:
if err == ErrSectionDone { if _, err := p.AResource(); err != nil {
break tb.Fatal("p.AResource():", err)
} }
if err != nil { case TypeAAAA:
panic(err) if _, err := p.AAAAResource(); err != nil {
tb.Fatal("p.AAAAResource():", err)
} }
case TypeCNAME:
switch h.Type { if _, err := p.CNAMEResource(); err != nil {
case TypeA: tb.Fatal("p.CNAMEResource():", err)
if _, err := p.AResource(); err != nil {
b.Fatal("p.AResource():", err)
}
case TypeAAAA:
if _, err := p.AAAAResource(); err != nil {
b.Fatal("p.AAAAResource():", err)
}
case TypeCNAME:
if _, err := p.CNAMEResource(); err != nil {
b.Fatal("p.CNAMEResource():", err)
}
case TypeNS:
if _, err := p.NSResource(); err != nil {
b.Fatal("p.NSResource():", err)
}
default:
b.Fatalf("unknown type: %T", h)
} }
case TypeNS:
if _, err := p.NSResource(); err != nil {
tb.Fatal("p.NSResource():", err)
}
default:
tb.Fatalf("unknown type: %T", h)
} }
} }
} }
func BenchmarkBuilding(b *testing.B) { func BenchmarkParsing(b *testing.B) {
buf, err := benchmarkParsingSetup()
if err != nil {
b.Fatal(err)
}
b.ReportAllocs() b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchmarkParsing(b, buf)
}
}
func TestParsingAllocs(t *testing.T) {
buf, err := benchmarkParsingSetup()
if err != nil {
t.Fatal(err)
}
if allocs := testing.AllocsPerRun(100, func() { benchmarkParsing(t, buf) }); allocs > 0.5 {
t.Errorf("Allocations during parsing: got = %f, want ~0", allocs)
}
}
func benchmarkBuildingSetup() (Name, []byte) {
name := mustNewName("foo.bar.example.com.") name := mustNewName("foo.bar.example.com.")
buf := make([]byte, 0, packStartingCap) buf := make([]byte, 0, packStartingCap)
return name, buf
}
for i := 0; i < b.N; i++ { func benchmarkBuilding(tb testing.TB, name Name, buf []byte) {
var bld Builder bld := NewBuilder(buf, Header{Response: true, Authoritative: true})
bld.StartWithoutCompression(buf, Header{Response: true, Authoritative: true})
if err := bld.StartQuestions(); err != nil { if err := bld.StartQuestions(); err != nil {
b.Fatal("bld.StartQuestions():", err) tb.Fatal("bld.StartQuestions():", err)
} }
q := Question{ q := Question{
Name: name, Name: name,
Type: TypeA, Type: TypeA,
Class: ClassINET, Class: ClassINET,
} }
if err := bld.Question(q); err != nil { if err := bld.Question(q); err != nil {
b.Fatalf("bld.Question(%+v): %v", q, err) tb.Fatalf("bld.Question(%+v): %v", q, err)
} }
hdr := ResourceHeader{ hdr := ResourceHeader{
Name: name, Name: name,
Class: ClassINET, Class: ClassINET,
} }
if err := bld.StartAnswers(); err != nil { if err := bld.StartAnswers(); err != nil {
b.Fatal("bld.StartQuestions():", err) tb.Fatal("bld.StartQuestions():", err)
} }
ar := AResource{[4]byte{}} ar := AResource{[4]byte{}}
if err := bld.AResource(hdr, ar); err != nil { if err := bld.AResource(hdr, ar); err != nil {
b.Fatalf("bld.AResource(%+v, %+v): %v", hdr, ar, err) tb.Fatalf("bld.AResource(%+v, %+v): %v", hdr, ar, err)
} }
aaar := AAAAResource{[16]byte{}} aaar := AAAAResource{[16]byte{}}
if err := bld.AAAAResource(hdr, aaar); err != nil { if err := bld.AAAAResource(hdr, aaar); err != nil {
b.Fatalf("bld.AAAAResource(%+v, %+v): %v", hdr, aaar, err) tb.Fatalf("bld.AAAAResource(%+v, %+v): %v", hdr, aaar, err)
} }
cnr := CNAMEResource{name} cnr := CNAMEResource{name}
if err := bld.CNAMEResource(hdr, cnr); err != nil { if err := bld.CNAMEResource(hdr, cnr); err != nil {
b.Fatalf("bld.CNAMEResource(%+v, %+v): %v", hdr, cnr, err) tb.Fatalf("bld.CNAMEResource(%+v, %+v): %v", hdr, cnr, err)
} }
nsr := NSResource{name} nsr := NSResource{name}
if err := bld.NSResource(hdr, nsr); err != nil { if err := bld.NSResource(hdr, nsr); err != nil {
b.Fatalf("bld.NSResource(%+v, %+v): %v", hdr, nsr, err) tb.Fatalf("bld.NSResource(%+v, %+v): %v", hdr, nsr, err)
} }
if _, err := bld.Finish(); err != nil { if _, err := bld.Finish(); err != nil {
b.Fatal("bld.Finish():", err) tb.Fatal("bld.Finish():", err)
} }
}
func BenchmarkBuilding(b *testing.B) {
name, buf := benchmarkBuildingSetup()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchmarkBuilding(b, name, buf)
}
}
func TestBuildingAllocs(t *testing.T) {
name, buf := benchmarkBuildingSetup()
if allocs := testing.AllocsPerRun(100, func() { benchmarkBuilding(t, name, buf) }); allocs > 0.5 {
t.Errorf("Allocations during building: got = %f, want ~0", allocs)
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment