1package protocol
2
3import (
4	"bytes"
5	"reflect"
6	"strconv"
7	"strings"
8	"unicode/utf8"
9	"unsafe"
10)
11
12const (
13	escapes            = "\t\n\f\r ,="
14	nameEscapes        = "\t\n\f\r ,"
15	stringFieldEscapes = "\t\n\f\r\\\""
16)
17
18var (
19	stringEscaper = strings.NewReplacer(
20		"\t", `\t`,
21		"\n", `\n`,
22		"\f", `\f`,
23		"\r", `\r`,
24		`,`, `\,`,
25		` `, `\ `,
26		`=`, `\=`,
27	)
28
29	nameEscaper = strings.NewReplacer(
30		"\t", `\t`,
31		"\n", `\n`,
32		"\f", `\f`,
33		"\r", `\r`,
34		`,`, `\,`,
35		` `, `\ `,
36	)
37
38	stringFieldEscaper = strings.NewReplacer(
39		"\t", `\t`,
40		"\n", `\n`,
41		"\f", `\f`,
42		"\r", `\r`,
43		`"`, `\"`,
44		`\`, `\\`,
45	)
46)
47
48var (
49	unescaper = strings.NewReplacer(
50		`\,`, `,`,
51		`\"`, `"`, // ???
52		`\ `, ` `,
53		`\=`, `=`,
54	)
55
56	nameUnescaper = strings.NewReplacer(
57		`\,`, `,`,
58		`\ `, ` `,
59	)
60
61	stringFieldUnescaper = strings.NewReplacer(
62		`\"`, `"`,
63		`\\`, `\`,
64	)
65)
66
67// The various escape functions allocate, I'd like to fix that.
68// TODO: make escape not allocate
69
70// Escape a tagkey, tagvalue, or fieldkey
71func escape(s string) string {
72	if strings.ContainsAny(s, escapes) {
73		return stringEscaper.Replace(s)
74	}
75	return s
76}
77
78// Escape a measurement name
79func nameEscape(s string) string {
80	if strings.ContainsAny(s, nameEscapes) {
81		return nameEscaper.Replace(s)
82	}
83	return s
84}
85
86// Escape a string field
87func stringFieldEscape(s string) string {
88	if strings.ContainsAny(s, stringFieldEscapes) {
89		return stringFieldEscaper.Replace(s)
90	}
91	return s
92}
93
94const (
95	utf8mask  = byte(0x3F)
96	utf8bytex = byte(0x80) // 1000 0000
97	utf8len2  = byte(0xC0) // 1100 0000
98	utf8len3  = byte(0xE0) // 1110 0000
99	utf8len4  = byte(0xF0) // 1111 0000
100)
101
102func escapeBytes(dest *[]byte, b []byte) {
103	if bytes.ContainsAny(b, escapes) {
104		var r rune
105		for i, j := 0, 0; i < len(b); i += j {
106			r, j = utf8.DecodeRune(b[i:])
107			switch {
108			case r == '\t':
109				*dest = append(*dest, `\t`...)
110			case r == '\n':
111				*dest = append(*dest, `\n`...)
112			case r == '\f':
113				*dest = append(*dest, `\f`...)
114			case r == '\r':
115				*dest = append(*dest, `\r`...)
116			case r == ',':
117				*dest = append(*dest, `\,`...)
118			case r == ' ':
119				*dest = append(*dest, `\ `...)
120			case r == '=':
121				*dest = append(*dest, `\=`...)
122			case r <= 1<<7-1:
123				*dest = append(*dest, byte(r))
124			case r <= 1<<11-1:
125				*dest = append(*dest, utf8len2|byte(r>>6), utf8bytex|byte(r)&utf8mask)
126			case r <= 1<<16-1:
127				*dest = append(*dest, utf8len3|byte(r>>12), utf8bytex|byte(r>>6)&utf8mask, utf8bytex|byte(r)&utf8mask)
128			default:
129				*dest = append(*dest, utf8len4|byte(r>>18), utf8bytex|byte(r>>12)&utf8mask, utf8bytex|byte(r>>6)&utf8mask, utf8bytex|byte(r)&utf8mask)
130			}
131		}
132		return
133	}
134	*dest = append(*dest, b...)
135}
136
137// Escape a measurement name
138func nameEscapeBytes(dest *[]byte, b []byte) {
139	if bytes.ContainsAny(b, nameEscapes) {
140		var r rune
141		for i, j := 0, 0; i < len(b); i += j {
142			r, j = utf8.DecodeRune(b[i:])
143			switch {
144			case r == '\t':
145				*dest = append(*dest, `\t`...)
146			case r == '\n':
147				*dest = append(*dest, `\n`...)
148			case r == '\f':
149				*dest = append(*dest, `\f`...)
150			case r == '\r':
151				*dest = append(*dest, `\r`...)
152			case r == ',':
153				*dest = append(*dest, `\,`...)
154			case r == ' ':
155				*dest = append(*dest, `\ `...)
156			case r == '\\':
157				*dest = append(*dest, `\\`...)
158			case r <= 1<<7-1:
159				*dest = append(*dest, byte(r))
160			case r <= 1<<11-1:
161				*dest = append(*dest, utf8len2|byte(r>>6), utf8bytex|byte(r)&utf8mask)
162			case r <= 1<<16-1:
163				*dest = append(*dest, utf8len3|byte(r>>12), utf8bytex|byte(r>>6)&utf8mask, utf8bytex|byte(r)&utf8mask)
164			default:
165				*dest = append(*dest, utf8len4|byte(r>>18), utf8bytex|byte(r>>12)&utf8mask, utf8bytex|byte(r>>6)&utf8mask, utf8bytex|byte(r)&utf8mask)
166			}
167		}
168		return
169	}
170	*dest = append(*dest, b...)
171}
172
173func stringFieldEscapeBytes(dest *[]byte, b []byte) {
174	if bytes.ContainsAny(b, stringFieldEscapes) {
175		var r rune
176		for i, j := 0, 0; i < len(b); i += j {
177			r, j = utf8.DecodeRune(b[i:])
178			switch {
179			case r == '\t':
180				*dest = append(*dest, `\t`...)
181			case r == '\n':
182				*dest = append(*dest, `\n`...)
183			case r == '\f':
184				*dest = append(*dest, `\f`...)
185			case r == '\r':
186				*dest = append(*dest, `\r`...)
187			case r == ',':
188				*dest = append(*dest, `\,`...)
189			case r == ' ':
190				*dest = append(*dest, `\ `...)
191			case r == '\\':
192				*dest = append(*dest, `\\`...)
193			case r <= 1<<7-1:
194				*dest = append(*dest, byte(r))
195			case r <= 1<<11-1:
196				*dest = append(*dest, utf8len2|byte(r>>6), utf8bytex|byte(r)&utf8mask)
197			case r <= 1<<16-1:
198				*dest = append(*dest, utf8len3|byte(r>>12), utf8bytex|byte(r>>6)&utf8mask, utf8bytex|byte(r)&utf8mask)
199			default:
200				*dest = append(*dest, utf8len4|byte(r>>18), utf8bytex|byte(r>>12)&utf8mask, utf8bytex|byte(r>>6)&utf8mask, utf8bytex|byte(r)&utf8mask)
201			}
202		}
203		return
204	}
205	*dest = append(*dest, b...)
206}
207
208func unescape(b []byte) string {
209	if bytes.ContainsAny(b, escapes) {
210		return unescaper.Replace(unsafeBytesToString(b))
211	}
212	return string(b)
213}
214
215func nameUnescape(b []byte) string {
216	if bytes.ContainsAny(b, nameEscapes) {
217		return nameUnescaper.Replace(unsafeBytesToString(b))
218	}
219	return string(b)
220}
221
222// unsafeBytesToString converts a []byte to a string without a heap allocation.
223//
224// It is unsafe, and is intended to prepare input to short-lived functions
225// that require strings.
226func unsafeBytesToString(in []byte) string {
227	src := *(*reflect.SliceHeader)(unsafe.Pointer(&in))
228	dst := reflect.StringHeader{
229		Data: src.Data,
230		Len:  src.Len,
231	}
232	s := *(*string)(unsafe.Pointer(&dst))
233	return s
234}
235
236// parseIntBytes is a zero-alloc wrapper around strconv.ParseInt.
237func parseIntBytes(b []byte, base int, bitSize int) (i int64, err error) {
238	s := unsafeBytesToString(b)
239	return strconv.ParseInt(s, base, bitSize)
240}
241
242// parseUintBytes is a zero-alloc wrapper around strconv.ParseUint.
243func parseUintBytes(b []byte, base int, bitSize int) (i uint64, err error) {
244	s := unsafeBytesToString(b)
245	return strconv.ParseUint(s, base, bitSize)
246}
247
248// parseFloatBytes is a zero-alloc wrapper around strconv.ParseFloat.
249func parseFloatBytes(b []byte, bitSize int) (float64, error) {
250	s := unsafeBytesToString(b)
251	return strconv.ParseFloat(s, bitSize)
252}
253
254// parseBoolBytes is a zero-alloc wrapper around strconv.ParseBool.
255func parseBoolBytes(b []byte) (bool, error) {
256	return strconv.ParseBool(unsafeBytesToString(b))
257}
258
259func stringFieldUnescape(b []byte) string {
260	if bytes.ContainsAny(b, stringFieldEscapes) {
261		return stringFieldUnescaper.Replace(unsafeBytesToString(b))
262	}
263	return string(b)
264}
265