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)
 	}
 }