Created
February 13, 2015 12:13
-
-
Save gjohnson/f203313ff234238d1cb6 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://github.com/codahale/lunk/blob/master/id.go