Optimise sprintX functions in types.go (#757)

* Simplify appendByte

* Add test case and benchmark for sprintName

* Add test case and benchmark for sprintTxtOctet

* Add test case and benchmark for sprintTxt

* Use strings.Builder for sprint* functions in types.go

* Use writeByte helper in unpackString

* Rename writeByte to writeEscapedByte

This better captures the purpose of this function.
This commit is contained in:
Tom Thorogood 2018-10-06 02:06:59 +09:30 committed by GitHub
parent 36ffedf7d0
commit 0d29b283ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 143 additions and 82 deletions

4
msg.go
View File

@ -531,6 +531,10 @@ func dddToByte(s []byte) byte {
return byte((s[0]-'0')*100 + (s[1]-'0')*10 + (s[2] - '0')) return byte((s[0]-'0')*100 + (s[1]-'0')*10 + (s[2] - '0'))
} }
func dddStringToByte(s string) byte {
return byte((s[0]-'0')*100 + (s[1]-'0')*10 + (s[2] - '0'))
}
// Helper function for packing and unpacking // Helper function for packing and unpacking
func intToBytes(i *big.Int, length int) []byte { func intToBytes(i *big.Int, length int) []byte {
buf := i.Bytes() buf := i.Bytes()

View File

@ -6,7 +6,6 @@ import (
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"net" "net"
"strconv"
"strings" "strings"
) )
@ -276,13 +275,7 @@ func unpackString(msg []byte, off int) (string, int, error) {
s.WriteByte('\\') s.WriteByte('\\')
s.WriteByte(b) s.WriteByte(b)
case b < ' ' || b > '~': // unprintable case b < ' ' || b > '~': // unprintable
var buf [3]byte writeEscapedByte(&s, b)
bufs := strconv.AppendInt(buf[:0], int64(b), 10)
s.WriteByte('\\')
for i := len(bufs); i < 3; i++ {
s.WriteByte('0')
}
s.Write(bufs)
default: default:
s.WriteByte(b) s.WriteByte(b)
} }

150
types.go
View File

