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:
parent
36ffedf7d0
commit
0d29b283ac
4
msg.go
4
msg.go
|
@ -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()
|
||||||
|
|
|
@ -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
150
types.go
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue