Skip to content

Instantly share code, notes, and snippets.

@gjohnson
Created February 13, 2015 12:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gjohnson/f203313ff234238d1cb6 to your computer and use it in GitHub Desktop.
Save gjohnson/f203313ff234238d1cb6 to your computer and use it in GitHub Desktop.
package lunk
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/json"
"fmt"
"io"
"strconv"
"sync"
"unsafe"
)
// An ID is a unique, uniformly distributed 64-bit ID.
type ID uint64
// String returns the ID as a hex string.
func (id ID) String() string {
return fmt.Sprintf("%016x", uint64(id))
}
// MarshalJSON encodes the ID as a hex string.
func (id ID) MarshalJSON() ([]byte, error) {
return json.Marshal(id.String())
}
// UnmarshalJSON decodes the given data as either a hexadecimal string or JSON
// integer.
func (id *ID) UnmarshalJSON(data []byte) error {
i, err := parseJSONString(data)
if err == nil {
*id = i
return nil
}
i, err = parseJSONInt(data)
if err == nil {
*id = i
return nil
}
return fmt.Errorf("%s is not a valid ID", data)
}
// ParseID parses the given string as a hexadecimal string.
func ParseID(s string) (ID, error) {
i, err := strconv.ParseUint(s, 16, 64)
if err != nil {
return 0, err
}
return ID(i), nil
}
// generateID returns a randomly-generated 64-bit ID. This function is
// thread-safe. IDs are produced by consuming an AES-CTR-128 keystream in
// 64-bit chunks. The AES key is randomly generated on initialization, as is the
// counter's initial state. On machines with AES-NI support, ID generation takes
// ~30ns and generates no garbage.
func generateID() ID {
m.Lock()
if n == aes.BlockSize {
c.Encrypt(b, ctr)
for i := aes.BlockSize - 1; i >= 0; i-- { // increment ctr
ctr[i]++
if ctr[i] != 0 {
break
}
}
n = 0
}
id := *(*ID)(unsafe.Pointer(&b[n])) // zero-copy b/c we're arch-neutral
n += idSize
m.Unlock()
return id
}
const (
idSize = aes.BlockSize / 2 // 64 bits
keySize = aes.BlockSize // 128 bits
)
var (
ctr []byte
n int
b []byte
c cipher.Block
m sync.Mutex
)
func init() {
buf := make([]byte, keySize+aes.BlockSize)
_, err := io.ReadFull(rand.Reader, buf)
if err != nil {
panic(err) // /dev/urandom had better work
}
c, err = aes.NewCipher(buf[:keySize])
if err != nil {
panic(err) // AES had better work
}
n = aes.BlockSize
ctr = buf[keySize:]
b = make([]byte, aes.BlockSize)
}
func parseJSONString(data []byte) (ID, error) {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return 0, err
}
i, err := ParseID(s)
if err != nil {
return 0, err
}
return i, nil
}
func parseJSONInt(data []byte) (ID, error) {
var i uint64
if err := json.Unmarshal(data, &i); err != nil {
return 0, err
}
return ID(i), nil
}
@gjohnson
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment