Skip to content

Commit dd26313

Browse files
committed
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 680584e commit dd26313

File tree

2 files changed

+509
-54
lines changed

2 files changed

+509
-54
lines changed

uuid.go

Lines changed: 286 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,307 @@ 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 pairity
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+
var reverseHexTable = [256]uint8{
344+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
345+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
346+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
347+
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
348+
255, 10, 11, 12, 13, 14, 15, 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, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
351+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
151352
}
152353

153354
// String returns a human-readable version of this UUID, such as
154355
// 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 {
356+
func (u UUID) String() string {
357+
buf, _ := u.AppendText(make([]byte, 0, 36))
358+
359+
// pulled from the guts of string builder
360+
return unsafe.String(unsafe.SliceData(buf), 36)
361+
}
362+
363+
const hexDigitLower = "0123456789abcdef"
364+
365+
// AppendText converts and appends the uuid onto the given byte slice
366+
// representing a human-readable version of this UUID, such as
367+
// 00001234-0000-1000-8000-00805f9b34fb.
368+
func (u UUID) AppendText(buf []byte) ([]byte, error) {
369+
for i := 3; i >= 0; i-- {
160370
// Insert a hyphen at the correct locations.
161-
if i == 4 || i == 6 || i == 8 || i == 10 {
162-
s.WriteRune('-')
371+
// position 4 and 8
372+
if i != 3 && i != 0 {
373+
buf = append(buf, '-')
163374
}
164375

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

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-
}
379+
buf = append(buf, hexDigitLower[byte(u[i]>>16)>>4])
380+
buf = append(buf, hexDigitLower[byte(u[i]>>16)&0xF])
175381

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

185-
return s.String()
413+
u[0] = uint32(uuid[0]) | uint32(uuid[1])<<8 | uint32(uuid[2])<<16 | uint32(uuid[3])<<24
414+
u[1] = uint32(uuid[4]) | uint32(uuid[5])<<8 | uint32(uuid[6])<<16 | uint32(uuid[7])<<24
415+
u[2] = uint32(uuid[8]) | uint32(uuid[9])<<8 | uint32(uuid[10])<<16 | uint32(uuid[11])<<24
416+
u[3] = uint32(uuid[12]) | uint32(uuid[13])<<8 | uint32(uuid[14])<<16 | uint32(uuid[15])<<24
417+
return nil
186418
}

0 commit comments

Comments
 (0)