Skip to content

Commit 0d1c723

Browse files
onshorechetdeadprogram
authored andcommitted
UUIDs: implement encoding methods, enhance testing and improve performance
This changeset implements these interfaces found in the encoding package:  - MarshalBinary() (data []byte, err error)  - UnmarshalBinary(data []byte) error  - AppendBinary(b []byte) ([]byte, error)  - MarshalText() (text []byte, err error)  - UnmarshalText(text []byte) error  - AppendText(b []byte) ([]byte, error) ParseUUID and uuid.String are now implemented in terms of these new methods. UnmarshalText and AppendText use algorithms inspired by the hex package. Also the UUID.String method has been optimized to avoid allocations by using unsafe.String. Finally the new UnmarshalText supports 128, 32 and 16 bit uuids. Additional tests were added to verify the new methods and some existing methods missing coverage. Benchmark results (using benchstat, 10 runs each):  - `ParseUUID`:  ~71.04% faster (p=0.000, n=10)  - `UUID.String()`: ~81.26% faster (p=0.000, n=10)  - `UUID.String()`: Memory allocations reduced by 100% (48 B/op to 0 B/op)
1 parent a9037ef commit 0d1c723

File tree

2 files changed

+512
-54
lines changed

2 files changed

+512
-54
lines changed

uuid.go

Lines changed: 290 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ package bluetooth
55

66
import (
77
"errors"
8-
"strings"
8+
"unsafe"
99
)
1010

1111
// UUID is a single UUID as used in the Bluetooth stack. It is represented as a
@@ -112,75 +112,311 @@ func (uuid UUID) Bytes() [16]byte {
112112
return buf
113113
}
114114

