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/97716
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/dns/dnsmessage/message.go b/dns/dnsmessage/message.go
index e98fda6..624f9b6 100644
--- a/dns/dnsmessage/message.go
+++ b/dns/dnsmessage/message.go
@@ -273,25 +273,25 @@
// A ResourceBody is a DNS resource record minus the header.
type ResourceBody interface {
// pack packs a Resource except for its header.
- pack(msg []byte, compression map[string]int) ([]byte, error)
+ pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error)
// realType returns the actual type of the Resource. This is used to
// fill in the header Type field.
realType() Type
}
-func (r *Resource) pack(msg []byte, compression map[string]int) ([]byte, error) {
+func (r *Resource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
if r.Body == nil {
return msg, errNilResouceBody
}
oldMsg := msg
r.Header.Type = r.Body.realType()
- msg, length, err := r.Header.pack(msg, compression)
+ msg, length, err := r.Header.pack(msg, compression, compressionOff)
if err != nil {
return msg, &nestedError{"ResourceHeader", err}
}
preLen := len(msg)
- msg, err = r.Body.pack(msg, compression)
+ msg, err = r.Body.pack(msg, compression, compressionOff)
if err != nil {
return msg, &nestedError{"content", err}
}
@@ -852,6 +852,7 @@
h.authorities = uint16(len(m.Authorities))
h.additionals = uint16(len(m.Additionals))
+ compressionOff := len(b)
msg := h.pack(b)
// RFC 1035 allows (but does not require) compression for packing. RFC
@@ -866,25 +867,25 @@
for i := range m.Questions {
var err error
- if msg, err = m.Questions[i].pack(msg, compression); err != nil {
+ if msg, err = m.Questions[i].pack(msg, compression, compressionOff); err != nil {
return nil, &nestedError{"packing Question", err}
}
}
for i := range m.Answers {
var err error
- if msg, err = m.Answers[i].pack(msg, compression); err != nil {
+ if msg, err = m.Answers[i].pack(msg, compression, compressionOff); err != nil {
return nil, &nestedError{"packing Answer", err}
}
}
for i := range m.Authorities {
var err error
- if msg, err = m.Authorities[i].pack(msg, compression); err != nil {
+ if msg, err = m.Authorities[i].pack(msg, compression, compressionOff); err != nil {
return nil, &nestedError{"packing Authority", err}
}
}
for i := range m.Additionals {
var err error
- if msg, err = m.Additionals[i].pack(msg, compression); err != nil {
+ if msg, err = m.Additionals[i].pack(msg, compression, compressionOff); err != nil {
return nil, &nestedError{"packing Additional", err}
}
}
@@ -893,36 +894,69 @@
}
// A Builder allows incrementally packing a DNS message.
+//
+// Example usage:
+// buf := make([]byte, 2, 514)
+// b := NewBuilder(buf, Header{...})
+// b.EnableCompression()
+// // Optionally start a section and add things to that section.
+// // Repeat adding sections as necessary.
+// buf, err := b.Finish()
+// // If err is nil, buf[2:] will contain the built bytes.
type Builder struct {
- msg []byte
- header header
- section section
+ // msg is the storage for the message being built.
+ msg []byte
+
+ // section keeps track of the current section being built.
+ section section
+
+ // header keeps track of what should go in the header when Finish is
+ // called.
+ header header
+
+ // start is the starting index of the bytes allocated in msg for header.
+ start int
+
+ // compression is a mapping from name suffixes to their starting index
+ // in msg.
compression map[string]int
}
-// Start initializes the builder.
+// NewBuilder creates a new builder with compression disabled.
//
-// buf is optional (nil is fine), but if provided, Start takes ownership of buf.
-func (b *Builder) Start(buf []byte, h Header) {
- b.StartWithoutCompression(buf, h)
- b.compression = map[string]int{}
+// Note: Most users will want to immediately enable compression with the
+// EnableCompression method. See that method's comment for why you may or may
+// not want to enable compression.
+//
+// The DNS message is appended to the provided initial buffer buf (which may be
+// nil) as it is built. The final message is returned by the (*Builder).Finish
+// method, which may return the same underlying array if there was sufficient
+// capacity in the slice.
+func NewBuilder(buf []byte, h Header) Builder {
+ if buf == nil {
+ buf = make([]byte, 0, packStartingCap)
+ }
+ b := Builder{msg: buf, start: len(buf)}
+ b.header.id, b.header.bits = h.pack()
+ var hb [headerLen]byte
+ b.msg = append(b.msg, hb[:]...)
+ b.section = sectionHeader
+ return b
}
-// StartWithoutCompression initializes the builder with compression disabled.
+// EnableCompression enables compression in the Builder.
//
-// This avoids compression related allocations, but can result in larger message
-// sizes. Be careful with this mode as it can cause messages to exceed the UDP
-// size limit.
+// Leaving compression disabled avoids compression related allocations, but can
+// result in larger message sizes. Be careful with this mode as it can cause
+// messages to exceed the UDP size limit.
//
-// buf is optional (nil is fine), but if provided, Start takes ownership of buf.
-func (b *Builder) StartWithoutCompression(buf []byte, h Header) {
- *b = Builder{msg: buf}
- b.header.id, b.header.bits = h.pack()
- if cap(b.msg) < headerLen {
- b.msg = make([]byte, 0, packStartingCap)
- }
- b.msg = b.msg[:headerLen]
- b.section = sectionHeader
+// According to RFC 1035, section 4.1.4, the use of compression is optional, but
+// all implementations must accept both compressed and uncompressed DNS
+// messages.
+//
+// Compression should be enabled before any sections are added for best results.
+func (b *Builder) EnableCompression() {
+ b.compression = map[string]int{}
}
func (b *Builder) startCheck(s section) error {
@@ -1003,7 +1037,7 @@
if b.section > sectionQuestions {
return ErrSectionDone
}
- msg, err := q.pack(b.msg, b.compression)
+ msg, err := q.pack(b.msg, b.compression, b.start)
if err != nil {
return err
}
@@ -1030,12 +1064,12 @@
return err
}
h.Type = r.realType()
- msg, length, err := h.pack(b.msg, b.compression)
+ msg, length, err := h.pack(b.msg, b.compression, b.start)
if err != nil {
return &nestedError{"ResourceHeader", err}
}
preLen := len(msg)
- if msg, err = r.pack(msg, b.compression); err != nil {
+ if msg, err = r.pack(msg, b.compression, b.start); err != nil {
return &nestedError{"CNAMEResource body", err}
}
if err := h.fixLen(msg, length, preLen); err != nil {
@@ -1054,12 +1088,12 @@
return err
}
h.Type = r.realType()
- msg, length, err := h.pack(b.msg, b.compression)
+ msg, length, err := h.pack(b.msg, b.compression, b.start)
if err != nil {
return &nestedError{"ResourceHeader", err}
}
preLen := len(msg)
- if msg, err = r.pack(msg, b.compression); err != nil {
+ if msg, err = r.pack(msg, b.compression, b.start); err != nil {
return &nestedError{"MXResource body", err}
}
if err := h.fixLen(msg, length, preLen); err != nil {
@@ -1078,12 +1112,12 @@
return err
}
h.Type = r.realType()
- msg, length, err := h.pack(b.msg, b.compression)
+ msg, length, err := h.pack(b.msg, b.compression, b.start)
if err != nil {
return &nestedError{"ResourceHeader", err}
}
preLen := len(msg)
- if msg, err = r.pack(msg, b.compression); err != nil {
+ if msg, err = r.pack(msg, b.compression, b.start); err != nil {
return &nestedError{"NSResource body", err}
}
if err := h.fixLen(msg, length, preLen); err != nil {
@@ -1102,12 +1136,12 @@
return err
}
h.Type = r.realType()
- msg, length, err := h.pack(b.msg, b.compression)
+ msg, length, err := h.pack(b.msg, b.compression, b.start)
if err != nil {
return &nestedError{"ResourceHeader", err}
}
preLen := len(msg)
- if msg, err = r.pack(msg, b.compression); err != nil {
+ if msg, err = r.pack(msg, b.compression, b.start); err != nil {
return &nestedError{"PTRResource body", err}
}
if err := h.fixLen(msg, length, preLen); err != nil {
@@ -1126,12 +1160,12 @@
return err
}
h.Type = r.realType()
- msg, length, err := h.pack(b.msg, b.compression)
+ msg, length, err := h.pack(b.msg, b.compression, b.start)
if err != nil {
return &nestedError{"ResourceHeader", err}
}
preLen := len(msg)
- if msg, err = r.pack(msg, b.compression); err != nil {
+ if msg, err = r.pack(msg, b.compression, b.start); err != nil {
return &nestedError{"SOAResource body", err}
}
if err := h.fixLen(msg, length, preLen); err != nil {
@@ -1150,12 +1184,12 @@
return err
}
h.Type = r.realType()
- msg, length, err := h.pack(b.msg, b.compression)
+ msg, length, err := h.pack(b.msg, b.compression, b.start)
if err != nil {
return &nestedError{"ResourceHeader", err}
}
preLen := len(msg)
- if msg, err = r.pack(msg, b.compression); err != nil {
+ if msg, err = r.pack(msg, b.compression, b.start); err != nil {
return &nestedError{"TXTResource body", err}
}
if err := h.fixLen(msg, length, preLen); err != nil {
@@ -1174,12 +1208,12 @@
return err
}
h.Type = r.realType()
- msg, length, err := h.pack(b.msg, b.compression)
+ msg, length, err := h.pack(b.msg, b.compression, b.start)
if err != nil {
return &nestedError{"ResourceHeader", err}
}
preLen := len(msg)
- if msg, err = r.pack(msg, b.compression); err != nil {
+ if msg, err = r.pack(msg, b.compression, b.start); err != nil {
return &nestedError{"SRVResource body", err}
}
if err := h.fixLen(msg, length, preLen); err != nil {
@@ -1198,12 +1232,12 @@
return err
}
h.Type = r.realType()
- msg, length, err := h.pack(b.msg, b.compression)
+ msg, length, err := h.pack(b.msg, b.compression, b.start)
if err != nil {
return &nestedError{"ResourceHeader", err}
}
preLen := len(msg)
- if msg, err = r.pack(msg, b.compression); err != nil {
+ if msg, err = r.pack(msg, b.compression, b.start); err != nil {
return &nestedError{"AResource body", err}
}
if err := h.fixLen(msg, length, preLen); err != nil {
@@ -1222,12 +1256,12 @@
return err
}
h.Type = r.realType()
- msg, length, err := h.pack(b.msg, b.compression)
+ msg, length, err := h.pack(b.msg, b.compression, b.start)
if err != nil {
return &nestedError{"ResourceHeader", err}
}
preLen := len(msg)
- if msg, err = r.pack(msg, b.compression); err != nil {
+ if msg, err = r.pack(msg, b.compression, b.start); err != nil {
return &nestedError{"AAAAResource body", err}
}
if err := h.fixLen(msg, length, preLen); err != nil {
@@ -1246,7 +1280,8 @@
return nil, ErrNotStarted
}
b.section = sectionDone
- b.header.pack(b.msg[:0])
+ // Space for the header was allocated in NewBuilder.
+ b.header.pack(b.msg[b.start:b.start])
return b.msg, nil
}
@@ -1279,9 +1314,9 @@
// pack packs all of the fields in a ResourceHeader except for the length. The
// length bytes are returned as a slice so they can be filled in after the rest
// of the Resource has been packed.
-func (h *ResourceHeader) pack(oldMsg []byte, compression map[string]int) (msg []byte, length []byte, err error) {
+func (h *ResourceHeader) pack(oldMsg []byte, compression map[string]int, compressionOff int) (msg []byte, length []byte, err error) {
msg = oldMsg
- if msg, err = h.Name.pack(msg, compression); err != nil {
+ if msg, err = h.Name.pack(msg, compression, compressionOff); err != nil {
return oldMsg, nil, &nestedError{"Name", err}
}
msg = packType(msg, h.Type)
@@ -1506,7 +1541,7 @@
//
// The compression map will be updated with new domain suffixes. If compression
// is nil, compression will not be used.
-func (n *Name) pack(msg []byte, compression map[string]int) ([]byte, error) {
+func (n *Name) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
oldMsg := msg
// Add a trailing dot to canonicalize name.
@@ -1558,7 +1593,7 @@
// Miss. Add the suffix to the compression table if the
// offset can be stored in the available 14 bytes.
if len(msg) <= int(^uint16(0)>>2) {
- compression[string(n.Data[i:])] = len(msg)
+ compression[string(n.Data[i:])] = len(msg) - compressionOff
}
}
}
@@ -1681,8 +1716,8 @@
Class Class
}
-func (q *Question) pack(msg []byte, compression map[string]int) ([]byte, error) {
- msg, err := q.Name.pack(msg, compression)
+func (q *Question) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
+ msg, err := q.Name.pack(msg, compression, compressionOff)
if err != nil {
return msg, &nestedError{"Name", err}
}
@@ -1761,8 +1796,8 @@
return TypeCNAME
}
-func (r *CNAMEResource) pack(msg []byte, compression map[string]int) ([]byte, error) {
- return r.CNAME.pack(msg, compression)
+func (r *CNAMEResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
+ return r.CNAME.pack(msg, compression, compressionOff)
}
func unpackCNAMEResource(msg []byte, off int) (CNAMEResource, error) {
@@ -1783,10 +1818,10 @@
return TypeMX
}
-func (r *MXResource) pack(msg []byte, compression map[string]int) ([]byte, error) {
+func (r *MXResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
oldMsg := msg
msg = packUint16(msg, r.Pref)
- msg, err := r.MX.pack(msg, compression)
+ msg, err := r.MX.pack(msg, compression, compressionOff)
if err != nil {
return oldMsg, &nestedError{"MXResource.MX", err}
}
@@ -1814,8 +1849,8 @@
return TypeNS
}
-func (r *NSResource) pack(msg []byte, compression map[string]int) ([]byte, error) {
- return r.NS.pack(msg, compression)
+func (r *NSResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
+ return r.NS.pack(msg, compression, compressionOff)
}
func unpackNSResource(msg []byte, off int) (NSResource, error) {
@@ -1835,8 +1870,8 @@
return TypePTR
}
-func (r *PTRResource) pack(msg []byte, compression map[string]int) ([]byte, error) {
- return r.PTR.pack(msg, compression)
+func (r *PTRResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
+ return r.PTR.pack(msg, compression, compressionOff)
}
func unpackPTRResource(msg []byte, off int) (PTRResource, error) {
@@ -1866,13 +1901,13 @@
return TypeSOA
}
-func (r *SOAResource) pack(msg []byte, compression map[string]int) ([]byte, error) {
+func (r *SOAResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
oldMsg := msg
- msg, err := r.NS.pack(msg, compression)
+ msg, err := r.NS.pack(msg, compression, compressionOff)
if err != nil {
return oldMsg, &nestedError{"SOAResource.NS", err}
}
- msg, err = r.MBox.pack(msg, compression)
+ msg, err = r.MBox.pack(msg, compression, compressionOff)
if err != nil {
return oldMsg, &nestedError{"SOAResource.MBox", err}
}
@@ -1925,7 +1960,7 @@
return TypeTXT
}
-func (r *TXTResource) pack(msg []byte, compression map[string]int) ([]byte, error) {
+func (r *TXTResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
return packText(msg, r.Txt), nil
}
@@ -1959,12 +1994,12 @@
return TypeSRV
}
-func (r *SRVResource) pack(msg []byte, compression map[string]int) ([]byte, error) {
+func (r *SRVResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
oldMsg := msg
msg = packUint16(msg, r.Priority)
msg = packUint16(msg, r.Weight)
msg = packUint16(msg, r.Port)
- msg, err := r.Target.pack(msg, nil)
+ msg, err := r.Target.pack(msg, nil, compressionOff)
if err != nil {
return oldMsg, &nestedError{"SRVResource.Target", err}
}
@@ -2000,7 +2035,7 @@
return TypeA
}
-func (r *AResource) pack(msg []byte, compression map[string]int) ([]byte, error) {
+func (r *AResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
return packBytes(msg, r.A[:]), nil
}
@@ -2021,7 +2056,7 @@
return TypeAAAA
}
-func (r *AAAAResource) pack(msg []byte, compression map[string]int) ([]byte, error) {
+func (r *AAAAResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
return packBytes(msg, r.AAAA[:]), nil
}
diff --git a/dns/dnsmessage/message_test.go b/dns/dnsmessage/message_test.go
index 2bb7634..d4eca26 100644
--- a/dns/dnsmessage/message_test.go
+++ b/dns/dnsmessage/message_test.go
@@ -62,7 +62,7 @@
Type: TypeA,
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 {
t.Fatal("Packing failed:", err)
}
@@ -129,7 +129,7 @@
for _, test := range tests {
in := mustNewName(test.in)
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 {
t.Errorf("Packing of %q: got err = %v, want err = %v", test.in, err, test.err)
continue
@@ -248,6 +248,40 @@
}
}
+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) {
msg := largeTestMsg()
buf, err := msg.Pack()
@@ -412,7 +446,7 @@
},
&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 {
t.Fatal("Packing failed:", err)
}
@@ -434,6 +468,26 @@
}
}
+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) {
tests := []struct {
name string
@@ -514,8 +568,8 @@
t.Fatal("Packing without builder:", err)
}
- var b Builder
- b.Start(nil, msg.Header)
+ b := NewBuilder(nil, msg.Header)
+ b.EnableCompression()
if err := b.StartQuestions(); err != nil {
t.Fatal("b.StartQuestions():", err)
@@ -653,9 +707,7 @@
}
}
-func BenchmarkParsing(b *testing.B) {
- b.ReportAllocs()
-
+func benchmarkParsingSetup() ([]byte, error) {
name := mustNewName("foo.bar.example.com.")
msg := Message{
Header: Header{Response: true, Authoritative: true},
@@ -700,111 +752,148 @@
buf, err := msg.Pack()
if err != nil {
- b.Fatal("msg.Pack():", err)
+ return nil, fmt.Errorf("msg.Pack(): %v", err)
+ }
+ return buf, nil
+}
+
+func benchmarkParsing(tb testing.TB, buf []byte) {
+ var p Parser
+ if _, err := p.Start(buf); err != nil {
+ tb.Fatal("p.Start(buf):", err)
}
- for i := 0; i < b.N; i++ {
- var p Parser
- if _, err := p.Start(buf); err != nil {
- b.Fatal("p.Start(buf):", err)
+ for {
+ _, err := p.Question()
+ if err == ErrSectionDone {
+ break
+ }
+ if err != nil {
+ tb.Fatal("p.Question():", err)
+ }
+ }
+
+ for {
+ h, err := p.AnswerHeader()
+ if err == ErrSectionDone {
+ break
+ }
+ if err != nil {
+ panic(err)
}
- for {
- _, err := p.Question()
- if err == ErrSectionDone {
- break
+ switch h.Type {
+ case TypeA:
+ if _, err := p.AResource(); err != nil {
+ tb.Fatal("p.AResource():", err)
}
- if err != nil {
- b.Fatal("p.Question():", err)
+ case TypeAAAA:
+ if _, err := p.AAAAResource(); err != nil {
+ tb.Fatal("p.AAAAResource():", err)
}
- }
-
- for {
- h, err := p.AnswerHeader()
- if err == ErrSectionDone {
- break
+ case TypeCNAME:
+ if _, err := p.CNAMEResource(); err != nil {
+ tb.Fatal("p.CNAMEResource():", err)
}
- if err != nil {
- panic(err)
+ case TypeNS:
+ if _, err := p.NSResource(); err != nil {
+ tb.Fatal("p.NSResource():", err)
}
-
- switch h.Type {
- case TypeA:
- 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)
- }
+ default:
+ tb.Fatalf("unknown type: %T", h)
}
}
}
-func BenchmarkBuilding(b *testing.B) {
- b.ReportAllocs()
+func BenchmarkParsing(b *testing.B) {
+ buf, err := benchmarkParsingSetup()
+ if err != nil {
+ b.Fatal(err)
+ }
+ 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.")
buf := make([]byte, 0, packStartingCap)
+ return name, buf
+}
+func benchmarkBuilding(tb testing.TB, name Name, buf []byte) {
+ bld := NewBuilder(buf, Header{Response: true, Authoritative: true})
+
+ if err := bld.StartQuestions(); err != nil {
+ tb.Fatal("bld.StartQuestions():", err)
+ }
+ q := Question{
+ Name: name,
+ Type: TypeA,
+ Class: ClassINET,
+ }
+ if err := bld.Question(q); err != nil {
+ tb.Fatalf("bld.Question(%+v): %v", q, err)
+ }
+
+ hdr := ResourceHeader{
+ Name: name,
+ Class: ClassINET,
+ }
+ if err := bld.StartAnswers(); err != nil {
+ tb.Fatal("bld.StartQuestions():", err)
+ }
+
+ ar := AResource{[4]byte{}}
+ if err := bld.AResource(hdr, ar); err != nil {
+ tb.Fatalf("bld.AResource(%+v, %+v): %v", hdr, ar, err)
+ }
+
+ aaar := AAAAResource{[16]byte{}}
+ if err := bld.AAAAResource(hdr, aaar); err != nil {
+ tb.Fatalf("bld.AAAAResource(%+v, %+v): %v", hdr, aaar, err)
+ }
+
+ cnr := CNAMEResource{name}
+ if err := bld.CNAMEResource(hdr, cnr); err != nil {
+ tb.Fatalf("bld.CNAMEResource(%+v, %+v): %v", hdr, cnr, err)
+ }
+
+ nsr := NSResource{name}
+ if err := bld.NSResource(hdr, nsr); err != nil {
+ tb.Fatalf("bld.NSResource(%+v, %+v): %v", hdr, nsr, err)
+ }
+
+ if _, err := bld.Finish(); err != nil {
+ tb.Fatal("bld.Finish():", err)
+ }
+}
+
+func BenchmarkBuilding(b *testing.B) {
+ name, buf := benchmarkBuildingSetup()
+ b.ReportAllocs()
for i := 0; i < b.N; i++ {
- var bld Builder
- bld.StartWithoutCompression(buf, Header{Response: true, Authoritative: true})
+ benchmarkBuilding(b, name, buf)
+ }
+}
- if err := bld.StartQuestions(); err != nil {
- b.Fatal("bld.StartQuestions():", err)
- }
- q := Question{
- Name: name,
- Type: TypeA,
- Class: ClassINET,
- }
- if err := bld.Question(q); err != nil {
- b.Fatalf("bld.Question(%+v): %v", q, err)
- }
-
- hdr := ResourceHeader{
- Name: name,
- Class: ClassINET,
- }
- if err := bld.StartAnswers(); err != nil {
- b.Fatal("bld.StartQuestions():", err)
- }
-
- ar := AResource{[4]byte{}}
- if err := bld.AResource(hdr, ar); err != nil {
- b.Fatalf("bld.AResource(%+v, %+v): %v", hdr, ar, err)
- }
-
- aaar := AAAAResource{[16]byte{}}
- if err := bld.AAAAResource(hdr, aaar); err != nil {
- b.Fatalf("bld.AAAAResource(%+v, %+v): %v", hdr, aaar, err)
- }
-
- cnr := CNAMEResource{name}
- if err := bld.CNAMEResource(hdr, cnr); err != nil {
- b.Fatalf("bld.CNAMEResource(%+v, %+v): %v", hdr, cnr, err)
- }
-
- nsr := NSResource{name}
- if err := bld.NSResource(hdr, nsr); err != nil {
- b.Fatalf("bld.NSResource(%+v, %+v): %v", hdr, nsr, err)
- }
-
- if _, err := bld.Finish(); err != nil {
- b.Fatal("bld.Finish():", err)
- }
+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)
}
}