Browse Source

dns/dnsmessage: implement fmt.GoStringer.GoString

This improves the debugability of the library and eases the transition
to this library. For example, test DNS messages defined with another DNS
library can be packed and then unpacked with this library. These
unpacked messages can have GoString called on them to generate new test
messages defined with this library.

Updates golang/go#16218

Change-Id: I602586500fd8202892ef04187d3bd8a11039cf27
Reviewed-on: https://go-review.googlesource.com/120697
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Ian Gudger 7 years ago
parent
commit
87b3feba56
2 changed files with 477 additions and 29 deletions
  1. 364 10
      dns/dnsmessage/message.go
  2. 113 19
      dns/dnsmessage/message_test.go

+ 364 - 10
dns/dnsmessage/message.go

@@ -21,16 +21,6 @@ import (
 // A Type is a type of DNS request and response.
 type Type uint16
 
-// A Class is a type of network.
-type Class uint16
-
-// An OpCode is a DNS operation code.
-type OpCode uint16
-
-// An RCode is a DNS response status code.
-type RCode uint16
-
-// Wire constants.
 const (
 	// ResourceHeader.Type and Question.Type
 	TypeA     Type = 1
@@ -50,7 +40,46 @@ const (
 	TypeMINFO Type = 14
 	TypeAXFR  Type = 252
 	TypeALL   Type = 255
+)
+
+var typeNames = map[Type]string{
+	TypeA:     "TypeA",
+	TypeNS:    "TypeNS",
+	TypeCNAME: "TypeCNAME",
+	TypeSOA:   "TypeSOA",
+	TypePTR:   "TypePTR",
+	TypeMX:    "TypeMX",
+	TypeTXT:   "TypeTXT",
+	TypeAAAA:  "TypeAAAA",
+	TypeSRV:   "TypeSRV",
+	TypeOPT:   "TypeOPT",
+	TypeWKS:   "TypeWKS",
+	TypeHINFO: "TypeHINFO",
+	TypeMINFO: "TypeMINFO",
+	TypeAXFR:  "TypeAXFR",
+	TypeALL:   "TypeALL",
+}
+
+// String implements fmt.Stringer.String.
+func (t Type) String() string {
+	if n, ok := typeNames[t]; ok {
+		return n
+	}
+	return printUint16(uint16(t))
+}
+
+// GoString implements fmt.GoStringer.GoString.
+func (t Type) GoString() string {
+	if n, ok := typeNames[t]; ok {
+		return "dnsmessage." + n
+	}
+	return printUint16(uint16(t))
+}
+
+// A Class is a type of network.
+type Class uint16
 
+const (
 	// ResourceHeader.Class and Question.Class
 	ClassINET   Class = 1
 	ClassCSNET  Class = 2
@@ -59,7 +88,44 @@ const (
 
 	// Question.Class
 	ClassANY Class = 255
+)
+
+var classNames = map[Class]string{
+	ClassINET:   "ClassINET",
+	ClassCSNET:  "ClassCSNET",
+	ClassCHAOS:  "ClassCHAOS",
+	ClassHESIOD: "ClassHESIOD",
+	ClassANY:    "ClassANY",
+}
+
+// String implements fmt.Stringer.String.
+func (c Class) String() string {
+	if n, ok := classNames[c]; ok {
+		return n
+	}
+	return printUint16(uint16(c))
+}
 
+// GoString implements fmt.GoStringer.GoString.
+func (c Class) GoString() string {
+	if n, ok := classNames[c]; ok {
+		return "dnsmessage." + n
+	}
+	return printUint16(uint16(c))
+}
+
+// An OpCode is a DNS operation code.
+type OpCode uint16
+
+// GoString implements fmt.GoStringer.GoString.
+func (o OpCode) GoString() string {
+	return printUint16(uint16(o))
+}
+
+// An RCode is a DNS response status code.
+type RCode uint16
+
+const (
 	// Message.Rcode
 	RCodeSuccess        RCode = 0
 	RCodeFormatError    RCode = 1
@@ -69,6 +135,116 @@ const (
 	RCodeRefused        RCode = 5
 )
 
+var rCodeNames = map[RCode]string{
+	RCodeSuccess:        "RCodeSuccess",
+	RCodeFormatError:    "RCodeFormatError",
+	RCodeServerFailure:  "RCodeServerFailure",
+	RCodeNameError:      "RCodeNameError",
+	RCodeNotImplemented: "RCodeNotImplemented",
+	RCodeRefused:        "RCodeRefused",
+}
+
+// String implements fmt.Stringer.String.
+func (r RCode) String() string {
+	if n, ok := rCodeNames[r]; ok {
+		return n
+	}
+	return printUint16(uint16(r))
+}
+
+// GoString implements fmt.GoStringer.GoString.
+func (r RCode) GoString() string {
+	if n, ok := rCodeNames[r]; ok {
+		return "dnsmessage." + n
+	}
+	return printUint16(uint16(r))
+}
+
+func printPaddedUint8(i uint8) string {
+	b := byte(i)
+	return string([]byte{
+		b/100 + '0',
+		b/10%10 + '0',
+		b%10 + '0',
+	})
+}
+
+func printUint8Bytes(buf []byte, i uint8) []byte {
+	b := byte(i)
+	if i >= 100 {
+		buf = append(buf, b/100+'0')
+	}
+	if i >= 10 {
+		buf = append(buf, b/10%10+'0')
+	}
+	return append(buf, b%10+'0')
+}
+
+func printByteSlice(b []byte) string {
+	if len(b) == 0 {
+		return ""
+	}
+	buf := make([]byte, 0, 5*len(b))
+	buf = printUint8Bytes(buf, uint8(b[0]))
+	for _, n := range b[1:] {
+		buf = append(buf, ',', ' ')
+		buf = printUint8Bytes(buf, uint8(n))
+	}
+	return string(buf)
+}
+
+const hexDigits = "0123456789abcdef"
+
+func printString(str []byte) string {
+	buf := make([]byte, 0, len(str))
+	for i := 0; i < len(str); i++ {
+		c := str[i]
+		if c == '.' || c == '-' || c == ' ' ||
+			'A' <= c && c <= 'Z' ||
+			'a' <= c && c <= 'z' ||
+			'0' <= c && c <= '9' {
+			buf = append(buf, c)
+			continue
+		}
+
+		upper := c >> 4
+		lower := (c << 4) >> 4
+		buf = append(
+			buf,
+			'\\',
+			'x',
+			hexDigits[upper],
+			hexDigits[lower],
+		)
+	}
+	return string(buf)
+}
+
+func printUint16(i uint16) string {
+	return printUint32(uint32(i))
+}
+
+func printUint32(i uint32) string {
+	// Max value is 4294967295.
+	buf := make([]byte, 10)
+	for b, d := buf, uint32(1000000000); d > 0; d /= 10 {
+		b[0] = byte(i/d%10 + '0')
+		if b[0] == '0' && len(b) == len(buf) && len(buf) > 1 {
+			buf = buf[1:]
+		}
+		b = b[1:]
+		i %= d
+	}
+	return string(buf)
+}
+
+func printBool(b bool) string {
+	if b {
+		return "true"
+	}
+	return "false"
+}
+
 var (
 	// ErrNotStarted indicates that the prerequisite information isn't
 	// available yet because the previous records haven't been appropriately
@@ -165,6 +341,19 @@ func (m *Header) pack() (id uint16, bits uint16) {
 	return
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (m *Header) GoString() string {
+	return "dnsmessage.Header{" +
+		"ID: " + printUint16(m.ID) + ", " +
+		"Response: " + printBool(m.Response) + ", " +
+		"OpCode: " + m.OpCode.GoString() + ", " +
+		"Authoritative: " + printBool(m.Authoritative) + ", " +
+		"Truncated: " + printBool(m.Truncated) + ", " +
+		"RecursionDesired: " + printBool(m.RecursionDesired) + ", " +
+		"RecursionAvailable: " + printBool(m.RecursionAvailable) + ", " +
+		"RCode: " + m.RCode.GoString() + "}"
+}
+
 // Message is a representation of a DNS message.
 type Message struct {
 	Header
@@ -277,6 +466,13 @@ type Resource struct {
 	Body   ResourceBody
 }
 
+func (r *Resource) GoString() string {
+	return "dnsmessage.Resource{" +
+		"Header: " + r.Header.GoString() +
+		", Body: &" + r.Body.GoString() +
+		"}"
+}
+
 // A ResourceBody is a DNS resource record minus the header.
 type ResourceBody interface {
 	// pack packs a Resource except for its header.
@@ -285,6 +481,9 @@ type ResourceBody interface {
 	// realType returns the actual type of the Resource. This is used to
 	// fill in the header Type field.
 	realType() Type
+
+	// GoString implements fmt.GoStringer.GoString.
+	GoString() string
 }
 
 // pack appends the wire format of the Resource to msg.
@@ -919,6 +1118,40 @@ func (m *Message) AppendPack(b []byte) ([]byte, error) {
 	return msg, nil
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (m *Message) GoString() string {
+	s := "dnsmessage.Message{Header: " + m.Header.GoString() + ", " +
+		"Questions: []dnsmessage.Question{"
+	if len(m.Questions) > 0 {
+		s += m.Questions[0].GoString()
+		for _, q := range m.Questions[1:] {
+			s += ", " + q.GoString()
+		}
+	}
+	s += "}, Answers: []dnsmessage.Resource{"
+	if len(m.Answers) > 0 {
+		s += m.Answers[0].GoString()
+		for _, a := range m.Answers[1:] {
+			s += ", " + a.GoString()
+		}
+	}
+	s += "}, Authorities: []dnsmessage.Resource{"
+	if len(m.Authorities) > 0 {
+		s += m.Authorities[0].GoString()
+		for _, a := range m.Authorities[1:] {
+			s += ", " + a.GoString()
+		}
+	}
+	s += "}, Additionals: []dnsmessage.Resource{"
+	if len(m.Additionals) > 0 {
+		s += m.Additionals[0].GoString()
+		for _, a := range m.Additionals[1:] {
+			s += ", " + a.GoString()
+		}
+	}
+	return s + "}}"
+}
+
 // A Builder allows incrementally packing a DNS message.
 //
 // Example usage:
@@ -1361,6 +1594,16 @@ type ResourceHeader struct {
 	Length uint16
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (h *ResourceHeader) GoString() string {
+	return "dnsmessage.ResourceHeader{" +
+		"Name: " + h.Name.GoString() + ", " +
+		"Type: " + h.Type.GoString() + ", " +
+		"Class: " + h.Class.GoString() + ", " +
+		"TTL: " + printUint32(h.TTL) + ", " +
+		"Length: " + printUint16(h.Length) + "}"
+}
+
 // pack appends the wire format of the ResourceHeader to oldMsg.
 //
 // The bytes where length was packed are returned as a slice so they can be
@@ -1623,10 +1866,25 @@ func NewName(name string) (Name, error) {
 	return n, nil
 }
 
+// MustNewName creates a new Name from a string and panics on error.
+func MustNewName(name string) Name {
+	n, err := NewName(name)
+	if err != nil {
+		panic("creating name: " + err.Error())
+	}
+	return n
+}
+
+// String implements fmt.Stringer.String.
 func (n Name) String() string {
 	return string(n.Data[:n.Length])
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (n *Name) GoString() string {
+	return `dnsmessage.MustNewName("` + printString(n.Data[:n.Length]) + `")`
+}
+
 // pack appends the wire format of the Name to msg.
 //
 // Domain names are a sequence of counted strings split at the dots. They end
@@ -1826,6 +2084,14 @@ func (q *Question) pack(msg []byte, compression map[string]int, compressionOff i
 	return packClass(msg, q.Class), nil
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (q *Question) GoString() string {
+	return "dnsmessage.Question{" +
+		"Name: " + q.Name.GoString() + ", " +
+		"Type: " + q.Type.GoString() + ", " +
+		"Class: " + q.Class.GoString() + "}"
+}
+
 func unpackResourceBody(msg []byte, off int, hdr ResourceHeader) (ResourceBody, int, error) {
 	var (
 		r    ResourceBody
@@ -1907,6 +2173,11 @@ func (r *CNAMEResource) pack(msg []byte, compression map[string]int, compression
 	return r.CNAME.pack(msg, compression, compressionOff)
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (r *CNAMEResource) GoString() string {
+	return "dnsmessage.CNAMEResource{CNAME: " + r.CNAME.GoString() + "}"
+}
+
 func unpackCNAMEResource(msg []byte, off int) (CNAMEResource, error) {
 	var cname Name
 	if _, err := cname.unpack(msg, off); err != nil {
@@ -1936,6 +2207,13 @@ func (r *MXResource) pack(msg []byte, compression map[string]int, compressionOff
 	return msg, nil
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (r *MXResource) GoString() string {
+	return "dnsmessage.MXResource{" +
+		"Pref: " + printUint16(r.Pref) + ", " +
+		"MX: " + r.MX.GoString() + "}"
+}
+
 func unpackMXResource(msg []byte, off int) (MXResource, error) {
 	pref, off, err := unpackUint16(msg, off)
 	if err != nil {
@@ -1962,6 +2240,11 @@ func (r *NSResource) pack(msg []byte, compression map[string]int, compressionOff
 	return r.NS.pack(msg, compression, compressionOff)
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (r *NSResource) GoString() string {
+	return "dnsmessage.NSResource{NS: " + r.NS.GoString() + "}"
+}
+
 func unpackNSResource(msg []byte, off int) (NSResource, error) {
 	var ns Name
 	if _, err := ns.unpack(msg, off); err != nil {
@@ -1984,6 +2267,11 @@ func (r *PTRResource) pack(msg []byte, compression map[string]int, compressionOf
 	return r.PTR.pack(msg, compression, compressionOff)
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (r *PTRResource) GoString() string {
+	return "dnsmessage.PTRResource{PTR: " + r.PTR.GoString() + "}"
+}
+
 func unpackPTRResource(msg []byte, off int) (PTRResource, error) {
 	var ptr Name
 	if _, err := ptr.unpack(msg, off); err != nil {
@@ -2029,6 +2317,18 @@ func (r *SOAResource) pack(msg []byte, compression map[string]int, compressionOf
 	return packUint32(msg, r.MinTTL), nil
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (r *SOAResource) GoString() string {
+	return "dnsmessage.SOAResource{" +
+		"NS: " + r.NS.GoString() + ", " +
+		"MBox: " + r.MBox.GoString() + ", " +
+		"Serial: " + printUint32(r.Serial) + ", " +
+		"Refresh: " + printUint32(r.Refresh) + ", " +
+		"Retry: " + printUint32(r.Retry) + ", " +
+		"Expire: " + printUint32(r.Expire) + ", " +
+		"MinTTL: " + printUint32(r.MinTTL) + "}"
+}
+
 func unpackSOAResource(msg []byte, off int) (SOAResource, error) {
 	var ns Name
 	off, err := ns.unpack(msg, off)
@@ -2084,6 +2384,19 @@ func (r *TXTResource) pack(msg []byte, compression map[string]int, compressionOf
 	return msg, nil
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (r *TXTResource) GoString() string {
+	s := "dnsmessage.TXTResource{TXT: []string{"
+	if len(r.TXT) == 0 {
+		return s + "}}"
+	}
+	s += `"` + printString([]byte(r.TXT[0]))
+	for _, t := range r.TXT[1:] {
+		s += `", "` + printString([]byte(t))
+	}
+	return s + `"}}`
+}
+
 func unpackTXTResource(msg []byte, off int, length uint16) (TXTResource, error) {
 	txts := make([]string, 0, 1)
 	for n := uint16(0); n < length; {
@@ -2127,6 +2440,15 @@ func (r *SRVResource) pack(msg []byte, compression map[string]int, compressionOf
 	return msg, nil
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (r *SRVResource) GoString() string {
+	return "dnsmessage.SRVResource{" +
+		"Priority: " + printUint16(r.Priority) + ", " +
+		"Weight: " + printUint16(r.Weight) + ", " +
+		"Port: " + printUint16(r.Port) + ", " +
+		"Target: " + r.Target.GoString() + "}"
+}
+
 func unpackSRVResource(msg []byte, off int) (SRVResource, error) {
 	priority, off, err := unpackUint16(msg, off)
 	if err != nil {
@@ -2161,6 +2483,12 @@ func (r *AResource) pack(msg []byte, compression map[string]int, compressionOff
 	return packBytes(msg, r.A[:]), nil
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (r *AResource) GoString() string {
+	return "dnsmessage.AResource{" +
+		"A: [4]byte{" + printByteSlice(r.A[:]) + "}}"
+}
+
 func unpackAResource(msg []byte, off int) (AResource, error) {
 	var a [4]byte
 	if _, err := unpackBytes(msg, off, a[:]); err != nil {
@@ -2178,6 +2506,12 @@ func (r *AAAAResource) realType() Type {
 	return TypeAAAA
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (r *AAAAResource) GoString() string {
+	return "dnsmessage.AAAAResource{" +
+		"AAAA: [16]byte{" + printByteSlice(r.AAAA[:]) + "}}"
+}
+
 // pack appends the wire format of the AAAAResource to msg.
 func (r *AAAAResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
 	return packBytes(msg, r.AAAA[:]), nil
@@ -2208,6 +2542,13 @@ type Option struct {
 	Data []byte
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (o *Option) GoString() string {
+	return "dnsmessage.Option{" +
+		"Code: " + printUint16(o.Code) + ", " +
+		"Data: []byte{" + printByteSlice(o.Data) + "}}"
+}
+
 func (r *OPTResource) realType() Type {
 	return TypeOPT
 }
@@ -2222,6 +2563,19 @@ func (r *OPTResource) pack(msg []byte, compression map[string]int, compressionOf
 	return msg, nil
 }
 
+// GoString implements fmt.GoStringer.GoString.
+func (r *OPTResource) GoString() string {
+	s := "dnsmessage.OPTResource{Options: []dnsmessage.Option{"
+	if len(r.Options) == 0 {
+		return s + "}}"
+	}
+	s += r.Options[0].GoString()
+	for _, o := range r.Options[1:] {
+		s += ", " + o.GoString()
+	}
+	return s + "}}"
+}
+
 func unpackOPTResource(msg []byte, off int, length uint16) (OPTResource, error) {
 	var opts []Option
 	for oldOff := off; off < oldOff+int(length); {

File diff suppressed because it is too large
+ 113 - 19
dns/dnsmessage/message_test.go


Some files were not shown because too many files changed in this diff