blob: e11dacfa91f5f437475197ca91d524fee8e579e5 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package syncbase
import (
"bytes"
"crypto/rand"
"encoding/base64"
"encoding/binary"
"fmt"
"sync/atomic"
"time"
)
const (
// First character of UUID to make the UUID start with a valid identifier character.
// Can be used for versioning.
uuidPrefix string = "v"
// Legal characters of Java identifiers in ASCII-encoding order.
uuidCharacterSet string = "$0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"
)
var (
invocationCounter uint32
base64Encoding *base64.Encoding
)
func init() {
base64Encoding = base64.NewEncoding(uuidCharacterSet).WithPadding(base64.NoPadding)
}
// Truncates string 's' to 'length'. Will remove characters from the beginning of the string
// if 's' is too long.
func truncate(s string, length int) string {
if len(s) > length {
return s[len(s)-length:]
} else {
return s
}
}
// Pad string 's' to 'length'. Will prefix 's' with sequences of 'padding' if the string
// is too short. If 'padding' has more than one character, the resulting string may be
// longer than 'length'.
func pad(s string, length int, padding string) string {
for len(s) < length {
s = padding + s
}
return s
}
// UUID generates a sequential and pseudo-random key that can be used as an ID within
// Syncbase. The generated IDs are of the format [a-zA-z][a-zA-Z0-9$_]{19}
//
// A UUID is made up of four parts:
// - a prefix which guarantees that the generated UUID is a valid variable name in most programming
// languages
// - a time component which uses nanosecond precision
// - a counter component which is used to guarantee ordering between calls
// - a random component which achieves pseudo-randomness
func UUID() string {
buffer := new(bytes.Buffer)
// Encode the current time in nanoseconds as a base64-character string.
// The maximum encoded length is 11 characters: 2^63/64^11 < 1
currentTime := time.Now().UnixNano()
binary.Write(buffer, binary.BigEndian, currentTime)
uuidTime := base64Encoding.EncodeToString(buffer.Bytes())
// Increment the global count of invocations and encode as a 3-character base64-string.
// Note that overflows here are fine.
buffer.Reset()
currentInvocation := atomic.AddUint32(&invocationCounter, 1)
binary.Write(buffer, binary.BigEndian, currentInvocation)
uuidInvocation := base64Encoding.EncodeToString(buffer.Next(4)[1:])
// Encode 4 bytes of randomness as a base64-string (maximum of 6 characters)
random := make([]byte, 4)
rand.Read(random)
uuidRandNumber := base64Encoding.EncodeToString(random)
return fmt.Sprintf(
"%s%s%s%s",
uuidPrefix,
pad(uuidTime, 11, uuidCharacterSet[:1]),
pad(truncate(uuidInvocation, 2), 2, uuidCharacterSet[:1]),
pad(uuidRandNumber, 6, uuidCharacterSet[:1]))
}