115-
// ParseUUID parses the given UUID, which must be in
116-
// 00001234-0000-1000-8000-00805f9b34fb format. This means that it cannot (yet)
117-
// parse 16-bit UUIDs unless they are serialized as a 128-bit UUID. If the UUID
118-
// cannot be parsed, an error is returned. It will always successfully parse
119-
// UUIDs generated by UUID.String().
115+
// AppendBinary appends the bytes of the uuid to the given byte slice b.
116+
func (uuid UUID) AppendBinary(b []byte) ([]byte, error) {
117+
return append(b,
118+
byte(uuid[0]),
119+
byte(uuid[0]>>8),
120+
byte(uuid[0]>>16),
121+
byte(uuid[0]>>24),
122+
byte(uuid[1]),
123+
byte(uuid[1]>>8),
124+
byte(uuid[1]>>16),
125+
byte(uuid[1]>>24),
126+
byte(uuid[2]),
127+
byte(uuid[2]>>8),
128+
byte(uuid[2]>>16),
129+
byte(uuid[2]>>24),
130+
byte(uuid[3]),
131+
byte(uuid[3]>>8),
132+
byte(uuid[3]>>16),
133+
byte(uuid[3]>>24),
134+
), nil
135+
}
136+
137+
// MarshalBinary marshals the uuid into and byte slice and returns the slice. It will not return an error
138+
func (uuid UUID) MarshalBinary() (data []byte, err error) {
139+
return uuid.AppendBinary(make([]byte, 0, 16))
140+
}
141+
142+
// ParseUUID parses the given UUID
143+
//
144+
// Expected formats:
145+
//
146+
// 00001234-0000-1000-8000-00805f9b34fb
147+
// 00001234
148+
// 1234
149+
//
150+
// If the UUID cannot be parsed, an error is returned.
151+
// It will always successfully parse UUIDs generated by UUID.String().
120152
func ParseUUID(s string) (uuid UUID, err error) {
121-
uuidIndex := 0
122-
for i := 0; i < len(s); i++ {
123-
c := s[i]
124-
if c == '-' {
125-
continue
153+
err = (&uuid).UnmarshalText([]byte(s))
154+
return
155+
}
156+
157+
// UnmarshalText unmarshals a text representation of a UUID.
158+
//
159+
// Expected formats:
160+
//
161+
// 00001234-0000-1000-8000-00805f9b34fb
162+
// 00001234
163+
// 1234
164+
//
165+
// If the UUID cannot be parsed, an error is returned.
166+
// It will always successfully parse UUIDs generated by UUID.String().
167+
// This method is an adaptation of hex.Decode idea of using a reverse hex table.
168+
func (u *UUID) UnmarshalText(s []byte) error {
169+
switch len(s) {
170+
case 36:
171+
return u.unmarshalText128(s)
172+
case 8:
173+
return u.unmarshalText32(s)
174+
case 4:
175+
return u.unmarshalText16(s)
176+
default:
177+
return errInvalidUUID
178+
}
179+
}
180+
181+
// Using the reverseHexTable rebuild the UUID from the string s represented in bytes
182+
// This implementation is the inverse of MarshalText and reaches performance parity
183+
func (u *UUID) unmarshalText128(s []byte) error {
184+
var j uint8
185+
for i := 3; i >= 0; i-- {
186+
// Skip hyphens
187+
if s[j] == '-' {
188+
j++
189+
}
190+
191+
if reverseHexTable[s[j]] == 255 {
192+
return errInvalidUUID
193+
}
194+
u[i] |= uint32(reverseHexTable[s[j]]) << 28
195+
j++
196+
197+
if reverseHexTable[s[j]] == 255 {
198+
return errInvalidUUID
199+
}
200+
u[i] |= uint32(reverseHexTable[s[j]]) << 24
201+
j++
202+
203+
if reverseHexTable[s[j]] == 255 {
204+
return errInvalidUUID
205+
}
206+
u[i] |= uint32(reverseHexTable[s[j]]) << 20
207+
j++
208+
209+
if reverseHexTable[s[j]] == 255 {
210+
return errInvalidUUID
126211
}
127-
var nibble byte
128-
if c >= '0' && c <= '9' {
129-
nibble = c - '0' + 0x0
130-
} else if c >= 'a' && c <= 'f' {
131-
nibble = c - 'a' + 0xa
132-
} else if c >= 'A' && c <= 'F' {
133-
nibble = c - 'A' + 0xa
134-
} else {
135-
err = errInvalidUUID
136-
return
212+
u[i] |= uint32(reverseHexTable[s[j]]) << 16
213+
j++
214+
215+
// skip hypens
216+
if s[j] == '-' {
217+
j++
218+
}
219+
220+
if reverseHexTable[s[j]] == 255 {
221+
return errInvalidUUID
137222
}
138-
if uuidIndex > 31 {
139-
err = errInvalidUUID
140-
return
223+
u[i] |= uint32(reverseHexTable[s[j]]) << 12
224+
j++
225+
226+
if reverseHexTable[s[j]] == 255 {
227+
return errInvalidUUID
228+
}
229+
u[i] |= uint32(reverseHexTable[s[j]]) << 8
230+
j++
231+
232+
if reverseHexTable[s[j]] == 255 {
233+
return errInvalidUUID
234+
}
235+
u[i] |= uint32(reverseHexTable[s[j]]) << 4
236+
j++
237+
238+
if reverseHexTable[s[j]] == 255 {
239+
return errInvalidUUID
141240
}
142-
uuid[3-uuidIndex/8] |= uint32(nibble) << (4 * (7 - uuidIndex%8))
143-
uuidIndex++
241+
u[i] |= uint32(reverseHexTable[s[j]])
242+
j++
144243
}
145-
if uuidIndex != 32 {
146-
// The UUID doesn't have exactly 32 nibbles. Perhaps a 16-bit or 32-bit
147-
// UUID?
148-
err = errInvalidUUID
244+
245+
return nil
246+
}
247+
248+
// Using the reverseHexTable rebuild the UUID from the string s represented in bytes
249+
// This implementation is the inverse of MarshalText and reaches performance pairity
250+
func (u *UUID) unmarshalText32(s []byte) error {
251+
u[0] = 0x5F9B34FB
252+
u[1] = 0x80000080
253+
u[2] = 0x00001000
254+
255+
var j uint8 = 0
256+
257+
if reverseHexTable[s[j]] == 255 {
258+
return errInvalidUUID
149259
}
150-
return
260+
u[3] |= uint32(reverseHexTable[s[j]]) << 28
261+
j++
262+
263+
if reverseHexTable[s[j]] == 255 {
264+
return errInvalidUUID
265+
}
266+
u[3] |= uint32(reverseHexTable[s[j]]) << 24
267+
j++
268+
269+
if reverseHexTable[s[j]] == 255 {
270+
return errInvalidUUID
271+
}
272+
u[3] |= uint32(reverseHexTable[s[j]]) << 20
273+
j++
274+
275+
if reverseHexTable[s[j]] == 255 {
276+
return errInvalidUUID
277+
}
278+
u[3] |= uint32(reverseHexTable[s[j]]) << 16
279+
j++
280+
281+
if reverseHexTable[s[j]] == 255 {
282+
return errInvalidUUID
283+
}
284+
u[3] |= uint32(reverseHexTable[s[j]]) << 12
285+
j++
286+
287+
if reverseHexTable[s[j]] == 255 {
288+
return errInvalidUUID
289+
}
290+
u[3] |= uint32(reverseHexTable[s[j]]) << 8
291+
j++
292+
293+
if reverseHexTable[s[j]] == 255 {
294+
return errInvalidUUID
295+
}
296+
u[3] |= uint32(reverseHexTable[s[j]]) << 4
297+
j++
298+
299+
if reverseHexTable[s[j]] == 255 {
300+
return errInvalidUUID
301+
}
302+
u[3] |= uint32(reverseHexTable[s[j]])
303+
j++
304+
305+
return nil
306+
}
307+
308+
// Using the reverseHexTable rebuild the UUID from the string s represented in bytes
309+
// This implementation is the inverse of MarshalText and reaches performance pairity
310+
func (u *UUID) unmarshalText16(s []byte) error {
311+
u[0] = 0x5F9B34FB
312+
u[1] = 0x80000080
313+
u[2] = 0x00001000
314+
315+
var j uint8 = 0
316+
if reverseHexTable[s[j]] == 255 {
317+
return errInvalidUUID
318+
}
319+
u[3] |= uint32(reverseHexTable[s[j]]) << 12
320+
j++
321+
322+
if reverseHexTable[s[j]] == 255 {
323+
return errInvalidUUID
324+
}
325+
u[3] |= uint32(reverseHexTable[s[j]]) << 8
326+
j++
327+
328+
if reverseHexTable[s[j]] == 255 {
329+
return errInvalidUUID
330+
}
331+
u[3] |= uint32(reverseHexTable[s[j]]) << 4
332+
j++
333+
334+
if reverseHexTable[s[j]] == 255 {
335+
return errInvalidUUID
336+
}
337+
u[3] |= uint32(reverseHexTable[s[j]])
338+
j++
339+
340+
return nil
341+
}
342+
343+
// This table is structured such that an ascii byte can be directly used as the array key
344+
// to directly look up the decoded uint8 value. This is a similar solution to
345+
// the reverseHexTable found in the hex package. 255 is a sentinel value
346+
// indicating an invalid hex byte.
347+
var reverseHexTable = [256]uint8{
348+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
349+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
350+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
351+
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
352+
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
353+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
354+
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
355+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
151356
}
152357

153358
// String returns a human-readable version of this UUID, such as
154359
// 00001234-0000-1000-8000-00805f9b34fb.
155-
func (uuid UUID) String() string {
156-
var s strings.Builder
157-
s.Grow(36)
158-
raw := uuid.Bytes()
159-
for i := range raw {
360+
func (u UUID) String() string {
361+
buf, _ := u.AppendText(make([]byte, 0, 36))
362+
363+
// pulled from the guts of string builder
364+
return unsafe.String(unsafe.SliceData(buf), 36)
365+
}
366+
367+
const hexDigitLower = "0123456789abcdef"
368+
369+
// AppendText converts and appends the uuid onto the given byte slice
370+
// representing a human-readable version of this UUID, such as
371+
// 00001234-0000-1000-8000-00805f9b34fb.
372+
func (u UUID) AppendText(buf []byte) ([]byte, error) {
373+
for i := 3; i >= 0; i-- {
160374
// Insert a hyphen at the correct locations.
161-
if i == 4 || i == 6 || i == 8 || i == 10 {
162-
s.WriteRune('-')
375+
// position 4 and 8
376+
if i != 3 && i != 0 {
377+
buf = append(buf, '-')
163378
}
164379

165-
// The character to convert to hex.
166-
c := raw[15-i]
380+
buf = append(buf, hexDigitLower[byte(u[i]>>24)>>4])
381+
buf = append(buf, hexDigitLower[byte(u[i]>>24)&0xF])
167382

168-
// First nibble.
169-
nibble := c >> 4
170-
if nibble <= 9 {
171-
s.WriteByte(nibble + '0')
172-
} else {
173-
s.WriteByte(nibble + 'a' - 10)
174-
}
383+
buf = append(buf, hexDigitLower[byte(u[i]>>16)>>4])
384+
buf = append(buf, hexDigitLower[byte(u[i]>>16)&0xF])
175385

176-
// Second nibble.
177-
nibble = c & 0x0f
178-
if nibble <= 9 {
179-
s.WriteByte(nibble + '0')
180-
} else {
181-
s.WriteByte(nibble + 'a' - 10)
386+
// Insert a hyphen at the correct locations.
387+
// position 6 and 10
388+
if i == 2 || i == 1 {
389+
buf = append(buf, '-')
182390
}
391+
392+
buf = append(buf, hexDigitLower[byte(u[i]>>8)>>4])
393+
buf = append(buf, hexDigitLower[byte(u[i]>>8)&0xF])
394+
395+
buf = append(buf, hexDigitLower[byte(u[i])>>4])
396+
buf = append(buf, hexDigitLower[byte(u[i])&0xF])
397+
}
398+
399+
return buf, nil
400+
}
401+
402+
// MarshalText returns the converted uuid as a bytle slice
403+
// representing a human-readable version, such as
404+
// 00001234-0000-1000-8000-00805f9b34fb.
405+
func (u UUID) MarshalText() ([]byte, error) {
406+
return u.AppendText(make([]byte, 0, 36))
407+
}
408+
409+
var ErrInvalidBinaryUUID = errors.New("bluetooth: failed to unmarshal the given binary UUID")
410+
411+
// UnmarshalBinary copies the given uuid bytes onto itself
412+
func (u *UUID) UnmarshalBinary(uuid []byte) error {
413+
if len(uuid) != 16 {
414+
return ErrInvalidBinaryUUID
183415
}
184416

185-
return s.String()
417+
u[0] = uint32(uuid[0]) | uint32(uuid[1])<<8 | uint32(uuid[2])<<16 | uint32(uuid[3])<<24
418+
u[1] = uint32(uuid[4]) | uint32(uuid[5])<<8 | uint32(uuid[6])<<16 | uint32(uuid[7])<<24
419+
u[2] = uint32(uuid[8]) | uint32(uuid[9])<<8 | uint32(uuid[10])<<16 | uint32(uuid[11])<<24
420+
u[3] = uint32(uuid[12]) | uint32(uuid[13])<<8 | uint32(uuid[14])<<16 | uint32(uuid[15])<<24
421+
return nil
186422
}

0 commit comments

Comments
 (0)