@ -419,128 +419,130 @@ type TXT struct {
func (rr *TXT) String() string { return rr.Hdr.String() + sprintTxt(rr.Txt) } func (rr *TXT) String() string { return rr.Hdr.String() + sprintTxt(rr.Txt) }
func sprintName(s string) string { func sprintName(s string) string {
src := []byte(s) var dst strings.Builder
dst := make([]byte, 0, len(src)) dst.Grow(len(s))
for i := 0; i < len(src); { for i := 0; i < len(s); {
if i+1 < len(src) && src[i] == '\\' && src[i+1] == '.' { if i+1 < len(s) && s[i] == '\\' && s[i+1] == '.' {
dst = append(dst, src[i:i+2]...) dst.WriteString(s[i : i+2])
i += 2 i += 2
} else { continue
b, n := nextByte(src, i)
if n == 0 {
i++ // dangling back slash
} else if b == '.' {
dst = append(dst, b)
} else {
dst = appendDomainNameByte(dst, b)
}
i += n
} }
b, n := nextByte(s, i)
switch {
case n == 0:
i++ // dangling back slash
case b == '.':
dst.WriteByte('.')
default:
writeDomainNameByte(&dst, b)
}
i += n
} }
return string(dst) return dst.String()
} }
func sprintTxtOctet(s string) string { func sprintTxtOctet(s string) string {
src := []byte(s) var dst strings.Builder
dst := make([]byte, 0, len(src)) dst.Grow(2 + len(s))
dst = append(dst, '"') dst.WriteByte('"')
for i := 0; i < len(src); { for i := 0; i < len(s); {
if i+1 < len(src) && src[i] == '\\' && src[i+1] == '.' { if i+1 < len(s) && s[i] == '\\' && s[i+1] == '.' {
dst = append(dst, src[i:i+2]...) dst.WriteString(s[i : i+2])
i += 2 i += 2
} else { continue
b, n := nextByte(src, i)
if n == 0 {
i++ // dangling back slash
} else if b == '.' {
dst = append(dst, b)
} else {
if b < ' ' || b > '~' {
dst = appendByte(dst, b)
} else {
dst = append(dst, b)
}
}
i += n
} }
b, n := nextByte(s, i)
switch {
case n == 0:
i++ // dangling back slash
case b == '.':
dst.WriteByte('.')
case b < ' ' || b > '~':
writeEscapedByte(&dst, b)
default:
dst.WriteByte(b)
}
i += n
} }
dst = append(dst, '"') dst.WriteByte('"')
return string(dst) return dst.String()
} }
func sprintTxt(txt []string) string { func sprintTxt(txt []string) string {
var out []byte var out strings.Builder
for i, s := range txt { for i, s := range txt {
out.Grow(3 + len(s))
if i > 0 { if i > 0 {
out = append(out, ` "`...) out.WriteString(` "`)
} else { } else {
out = append(out, '"') out.WriteByte('"')
} }
bs := []byte(s) for j := 0; j < len(s); {
for j := 0; j < len(bs); { b, n := nextByte(s, j)
b, n := nextByte(bs, j)
if n == 0 { if n == 0 {
break break
} }
out = appendTXTStringByte(out, b) writeTXTStringByte(&out, b)
j += n j += n
} }
out = append(out, '"') out.WriteByte('"')
} }
return string(out) return out.String()
} }
func appendDomainNameByte(s []byte, b byte) []byte { func writeDomainNameByte(s *strings.Builder, b byte) {
switch b { switch b {
case '.', ' ', '\'', '@', ';', '(', ')': // additional chars to escape case '.', ' ', '\'', '@', ';', '(', ')': // additional chars to escape
return append(s, '\\', b) s.WriteByte('\\')
s.WriteByte(b)
default:
writeTXTStringByte(s, b)
} }
return appendTXTStringByte(s, b)
} }
func appendTXTStringByte(s []byte, b byte) []byte { func writeTXTStringByte(s *strings.Builder, b byte) {
switch b { switch {
case '"', '\\': case b == '"' || b == '\\':
return append(s, '\\', b) s.WriteByte('\\')
s.WriteByte(b)
case b < ' ' || b > '~':
writeEscapedByte(s, b)
default:
s.WriteByte(b)
} }
if b < ' ' || b > '~' {
return appendByte(s, b)
}
return append(s, b)
} }
func appendByte(s []byte, b byte) []byte { func writeEscapedByte(s *strings.Builder, b byte) {
var buf [3]byte var buf [3]byte
bufs := strconv.AppendInt(buf[:0], int64(b), 10) bufs := strconv.AppendInt(buf[:0], int64(b), 10)
s = append(s, '\\') s.WriteByte('\\')
for i := 0; i < 3-len(bufs); i++ { for i := len(bufs); i < 3; i++ {
s = append(s, '0') s.WriteByte('0')
} }
for _, r := range bufs { s.Write(bufs)
s = append(s, r)
}
return s
} }
func nextByte(b []byte, offset int) (byte, int) { func nextByte(s string, offset int) (byte, int) {
if offset >= len(b) { if offset >= len(s) {
return 0, 0 return 0, 0
} }
if b[offset] != '\\' { if s[offset] != '\\' {
// not an escape sequence // not an escape sequence
return b[offset], 1 return s[offset], 1
} }
switch len(b) - offset { switch len(s) - offset {
case 1: // dangling escape case 1: // dangling escape
return 0, 0 return 0, 0
case 2, 3: // too short to be \ddd case 2, 3: // too short to be \ddd
default: // maybe \ddd default: // maybe \ddd
if isDigit(b[offset+1]) && isDigit(b[offset+2]) && isDigit(b[offset+3]) { if isDigit(s[offset+1]) && isDigit(s[offset+2]) && isDigit(s[offset+3]) {
return dddToByte(b[offset+1:]), 4 return dddStringToByte(s[offset+1:]), 4
} }
} }
// not \ddd, just an RFC 1035 "quoted" character // not \ddd, just an RFC 1035 "quoted" character
return b[offset+1], 2 return s[offset+1], 2
} }
// SPF RR. See RFC 4408, Section 3.1.1. // SPF RR. See RFC 4408, Section 3.1.1.

View File

@ -72,3 +72,65 @@ func TestSplitN(t *testing.T) {
t.Errorf("failure to split 510 char long string: %d", len(xs)) t.Errorf("failure to split 510 char long string: %d", len(xs))
} }
} }
func TestSprintName(t *testing.T) {
got := sprintName("abc\\.def\007\"\127@\255\x05\xef\\")
if want := "abc\\.def\\007\\\"W\\@\\173\\005\\239"; got != want {
t.Errorf("expected %q, got %q", got, want)
}
}
func TestSprintTxtOctet(t *testing.T) {
got := sprintTxtOctet("abc\\.def\007\"\127@\255\x05\xef\\")
if want := "\"abc\\.def\\007\"W@\\173\\005\\239\""; got != want {
t.Errorf("expected %q, got %q", got, want)
}
}
func TestSprintTxt(t *testing.T) {
got := sprintTxt([]string{
"abc\\.def\007\"\127@\255\x05\xef\\",
"example.com",
})
if want := "\"abc.def\\007\\\"W@\\173\\005\\239\" \"example.com\""; got != want {
t.Errorf("expected %q, got %q", got, want)
}
}
func BenchmarkSprintName(b *testing.B) {
for n := 0; n < b.N; n++ {
got := sprintName("abc\\.def\007\"\127@\255\x05\xef\\")
if want := "abc\\.def\\007\\\"W\\@\\173\\005\\239"; got != want {
b.Fatalf("expected %q, got %q", got, want)
}
}
}
func BenchmarkSprintTxtOctet(b *testing.B) {
for n := 0; n < b.N; n++ {
got := sprintTxtOctet("abc\\.def\007\"\127@\255\x05\xef\\")
if want := "\"abc\\.def\\007\"W@\\173\\005\\239\""; got != want {
b.Fatalf("expected %q, got %q", got, want)
}
}
}
func BenchmarkSprintTxt(b *testing.B) {
txt := []string{
"abc\\.def\007\"\127@\255\x05\xef\\",
"example.com",
}
for n := 0; n < b.N; n++ {
got := sprintTxt(txt)
if want := "\"abc.def\\007\\\"W@\\173\\005\\239\" \"example.com\""; got != want {
b.Fatalf("expected %q, got %q", got, want)
}
}
}