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