Ver Fonte

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 há 7 anos atrás
pai
commit
87b3feba56
2 ficheiros alterados com 477 adições e 29 exclusões
  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.
 // A Type is a type of DNS request and response.
 type Type uint16
 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 (
 const (
 	// ResourceHeader.Type and Question.Type
 	// ResourceHeader.Type and Question.Type
 	TypeA     Type = 1
 	TypeA     Type = 1
@@ -50,7 +40,46 @@ const (
 	TypeMINFO Type = 14
 	TypeMINFO Type = 14
 	TypeAXFR  Type = 252
 	TypeAXFR  Type = 252
 	TypeALL   Type = 255
 	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
 	// ResourceHeader.Class and Question.Class
 	ClassINET   Class = 1
 	ClassINET   Class = 1
 	ClassCSNET  Class = 2
 	ClassCSNET  Class = 2
@@ -59,7 +88,44 @@ const (
 
 
 	// Question.Class
 	// Question.Class
 	ClassANY Class = 255
 	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
 	// Message.Rcode
 	RCodeSuccess        RCode = 0
 	RCodeSuccess        RCode = 0
 	RCodeFormatError    RCode = 1
 	RCodeFormatError    RCode = 1
@@ -69,6 +135,116 @@ const (
 	RCodeRefused        RCode = 5
 	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 (
 var (
 	// ErrNotStarted indicates that the prerequisite information isn't
 	// ErrNotStarted indicates that the prerequisite information isn't
 	// available yet because the previous records haven't been appropriately
 	// available yet because the previous records haven't been appropriately
@@ -165,6 +341,19 @@ func (m *Header) pack() (id uint16, bits uint16) {
 	return
 	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.
 // Message is a representation of a DNS message.
 type Message struct {
 type Message struct {
 	Header
 	Header
@@ -277,6 +466,13 @@ type Resource struct {
 	Body   ResourceBody
 	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.
 // A ResourceBody is a DNS resource record minus the header.
 type ResourceBody interface {
 type ResourceBody interface {
 	// pack packs a Resource except for its header.
 	// 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
 	// realType returns the actual type of the Resource. This is used to
 	// fill in the header Type field.
 	// fill in the header Type field.
 	realType() Type
 	realType() Type
+
+	// GoString implements fmt.GoStringer.GoString.
+	GoString() string
 }
 }
 
 
 // pack appends the wire format of the Resource to msg.
 // 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
 	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.
 // A Builder allows incrementally packing a DNS message.
 //
 //
 // Example usage:
 // Example usage:
@@ -1361,6 +1594,16 @@ type ResourceHeader struct {
 	Length uint16
 	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.
 // 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
 // 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
 	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 {
 func (n Name) String() string {
 	return string(n.Data[:n.Length])
 	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.
 // pack appends the wire format of the Name to msg.
 //
 //
 // Domain names are a sequence of counted strings split at the dots. They end
 // 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
 	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) {
 func unpackResourceBody(msg []byte, off int, hdr ResourceHeader) (ResourceBody, int, error) {
 	var (
 	var (
 		r    ResourceBody
 		r    ResourceBody
@@ -1907,6 +2173,11 @@ func (r *CNAMEResource) pack(msg []byte, compression map[string]int, compression
 	return r.CNAME.pack(msg, compression, compressionOff)
 	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) {
 func unpackCNAMEResource(msg []byte, off int) (CNAMEResource, error) {
 	var cname Name
 	var cname Name
 	if _, err := cname.unpack(msg, off); err != nil {
 	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
 	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) {
 func unpackMXResource(msg []byte, off int) (MXResource, error) {
 	pref, off, err := unpackUint16(msg, off)
 	pref, off, err := unpackUint16(msg, off)
 	if err != nil {
 	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)
 	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) {
 func unpackNSResource(msg []byte, off int) (NSResource, error) {
 	var ns Name
 	var ns Name
 	if _, err := ns.unpack(msg, off); err != nil {
 	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)
 	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) {
 func unpackPTRResource(msg []byte, off int) (PTRResource, error) {
 	var ptr Name
 	var ptr Name
 	if _, err := ptr.unpack(msg, off); err != nil {
 	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
 	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) {
 func unpackSOAResource(msg []byte, off int) (SOAResource, error) {
 	var ns Name
 	var ns Name
 	off, err := ns.unpack(msg, off)
 	off, err := ns.unpack(msg, off)
@@ -2084,6 +2384,19 @@ func (r *TXTResource) pack(msg []byte, compression map[string]int, compressionOf
 	return msg, nil
 	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) {
 func unpackTXTResource(msg []byte, off int, length uint16) (TXTResource, error) {
 	txts := make([]string, 0, 1)
 	txts := make([]string, 0, 1)
 	for n := uint16(0); n < length; {
 	for n := uint16(0); n < length; {
@@ -2127,6 +2440,15 @@ func (r *SRVResource) pack(msg []byte, compression map[string]int, compressionOf
 	return msg, nil
 	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) {
 func unpackSRVResource(msg []byte, off int) (SRVResource, error) {
 	priority, off, err := unpackUint16(msg, off)
 	priority, off, err := unpackUint16(msg, off)
 	if err != nil {
 	if err != nil {
@@ -2161,6 +2483,12 @@ func (r *AResource) pack(msg []byte, compression map[string]int, compressionOff
 	return packBytes(msg, r.A[:]), nil
 	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) {
 func unpackAResource(msg []byte, off int) (AResource, error) {
 	var a [4]byte
 	var a [4]byte
 	if _, err := unpackBytes(msg, off, a[:]); err != nil {
 	if _, err := unpackBytes(msg, off, a[:]); err != nil {
@@ -2178,6 +2506,12 @@ func (r *AAAAResource) realType() Type {
 	return TypeAAAA
 	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.
 // pack appends the wire format of the AAAAResource to msg.
 func (r *AAAAResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
 func (r *AAAAResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
 	return packBytes(msg, r.AAAA[:]), nil
 	return packBytes(msg, r.AAAA[:]), nil
@@ -2208,6 +2542,13 @@ type Option struct {
 	Data []byte
 	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 {
 func (r *OPTResource) realType() Type {
 	return TypeOPT
 	return TypeOPT
 }
 }
@@ -2222,6 +2563,19 @@ func (r *OPTResource) pack(msg []byte, compression map[string]int, compressionOf
 	return msg, nil
 	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) {
 func unpackOPTResource(msg []byte, off int, length uint16) (OPTResource, error) {
 	var opts []Option
 	var opts []Option
 	for oldOff := off; off < oldOff+int(length); {
 	for oldOff := off; off < oldOff+int(length); {

Diff do ficheiro suprimidas por serem muito extensas
+ 113 - 19
dns/dnsmessage/message_test.go


Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff