package ksuid
import (
"bytes"
"crypto/rand"
"database/sql/driver"
"encoding/binary"
"fmt"
"io"
"math"
"sync"
"time"
)
const (
epochStamp int64 = 1400000000
timestampLengthInBytes = 4
payloadLengthInBytes = 16
byteLength = timestampLengthInBytes + payloadLengthInBytes
stringEncodedLength = 27
minStringEncoded = "000000000000000000000000000"
maxStringEncoded = "aWgEPTl1tmebfsQzFP4bxwgy80V"
)
type KSUID [byteLength ]byte
var (
rander = rand .Reader
randMutex = sync .Mutex {}
randBuffer = [payloadLengthInBytes ]byte {}
errSize = fmt .Errorf ("Valid KSUIDs are %v bytes" , byteLength )
errStrSize = fmt .Errorf ("Valid encoded KSUIDs are %v characters" , stringEncodedLength )
errStrValue = fmt .Errorf ("Valid encoded KSUIDs are bounded by %s and %s" , minStringEncoded , maxStringEncoded )
errPayloadSize = fmt .Errorf ("Valid KSUID payloads are %v bytes" , payloadLengthInBytes )
Nil KSUID
Max = KSUID {255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 }
)
func (i KSUID ) Append (b []byte ) []byte {
return fastAppendEncodeBase62 (b , i [:])
}
func (i KSUID ) Time () time .Time {
return correctedUTCTimestampToTime (i .Timestamp ())
}
func (i KSUID ) Timestamp () uint32 {
return binary .BigEndian .Uint32 (i [:timestampLengthInBytes ])
}
func (i KSUID ) Payload () []byte {
return i [timestampLengthInBytes :]
}
func (i KSUID ) String () string {
return string (i .Append (make ([]byte , 0 , stringEncodedLength )))
}
func (i KSUID ) Bytes () []byte {
return i [:]
}
func (i KSUID ) IsNil () bool {
return i == Nil
}
func (i KSUID ) Get () interface {} {
return i
}
func (i *KSUID ) Set (s string ) error {
return i .UnmarshalText ([]byte (s ))
}
func (i KSUID ) MarshalText () ([]byte , error ) {
return []byte (i .String ()), nil
}
func (i KSUID ) MarshalBinary () ([]byte , error ) {
return i .Bytes (), nil
}
func (i *KSUID ) UnmarshalText (b []byte ) error {
id , err := Parse (string (b ))
if err != nil {
return err
}
*i = id
return nil
}
func (i *KSUID ) UnmarshalBinary (b []byte ) error {
id , err := FromBytes (b )
if err != nil {
return err
}
*i = id
return nil
}
func (i KSUID ) Value () (driver .Value , error ) {
if i .IsNil () {
return nil , nil
}
return i .String (), nil
}
func (i *KSUID ) Scan (src interface {}) error {
switch v := src .(type ) {
case nil :
return i .scan (nil )
case []byte :
return i .scan (v )
case string :
return i .scan ([]byte (v ))
default :
return fmt .Errorf ("Scan: unable to scan type %T into KSUID" , v )
}
}
func (i *KSUID ) scan (b []byte ) error {
switch len (b ) {
case 0 :
*i = Nil
return nil
case byteLength :
return i .UnmarshalBinary (b )
case stringEncodedLength :
return i .UnmarshalText (b )
default :
return errSize
}
}
func Parse (s string ) (KSUID , error ) {
if len (s ) != stringEncodedLength {
return Nil , errStrSize
}
src := [stringEncodedLength ]byte {}
dst := [byteLength ]byte {}
copy (src [:], s [:])
if err := fastDecodeBase62 (dst [:], src [:]); err != nil {
return Nil , errStrValue
}
return FromBytes (dst [:])
}
func timeToCorrectedUTCTimestamp(t time .Time ) uint32 {
return uint32 (t .Unix () - epochStamp )
}
func correctedUTCTimestampToTime(ts uint32 ) time .Time {
return time .Unix (int64 (ts )+epochStamp , 0 )
}
func New () KSUID {
ksuid , err := NewRandom ()
if err != nil {
panic (fmt .Sprintf ("Couldn't generate KSUID, inconceivable! error: %v" , err ))
}
return ksuid
}
func NewRandom () (ksuid KSUID , err error ) {
return NewRandomWithTime (time .Now ())
}
func NewRandomWithTime (t time .Time ) (ksuid KSUID , err error ) {
randMutex .Lock ()
_, err = io .ReadAtLeast (rander , randBuffer [:], len (randBuffer ))
copy (ksuid [timestampLengthInBytes :], randBuffer [:])
randMutex .Unlock ()
if err != nil {
ksuid = Nil
return
}
ts := timeToCorrectedUTCTimestamp (t )
binary .BigEndian .PutUint32 (ksuid [:timestampLengthInBytes ], ts )
return
}
func FromParts (t time .Time , payload []byte ) (KSUID , error ) {
if len (payload ) != payloadLengthInBytes {
return Nil , errPayloadSize
}
var ksuid KSUID
ts := timeToCorrectedUTCTimestamp (t )
binary .BigEndian .PutUint32 (ksuid [:timestampLengthInBytes ], ts )
copy (ksuid [timestampLengthInBytes :], payload )
return ksuid , nil
}
func FromBytes (b []byte ) (KSUID , error ) {
var ksuid KSUID
if len (b ) != byteLength {
return Nil , errSize
}
copy (ksuid [:], b )
return ksuid , nil
}
func SetRand (r io .Reader ) {
if r == nil {
rander = rand .Reader
return
}
rander = r
}
func Compare (a , b KSUID ) int {
return bytes .Compare (a [:], b [:])
}
func Sort (ids []KSUID ) {
quickSort (ids , 0 , len (ids )-1 )
}
func IsSorted (ids []KSUID ) bool {
if len (ids ) != 0 {
min := ids [0 ]
for _ , id := range ids [1 :] {
if bytes .Compare (min [:], id [:]) > 0 {
return false
}
min = id
}
}
return true
}
func quickSort(a []KSUID , lo int , hi int ) {
if lo < hi {
pivot := a [hi ]
i := lo - 1
for j , n := lo , hi ; j != n ; j ++ {
if bytes .Compare (a [j ][:], pivot [:]) < 0 {
i ++
a [i ], a [j ] = a [j ], a [i ]
}
}
i ++
if bytes .Compare (a [hi ][:], a [i ][:]) < 0 {
a [i ], a [hi ] = a [hi ], a [i ]
}
quickSort (a , lo , i -1 )
quickSort (a , i +1 , hi )
}
}
func (id KSUID ) Next () KSUID {
zero := makeUint128 (0 , 0 )
t := id .Timestamp ()
u := uint128Payload (id )
v := add128 (u , makeUint128 (0 , 1 ))
if v == zero {
t ++
}
return v .ksuid (t )
}
func (id KSUID ) Prev () KSUID {
max := makeUint128 (math .MaxUint64 , math .MaxUint64 )
t := id .Timestamp ()
u := uint128Payload (id )
v := sub128 (u , makeUint128 (0 , 1 ))
if v == max {
t --
}
return v .ksuid (t )
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .